laravel-shift / blueprint

A code generation tool for Laravel developers.
MIT License
2.9k stars 276 forks source link

Provide JSON Schema for draft.yaml to SchemaStore for IDE autocompletion #708

Open edjw opened 1 week ago

edjw commented 1 week ago

Just discovering Blueprint in the last couple of days and noted a couple of things that might make it easier to pick up.

I'm really used to autocomplete, inline validation etc in my editor and missed these things while making my draft.yaml.

The JSON Schema Store project might offer a way to provide these nice things for developers for Blueprint.

Synopsis:

Create a JSON Schema for Blueprint to provide autocompletion, validation, and tooltips to the draft.yaml

PhpStorm seems to include the schema validation by default. Red Hat's YAML extension provides it for VS Code.

You then get some nice autocomplete, some tooltip docs, some validation.

Here's a couple of examples of what that looks like in VS Code with a Github Workflow yaml. CleanShot 2024-11-18 at 14 33 01@2x CleanShot 2024-11-18 at 14 34 07@2x

Proposed steps:

  1. Create a JSON Schema.
  2. Link to it in the JSON Schema Store.

Docs here on creating a schema: https://json-schema.org/learn/getting-started-step-by-step

There is a list of all other JSON Schemas here: https://www.schemastore.org/json/

It seems you can add the file in Blueprint's own repo or add to the SchemaStore repo directly: https://github.com/SchemaStore/schemastore/blob/master/src/api/json/catalog.json

Expected Behavior:

In supported JSON editors like Visual Studio and Visual Studio Code, developers get auto-completion and validation to make sure their JSON document is correct.

When a JSON editor supports schemas, tooltips can help inform the user about the various properties and values.

It is supported in VS Code, PhpStorm, NeoVim and lots of other editors.

jasonmccreary commented 1 week ago

This would be great! I'm not that familiar with writing one of these. So I'd have to learn. If you know and could provide even a basic example for one of the model statements, that would be awesome.

edjw commented 1 week ago

Hi, here's an example of what that might look like. Hopefully this is a helpful step forward for you :-)

I should say upfront that Claude helped a lot with this – I don't know how you'll feel about that. I gave it the Laravel Blueprint docs and several other schema.json files to help it work out what to do.

The schema seems to work with a few draft.yaml files I can find around blogposts and Github. But it could definitely use some more testing. Do you have lots of valid draft.yaml files that could be tested against it? I expect that you might see some things to change as someone who obviously knows a lot about Blueprint!

The regex patterns are a bit much in places. There might be a cleaner way to do this

The error messages could be more helpful instead of saying it doesn't match a long regex.

Ideally, this would error but currently it doesn't name: stringasdf

Obviously there'd be a maintenance burden here to update the schema when the project changed.

You can test it in vscode by putting this in .vscode/settings.json where schema.json and draft.yaml are in the root of your project

