feat(11.1-05): pin the walkthrough to the scaffolder and link it from the concept map
- TestScaffoldLayout runs make:plugin, make:model, make:migration, make:admin-controller and make:command for acme.blog in a copy of examples/hello and compares the file set with docs/examples/blog - the page lists the exact make commands, the go.mod a scaffolded plugin gets, what the scaffolder leaves to the developer and a checklist - scaffolding.md no longer claims same-second migrations get consecutive timestamps; only same-name ones do - coming-from-wintercms.md and index.md link the walkthrough
This commit is contained in:
@@ -10,6 +10,8 @@ SummerCMS keeps the parts of WinterCMS that make a plugin developer productive.
|
||||
|
||||
What changes is everything that depends on PHP at runtime. Plugins are Go packages compiled into one binary, so there is no plugin directory scanned at boot and no runtime autoloading. Magic methods, dynamic properties and behaviours give way to Go interfaces and composition. SummerCMS is headless: it serves a JSON API and the admin SPA, and the frontend is a separate application that calls that API.
|
||||
|
||||
This page maps the concepts. To see them applied to one plugin from start to finish, follow [Porting a plugin](porting-a-plugin.md), which takes an `acme/blog` plugin with a model, migrations, a route, a backend controller and a console command to SummerCMS.
|
||||
|
||||
## Concept map
|
||||
|
||||
Each row names the WinterCMS concept, the SummerCMS identifiers that replace it, and where to read more: the guide page first, then the module reference.
|
||||
|
||||
@@ -10,6 +10,52 @@ This walkthrough ports a small WinterCMS plugin, `Acme.Blog`, to SummerCMS. The
|
||||
|
||||
The name `acme/blog` is a neutral example. The SummerCMS code on this page is not a sketch: every Go and YAML block is a copy of a file under `docs/examples/blog` in the framework repository, a compiled plugin whose tests activate it, serve its route, run its command and run its migrations up and down against PostgreSQL. Read [Coming from WinterCMS](coming-from-wintercms.md) first for the map of concepts.
|
||||
|
||||
## Scaffold it yourself
|
||||
|
||||
Every file of the plugin starts as scaffolder output. From the application directory, these commands produce the same file layout as `docs/examples/blog`, which a test in the framework checks:
|
||||
|
||||
```sh
|
||||
summer make:plugin acme.blog
|
||||
summer make:model acme.blog Post
|
||||
summer make:migration acme.blog AddPublishedAt
|
||||
summer make:admin-controller acme.blog Posts
|
||||
summer make:command acme.blog Publish
|
||||
summer plugin:add plugins/blog
|
||||
summer build
|
||||
```
|
||||
|
||||
| Command | Writes |
|
||||
|---------|--------|
|
||||
| `summer make:plugin acme.blog` | `plugins/blog` as its own Go module: `plugin.go`, `routes.go`, `registry.gen.go`, `go.mod`, `config/config.yaml`, `lang/en/lang.yaml`, `views/mail/welcome.htm` and a `doc.go` in `classes`, `console`, `controllers`, `jobs`, `middleware`, `models` and `updates` |
|
||||
| `summer make:model acme.blog Post` | `models/post.go` and `updates/<timestamp>_create_acme_blog_posts.go` |
|
||||
| `summer make:migration acme.blog AddPublishedAt` | `updates/<timestamp>_add_published_at.go` |
|
||||
| `summer make:admin-controller acme.blog Posts` | `controllers/posts.go`, `controllers/posts/config_form.yaml`, `controllers/posts/config_list.yaml`, `models/posts/fields.yaml` and `models/posts/columns.yaml` |
|
||||
| `summer make:command acme.blog Publish` | `console/publish.go` |
|
||||
| `summer plugin:add plugins/blog` | The plugin in `summer.yaml`, a `require` and a local `replace` in the application's `go.mod`, and the directory in `go.work` |
|
||||
| `summer build` | `bin/acme`, the application binary with the plugin compiled in |
|
||||
|
||||
Each `make:` command also rewrites `registry.gen.go` and runs `go mod tidy` in the plugin. The sections below fill in what each generated file leaves empty. See [Scaffolding](../console/scaffolding.md) for every option of these commands.
|
||||
|
||||
The copy in the framework repository has no `go.mod`, because it is a package of the framework module so that the framework's own `go test ./...` covers it. A plugin you scaffold is a module of its own, and its `go.mod` starts like this for an application whose module is `example.com/acme` (indirect requirements left out):
|
||||
|
||||
```text
|
||||
module example.com/acme/plugins/blog
|
||||
|
||||
go 1.27.0
|
||||
|
||||
toolchain go1.27.0
|
||||
|
||||
require (
|
||||
git.golem15.com/golem15/summercms v0.0.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.7
|
||||
gorm.io/gorm v1.31.2
|
||||
)
|
||||
|
||||
replace git.golem15.com/golem15/summercms => ../../../summercms.go
|
||||
```
|
||||
|
||||
The `replace` points at the same framework checkout as the application's `go.mod`, so the plugin builds against your local framework while you develop.
|
||||
|
||||
## Plugin registration
|
||||
|
||||
In WinterCMS, `Plugin.php` describes the plugin and registers what it adds:
|
||||
@@ -758,3 +804,26 @@ summer build
|
||||
```
|
||||
|
||||
See [Writing commands](../console/writing-commands.md) for arguments, flags, prompts and output.
|
||||
|
||||
## What the scaffolder leaves to you
|
||||
|
||||
The `make:` commands write files that compile, not a finished plugin. These are the steps this walkthrough had to do by hand, and the scaffolder behaviour behind them:
|
||||
|
||||
- **The generated-code header stays.** Files the `make:` commands write start with `// Code generated by summer make. DO NOT EDIT.`, although you are meant to edit them. Keep the line: the commands rebuild `registry.gen.go` from the files that carry it, so a model, migration, command or admin controller whose header you delete disappears from the accessors the next time you run a `make:` command. Linters also treat these files as generated and skip them.
|
||||
- **Check the migration order.** A migration's file name and ID start with the second it was created in. Migrations with different names created in the same second get the same timestamp and run in file-name order, so `add_published_at` would run before `create_acme_blog_posts` and fail on the missing table. Look at `updates/` after scaffolding; if two files share a timestamp, rename the later one and its ID. The example uses fixed timestamps, `20260101000000` and `20260101000100`.
|
||||
- **The admin controller names the model after itself.** `make:admin-controller acme.blog Posts` returns `Posts` from `ModelName` and writes `modelClass: Posts`, and it puts `fields.yaml` and `columns.yaml` under `models/posts/`. Change `ModelName` and both `modelClass` values to the model, `Post`; the YAML can stay where it is, as in this example.
|
||||
- **The admin controller needs more than the scaffolder writes.** The generated controller implements only `pact.AdminController`. Add `NewRecord` (`pact.AdminRecordSource`) so the admin API has a model to query, and `RequiredPermissions` (`pact.AdminPermissioned`): without it, any signed-in administrator can open the controller. The model needs `Fillable` and `Rules` before the admin API can save it. The plugin needs `AdminFS` (`pact.AdminAssets`) to embed the YAML; without it the application refuses to start once the admin is enabled.
|
||||
- **A command that needs the application takes it as a parameter.** The function `make:command` writes takes no arguments, which is what the generated accessor looks for, so it cannot reach the database or the config. Give it the dependency as a parameter and return it from `Commands` yourself, as `blog:publish` does.
|
||||
|
||||
## Checklist
|
||||
|
||||
What changed on the way from WinterCMS to SummerCMS:
|
||||
|
||||
- `Plugin.php` became a `Plugin` type in `plugin.go`, registered from `init` with `party.Register`; each `register*` method became a capability interface such as `pact.HasPermissions` or `pact.HasNavigation`.
|
||||
- The plugin is a Go module the application imports; `summer plugin:add` and `summer build` replace dropping a directory into `plugins/`.
|
||||
- The Eloquent model became a GORM struct; `$fillable` and `$rules` became `Fillable` and `Rules` methods, and every mass assignment goes through `lagoon.Fill`.
|
||||
- `version.yaml` and the update scripts became timestamped gormigrate entries, each with a real `Rollback`.
|
||||
- `routes.php` became `Routes` on a `pact.Router`, with handlers that answer through a response type of their own and `lagoon.Paginate`.
|
||||
- The backend controller class became a `pact.AdminController` with a model, a permission and the same YAML; the generic admin API replaces the behaviours and their views.
|
||||
- The artisan command became a `bonfire.Command`, run with `./bin/acme blog:publish`.
|
||||
- Language strings and config defaults stay in YAML files embedded in the binary.
|
||||
|
||||
Reference in New Issue
Block a user