{
    "yaml.schemas": {
        "./schema.json": [
            "draft.yaml"
        ],
    }
}
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "title": "Laravel Blueprint Schema",
    "description": "JSON Schema for Laravel Blueprint YAML definitions",
    "definitions": {
        "columnType": {
            "type": "string",
            "enum": [
                "bigIncrements",
                "bigInteger",
                "binary",
                "boolean",
                "char",
                "dateTimeTz",
                "dateTime",
                "date",
                "decimal",
                "double",
                "enum",
                "float",
                "foreignId",
                "foreignIdFor",
                "foreignUlid",
                "foreignUuid",
                "geography",
                "geometry",
                "id",
                "increments",
                "integer",
                "ipAddress",
                "json",
                "jsonb",
                "longText",
                "macAddress",
                "mediumInteger",
                "mediumText",
                "morphs",
                "nullableMorphs",
                "nullableTimestamps",
                "nullableUlidMorphs",
                "nullableUuidMorphs",
                "rememberToken",
                "set",
                "smallInteger",
                "softDeletesTz",
                "softDeletes",
                "string",
                "text",
                "timeTz",
                "time",
                "timestampTz",
                "timestamp",
                "timestampsTz",
                "timestamps",
                "tinyInteger",
                "tinyText",
                "unsignedBigInteger",
                "unsignedInteger",
                "unsignedSmallInteger",
                "unsignedTinyInteger",
                "ulidMorphs",
                "uuidMorphs",
                "ulid",
                "uuid",
                "year"
            ]
        },
        "columnModifier": {
            "type": "string",
            "enum": [
                "autoIncrement",
                "charset",
                "collation",
                "default",
                "foreign",
                "index",
                "nullable",
                "primary",
                "unique",
                "unsigned",
                "useCurrent",
                "useCurrentOnUpdate"
            ]
        },
        "relationship": {
            "type": "string",
            "pattern": "^[A-Z][a-zA-Z0-9]*(:[a-zA-Z0-9_]+)?(,\\s*[A-Z][a-zA-Z0-9]*(:[a-zA-Z0-9_]+)?)*$",
            "examples": [
                "Project",
                "User",
                "Project, User",
                "Project:client, User:owner"
            ]
        },
        "controllerStatement": {
            "type": "object",
            "patternProperties": {
                "^(delete|dispatch|find|fire|flash|notify|query|redirect|render|resource|respond|save|send|store|update|validate)$": {
                    "type": "string"
                }
            },
            "additionalProperties": false
        }
    },
    "properties": {
        "models": {
            "type": "object",
            "description": "Define Eloquent models with their columns, relationships, and properties",
            "patternProperties": {
                "^[A-Z][a-zA-Z0-9]*(\\\\[A-Z][a-zA-Z0-9]*)*$": {
                    "type": "object",
                    "description": "Model definition",
                    "properties": {
                        "id": {
                            "oneOf": [
                                {
                                    "type": "boolean"
                                },
                                {
                                    "type": "string",
                                    "enum": [
                                        "id",
                                        "uuid"
                                    ]
                                }
                            ]
                        },
                        "timestamps": {
                            "type": "boolean"
                        },
                        "timestampsTz": {
                            "type": "boolean"
                        },
                        "softDeletes": {
                            "type": "boolean"
                        },
                        "softDeletesTz": {
                            "type": "boolean"
                        },
                        "relationships": {
                            "type": "object",
                            "properties": {
                                "hasMany": {
                                    "$ref": "#/definitions/relationship"
                                },
                                "belongsTo": {
                                    "$ref": "#/definitions/relationship"
                                },
                                "hasOne": {
                                    "$ref": "#/definitions/relationship"
                                },
                                "belongsToMany": {
                                    "$ref": "#/definitions/relationship"
                                }
                            },
                            "additionalProperties": false
                        }
                    },
                    "patternProperties": {
                        "^[a-z_][a-zA-Z0-9_]*$": {
                            "type": "string",
                            "description": "Column definition with valid type, optional parameters and modifiers"
                        }
                    },
                    "additionalProperties": false
                }
            }
        },
        "controllers": {
            "type": "object",
            "description": "Define controllers with their actions and statements",
            "patternProperties": {
                "^[A-Z][a-zA-Z0-9]*(Controller)?(\\\\[A-Z][a-zA-Z0-9]*)*$": {
                    "type": "object",
                    "properties": {
                        "resource": {
                            "oneOf": [
                                {
                                    "type": "boolean"
                                },
                                {
                                    "type": "string",
                                    "pattern": "^(web|api|api\\.[a-z]+|[a-z]+(,\\s*[a-z]+)*)?$"
                                }
                            ]
                        },
                        "invokable": {
                            "oneOf": [
                                {
                                    "type": "boolean"
                                },
                                {
                                    "$ref": "#/definitions/controllerStatement"
                                }
                            ]
                        }
                    },
                    "patternProperties": {
                        "^[a-z_][a-zA-Z0-9_]*$": {
                            "$ref": "#/definitions/controllerStatement"
                        }
                    },
                    "additionalProperties": false
                }
            }
        },
        "seeders": {
            "type": "string",
            "description": "Comma-separated list of models to generate seeders for",
            "pattern": "^[A-Z][a-zA-Z0-9]*(,\\s*[A-Z][a-zA-Z0-9]*)*$",
            "examples": [
                "Post",
                "Post, Comment, User"
            ]
        }
    },
    "required": [
        "models"
    ],
    "additionalProperties": false
}