docs(quick-261028-pq8): fix the four internal/build scaffolder todos
This commit is contained in:
@@ -1059,6 +1059,7 @@ Recent decisions affecting current work:
|
||||
| 261022-wah | User plugin opt-in golem15.user.jwt.token_cookie (default off) sets the HttpOnly auth.cookie session cookie on /login, /register and /activate-by-code instead of the body token; a /refresh carried by the auth.cookie cookie rewrites it (sm-user-plugin 846087d, 8bdf2c7, ships v0.5.0; verified) | 2026-10-22 | 846087d, 8bdf2c7 (sm-user-plugin) | [261022-wah-opt-in-httponly-session-cookie-on-user-p](./quick/261022-wah-opt-in-httponly-session-cookie-on-user-p/) |
|
||||
| 261022-x7a | User plugin golem15.user.password.public_routes (default true): false leaves POST /login, /forgot-password and /reset-password unregistered so they 404 like unknown paths; /register and the rest unchanged (sm-user-plugin 3828a65, ships v0.5.0; verified) | 2026-10-22 | e5dd457 | [261022-x7a-user-plugin-golem15-user-password-public](./quick/261022-x7a-user-plugin-golem15-user-password-public/) |
|
||||
| 55 | fix(cabana): backend subject events reach pier as *pact.MCPSubjectChanged after commit | 2026-10-23 | 7c43820 | — |
|
||||
| 261028-pq8 | Fix the four internal/build scaffolder todos (make:plugin vendor path, make:admin-controller output, scaffold header and make:command deps, same-second migration order) | 2026-10-28 | 8bc18c9 | [261028-pq8-fix-the-four-internal-build-scaffolder-t](./quick/261028-pq8-fix-the-four-internal-build-scaffolder-t/) |
|
||||
|
||||
## Deferred Items
|
||||
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
---
|
||||
phase: quick-261028-pq8
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- internal/build/artifact.go
|
||||
- internal/build/scaffold.go
|
||||
- internal/build/leaf.go
|
||||
- internal/build/registry.go
|
||||
- internal/build/new.go
|
||||
- internal/build/stubs/artifacts.tmpl
|
||||
- internal/build/stubs/plugin.tmpl
|
||||
- internal/build/stubs/registry.tmpl
|
||||
- internal/build/stubs/new.tmpl
|
||||
- internal/build/build_test.go
|
||||
- internal/build/new_test.go
|
||||
- internal/build/new_golden_test.go
|
||||
- internal/build/testdata/new/*.golden
|
||||
- internal/build/testdata/new-no-plugin/*.golden
|
||||
- cmd/summer/main.go
|
||||
- cmd/summer/main_test.go
|
||||
- docs/console/scaffolding.md
|
||||
- docs/console/writing-commands.md
|
||||
- docs/setup/porting-a-plugin.md
|
||||
- docs/architecture/go-modules-and-workspaces.md
|
||||
- docs/plugins/registration.md
|
||||
- docs/plugins/testing.md
|
||||
- docs/backend/admin-controllers.md
|
||||
- docs/database/migrations.md
|
||||
- modules/cabana/README.md
|
||||
- docs/examples/blog/scaffold_layout_test.go
|
||||
- docs/examples/blog/plugin.go
|
||||
- docs/examples/blog/registry.gen.go
|
||||
- docs/examples/blog/controllers/posts.go
|
||||
- docs/examples/blog/controllers/posts/config_form.yaml
|
||||
- docs/examples/blog/controllers/posts/config_list.yaml
|
||||
- docs/examples/blog/models/post/fields.yaml
|
||||
- docs/examples/blog/models/post/columns.yaml
|
||||
- docs/examples/blog/models/post.go
|
||||
- docs/examples/blog/console/publish.go
|
||||
- docs/examples/blog/updates/20260101000000_create_acme_blog_posts.go
|
||||
- docs/examples/blog/updates/20260101000100_add_published_at.go
|
||||
autonomous: true
|
||||
requirements:
|
||||
- todo:2026-10-28-make-plugin-ignores-vendor-namespace
|
||||
- todo:scaffold-same-second-migration-order
|
||||
- todo:scaffold-admin-controller-incomplete
|
||||
- todo:scaffold-generated-header-and-command-deps
|
||||
|
||||
estimate:
|
||||
tokens: 150000
|
||||
raw_tokens: 150000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "summer make:plugin pixelpixel.servers creates plugins/pixelpixel/servers with module <app>/plugins/pixelpixel/servers, and acme.blog and pixelpixel.blog can both be scaffolded in one application"
|
||||
- "make:model/migration/command/job/admin-controller find a plugin through summer.yaml, then plugins/<vendor>/<name>, then the legacy plugins/<name>; an existing app with plugins/<name> keeps working unchanged (no breaking change)"
|
||||
- "summer new puts its starter plugin golem15.<name> at plugins/golem15/<name> and the generated app still builds"
|
||||
- "A new migration always gets a timestamp later than every migration already in updates/, so make:model then make:migration in the same second list CreateX before AddY in registry.gen.go"
|
||||
- "Files the developer edits (model, migration, command, job, admin controller) start with //summer:make <kind>; registry.gen.go keeps its Code generated header; files with either the new directive or the old header are listed in registry.gen.go"
|
||||
- "make:command writes func <Name>Command(app *backpack.App) bonfire.Command, the registry lists it and passes the app; a scaffolded plugin keeps the app from Boot and returns generatedCommands(p.app); an older plugin.go that calls generatedCommands() still compiles"
|
||||
- "make:admin-controller acme.blog Posts writes ModelName/modelClass Post and YAML under models/post/, returns &models.Post{} from NewRecord when that model exists, offers create and delete with showCheckboxes: true, and --model overrides the singular"
|
||||
- "A scaffolded plugin implements pact.AdminAssets through generatedAdminFS(), an empty tree until the first controller's YAML exists and the embedded YAML afterwards"
|
||||
- "Docs and CLI help describe the new behavior, include an optional-move upgrading note, and summer docs:build --check stays green"
|
||||
artifacts:
|
||||
- path: internal/build/artifact.go
|
||||
provides: "nextMigrationFile ordering, plugin lookup with vendor + legacy fallback, MakeAdminController model argument, singularize"
|
||||
- path: internal/build/registry.go
|
||||
provides: "scaffold-marker detection (new directive + legacy header), app-taking command scan, admin YAML embed patterns"
|
||||
- path: internal/build/stubs/registry.tmpl
|
||||
provides: "generatedCommands(app ...*backpack.App) and generatedAdminFS()"
|
||||
- path: internal/build/stubs/plugin.tmpl
|
||||
provides: "Plugin keeps app from Boot, Commands passes it, AdminFS wired"
|
||||
- path: internal/build/stubs/artifacts.tmpl
|
||||
provides: "//summer:make <kind> markers, app-taking command stub, model-named admin controller and YAML"
|
||||
- path: docs/console/scaffolding.md
|
||||
provides: "Updated scaffolder reference and upgrading note"
|
||||
key_links:
|
||||
- from: internal/build/stubs/plugin.tmpl
|
||||
to: internal/build/stubs/registry.tmpl
|
||||
via: "plugin.go calls generatedCommands(p.app) and generatedAdminFS(), which registry.gen.go defines"
|
||||
pattern: "generatedCommands\\(p\\.app\\)|generatedAdminFS\\(\\)"
|
||||
- from: docs/examples/blog/scaffold_layout_test.go
|
||||
to: internal/build
|
||||
via: "TestScaffoldLayout runs the make commands and compares the file set with docs/examples/blog"
|
||||
pattern: "plugins\", \"acme\", \"blog\""
|
||||
- from: internal/build/artifact.go
|
||||
to: internal/build/leaf.go
|
||||
via: "one shared default-plugin-dir helper used by locatePluginDir and checkModelsLeaf"
|
||||
pattern: "plugins/<vendor>/<name>"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Fix the four pending `internal/build` scaffolder todos:
|
||||
1. `make:plugin` drops the vendor.
|
||||
2. Migrations created in the same second run out of order.
|
||||
3. Every editable file carries a header saying it is generated and must not be edited, and `make:command` has no way to reach the application.
|
||||
4. `make:admin-controller` output does not boot as a working screen.
|
||||
|
||||
Purpose: `summer make:*` should produce plugins that work without the hand fixes `docs/setup/porting-a-plugin.md` now lists under "What the scaffolder leaves to you". Applications that already use `plugins/<name>`, the old header or zero-argument `generatedCommands()` must keep working, because the core plugins run in many apps.
|
||||
|
||||
Output: three tasks and four code+docs commits (Task 1 makes two commits because the project's CLAUDE.md asks for one logical change per commit). The four todo files are moved by the orchestrator, not by this plan.
|
||||
|
||||
Shape: tracer-first does not apply here. These are three independent fixes on a scaffolder path that already works. Each task is proven end to end by the scaffold tests, which run the make commands in a copy of examples/hello and then build the generated app.
|
||||
|
||||
Orchestrator defaults (fixed, do not re-ask):
|
||||
- OD-1: the starter plugin of `summer new` moves to `plugins/golem15/<name>`.
|
||||
- OD-2: the registry scanner keeps recognizing the old header.
|
||||
- OD-3: the docs get an upgrading note saying that moving `plugins/<name>` is optional.
|
||||
- OD-4: docs and help text change in the same commit as the code they describe, and `summer docs:build --check` stays green.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/msd-core/workflows/execute-plan.md
|
||||
@~/.claude/msd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@./CLAUDE.md
|
||||
@.planning/STATE.md
|
||||
@.planning/todos/pending/2026-10-28-make-plugin-ignores-vendor-namespace.md
|
||||
@.planning/todos/pending/scaffold-same-second-migration-order.md
|
||||
@.planning/todos/pending/scaffold-admin-controller-incomplete.md
|
||||
@.planning/todos/pending/scaffold-generated-header-and-command-deps.md
|
||||
@internal/build/scaffold.go
|
||||
@internal/build/artifact.go
|
||||
@internal/build/registry.go
|
||||
@internal/build/leaf.go
|
||||
@internal/build/stubs/artifacts.tmpl
|
||||
@internal/build/stubs/plugin.tmpl
|
||||
@internal/build/stubs/registry.tmpl
|
||||
|
||||
Facts read at planning time. Use these, not the line numbers in the todos:
|
||||
- The admin-controller stub ALREADY implements `NewRecord` (returns nil with a TODO) and `RequiredPermissions` with `<id>.access_<snake>` (commit 20a79c5). What is left from that todo: the model name, the YAML directory, a real `NewRecord` when the model exists, `AdminFS` and list `delete`.
|
||||
- `party.Activate` runs Register then Boot, and only after that does the generated `main` call each plugin's `Commands()` (internal/build/build.go). So `Commands()` can use an app saved in Boot.
|
||||
- `docs/examples/blog` is part of the root module. Its `TestScaffoldLayout` runs MakePlugin, MakeModel, MakeMigration, MakeAdminController("Posts") and MakeCommand on acme.blog in a copy of examples/hello, then compares the FILE SET (not the contents) with docs/examples/blog. Every go fence in the porting page must be a `src=` copy of docs/examples/blog (cmd/summer/phase11_1_acceptance_test.go). After editing the example's source, run `go run ./cmd/summer docs:sync`.
|
||||
- examples/hello keeps the old layout (`plugins/base`, `plugins/greeter`, `plugins/optional`). It is the fixture `copyHelloApp` copies, so it covers the legacy-path fallback. Do not move it.
|
||||
- Existing users of the old header and of zero-argument `generatedCommands()` outside this repo include sm-demo-app/plugins/golem15/quizzes and many shared plugins. sm-faq-plugin declares its own `adminFS` var. New registry identifiers must not collide with these, so use `generatedAdminTree` and `generatedAdminFS`; grep found no existing uses of either name.
|
||||
- cabana rules: `modelClass` must equal `ModelName()`. The `delete` toolbar button requires `showCheckboxes: true`. A plugin that has admin controllers but no non-nil `AdminFS()` stops start-up. A zero `embed.FS` is a valid, empty, non-nil fs.FS.
|
||||
- Golden files: `go test ./internal/build -run 'TestNewGolden' -update` rewrites them; `goldenName` maps "/" to "__".
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Migration order, then vendor-namespaced plugin directories (two commits)</name>
|
||||
<files>internal/build/artifact.go, internal/build/scaffold.go, internal/build/leaf.go, internal/build/new.go, internal/build/stubs/new.tmpl, internal/build/build_test.go, internal/build/new_test.go, internal/build/new_golden_test.go, internal/build/testdata/new/*.golden, internal/build/testdata/new-no-plugin/*.golden, cmd/summer/main.go, cmd/summer/main_test.go, docs/examples/blog/scaffold_layout_test.go, docs/console/scaffolding.md, docs/setup/porting-a-plugin.md, docs/architecture/go-modules-and-workspaces.md, docs/plugins/registration.md, docs/plugins/testing.md, docs/database/migrations.md</files>
|
||||
<behavior>
|
||||
- nextMigrationFile with a frozen migrationNow: two different slugs created in the same second get strictly increasing timestamps, so the second file name sorts after the first.
|
||||
- If updates/ already holds a migration whose timestamp is later than now (for example 20990101000000_x.go), the new one gets that timestamp plus one second. Files that do not start with 14 digits and an underscore (doc.go, *_test.go) are ignored.
|
||||
- TestScaffoldCoreArtifacts with a frozen clock: MakeModel Album, then MakeMigration AddWidgetIndex. registry.gen.go lists updates.CreateAlbums() before updates.AddWidgetIndex().
|
||||
- MakePlugin "golem15.demo" returns <app>/plugins/golem15/demo with module <app module>/plugins/golem15/demo. MakePlugin acme.blog and pixelpixel.blog in one hello copy give two directories, each of which builds.
|
||||
- The existing-directory error hint names plugin:add plugins/<vendor>/<name>.
|
||||
- A plugin moved to the legacy plugins/<name> and not listed in summer.yaml is still found by MakeModel (the fallback). Lookup tries plugins/<vendor>/<name> first.
|
||||
- summer new: the starter plugin is at plugins/golem15/demo; go.work, go.mod and summer.yaml point there; the app still builds (TestNewAppSmoke and the goldens).
|
||||
</behavior>
|
||||
<action>
|
||||
COMMIT 1a: migration order.
|
||||
- In internal/build/artifact.go nextMigrationFile, before choosing a timestamp, read the updates/ directory once.
|
||||
- Collect the 14-digit timestamp prefix of every .go file whose name matches 14 digits followed by an underscore.
|
||||
- Start the candidate at migrationNow(). If the candidate is not later than the newest existing timestamp, use the newest plus one second.
|
||||
- Keep the underRoot check, the exact-name existence guard and the MkdirAll.
|
||||
- Add the unit test TestNextMigrationFileSortsAfterExisting with the behavior cases. Freeze migrationNow and restore it in t.Cleanup; do not use t.Parallel.
|
||||
- In TestScaffoldCoreArtifacts, freeze the clock around the MakeModel/MakeMigration pair and assert the order of the registry lines (use the index of each string).
|
||||
- Docs in the same commit:
|
||||
- docs/console/scaffolding.md: replace the paragraph saying that migrations with different names in the same second share a timestamp. New text: each new migration gets a timestamp after every file already in updates/, so registry order is creation order.
|
||||
- docs/setup/porting-a-plugin.md: remove the "Check the migration order" bullet. If the fixed example timestamps 20260101000000/20260101000100 are explained elsewhere on the page, keep that explanation.
|
||||
- grep docs/ and cmd/summer/main.go for "same second" and fix any other mention, including docs/database/migrations.md if it has one.
|
||||
- Commit as fix(build): order scaffolded migrations after every existing one. Stage by explicit path.
|
||||
|
||||
COMMIT 1b: vendor path. Per OD-1 and the todo; nothing breaks for existing apps.
|
||||
- In internal/build/scaffold.go:
|
||||
- Add a helper that returns the vendor (the part before the dot) next to pluginName.
|
||||
- Add one helper that returns the default plugin directory candidates for an ID, in this order: plugins/<vendor>/<name>, then plugins/<name>.
|
||||
- makePlugin creates plugins/<vendor>/<name>. The module path is m.Module + "/plugins/" + vendor + "/" + name. The Go package name stays <name>.
|
||||
- The already-exists hint becomes plugin:add plugins/<vendor>/<name>, slash-separated.
|
||||
- Update the MakePlugin doc comment.
|
||||
- frameworkReplaceFor already computes the replace relative to pluginDir, so it needs no change. Check that the plugin go.mod of the generated starter has one more ../ in its relative replace.
|
||||
- In artifact.go locatePluginDir: keep the summer.yaml/replace lookup first, then try the candidates in order, applying underRoot to each. The first existing directory wins. The not-found error and hint stay as they are.
|
||||
- In leaf.go checkModelsLeaf: when localModuleDir is empty, use the same candidates (the first existing one). Do not duplicate the list.
|
||||
- In new.go:
|
||||
- addStarterPlugin registers filepath.Join("plugins", "golem15", name).
|
||||
- In ValidateAppName, the reserved-name messages name plugins/golem15/main and plugins/golem15/internal. Both reasons still hold: package main, and the internal path element.
|
||||
- In stubs/new.tmpl, change the CLAUDE.md lines to make:plugin golem15.<name> under plugins/golem15/<name> and plugin:add plugins/<vendor>/<name>, the starter path to plugins/golem15/{{.Name}}, and the next-steps plugin:add line.
|
||||
- Tests:
|
||||
- Update every plugins/demo pin in internal/build/build_test.go, internal/build/new_test.go, internal/build/new_golden_test.go and cmd/summer/main_test.go to plugins/golem15/demo. This covers wantDir, AddPlugin paths, the module suffix check in assertScaffoldFiles and the go.work strings.
|
||||
- In docs/examples/blog/scaffold_layout_test.go, the scaffolded directory becomes plugins/acme/blog.
|
||||
- Add TestMakePluginVendorDirs: acme.blog and pixelpixel.blog in one hello copy.
|
||||
- Add TestLocatePluginLegacyLayout: scaffold golem15.demo, os.Rename it to plugins/demo (copyHelloApp makes the framework replace absolute, so the move is safe), then MakeModel with --no-migration succeeds there.
|
||||
- Regenerate the goldens with go test ./internal/build -run TestNewGolden -update. git rm the old plugins__demo__*.golden files. Review the golden diff: only path changes are expected.
|
||||
- cmd/summer/main.go help text:
|
||||
- make:plugin Long says plugins/<vendor>/<plugin>.
|
||||
- Examples: "Create plugins/acme/blog with the ID acme.blog" and plugin:add plugins/acme/blog.
|
||||
- Every "Inside plugins/blog" and "from inside plugins/blog" becomes plugins/acme/blog.
|
||||
- Docs in the same commit:
|
||||
- docs/console/scaffolding.md: the starter-plugin table row, "Creating a plugin" and the cd example.
|
||||
- docs/architecture/go-modules-and-workspaces.md, around the plugins/<name> paragraph and the plugin:add example.
|
||||
- docs/plugins/registration.md: the directory sentence and the tree root.
|
||||
- docs/plugins/testing.md: go test ./plugins/acme/blog/...
|
||||
- docs/setup/porting-a-plugin.md: the plugin:add lines, the make:plugin and plugin:add table rows, and the module example.
|
||||
- grep docs/, README.md and modules/*/README.md for plugins/blog and plugins/<name>, and fix the remaining layout mentions. Manifest-only module strings in tag tests can stay.
|
||||
- Add the OD-3 upgrading note to docs/console/scaffolding.md under "Creating a plugin":
|
||||
- An existing plugins/<name> keeps working, because make:* finds both layouts and summer build follows summer.yaml.
|
||||
- Moving is optional. To move: move the directory, drop the old go.work use line (go work edit -dropuse), run summer plugin:add plugins/<vendor>/<name> (the module path and the summer.yaml entry stay), and add one ../ to a relative framework replace in the plugin go.mod.
|
||||
- Verify these exact steps in a scratch copy of examples/hello under the session scratchpad before writing them down. Correct the note to match what actually works.
|
||||
- Run go vet ./... and go test -short ./... before committing.
|
||||
- Commit as fix(build): scaffold plugins under plugins/<vendor>/<name>.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>go vet ./... && go test -short -count=1 ./internal/build/ ./cmd/summer/ ./docs/examples/blog/... && go test -count=1 ./cmd/summer -run TestDocsTree && go run ./cmd/summer docs:build --check</automated>
|
||||
</verify>
|
||||
<done>Two commits exist. Migration timestamps always sort after the existing ones, with a frozen-clock unit test and a registry-order check. make:plugin writes plugins/<vendor>/<name>. make:* and the models-leaf check find both layouts. The summer new starter is at plugins/golem15/<name> and the goldens are regenerated. Help text and docs, including the verified upgrading note, match. go vet, go test -short ./... and the docs checker are green.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: //summer:make marker for editable files and app-taking console commands (one commit)</name>
|
||||
<files>internal/build/registry.go, internal/build/artifact.go, internal/build/stubs/artifacts.tmpl, internal/build/stubs/plugin.tmpl, internal/build/stubs/registry.tmpl, internal/build/build_test.go, internal/build/testdata/new/*.golden, cmd/summer/main.go, docs/console/scaffolding.md, docs/console/writing-commands.md, docs/setup/porting-a-plugin.md, docs/examples/blog/plugin.go, docs/examples/blog/registry.gen.go, docs/examples/blog/models/post.go, docs/examples/blog/updates/20260101000000_create_acme_blog_posts.go, docs/examples/blog/updates/20260101000100_add_published_at.go, docs/examples/blog/controllers/posts.go, docs/examples/blog/console/publish.go</files>
|
||||
<behavior>
|
||||
- The model, migration, command, job and admin-controller stubs begin with the line //summer:make model / migration / command / job / admin-controller, followed by a blank line. registry.gen.go still begins with the Code generated by summer make header.
|
||||
- A console/ file carrying the old header with a zero-argument command function is still listed (OD-2). A console/ file with neither marker is not listed.
|
||||
- A command function taking exactly one *backpack.App parameter (named or unnamed) is listed. registry.gen.go calls it with the app passed to generatedCommands. A value backpack.App parameter, two parameters, or any other parameter type is skipped.
|
||||
- A freshly scaffolded plugin keeps the app from Boot, and Commands returns generatedCommands(p.app). make:command writes a function taking app *backpack.App. The plugin builds, and the built app runs the scaffolded command.
|
||||
- A plugin.go in the older form, with an empty Plugin struct and a Commands that returns generatedCommands() without arguments, still compiles after make:command. The make:command hint then explains that the app arrives as nil and how to pass it.
|
||||
</behavior>
|
||||
<action>
|
||||
Per the todo and OD-2. The aim is to stay backward compatible with every existing plugin.go.
|
||||
- In internal/build/registry.go:
|
||||
- Keep the legacy marker constant.
|
||||
- Add the directive constant //summer:make.
|
||||
- Replace isGeneratedFile with a scaffold-marker check: a comment whose text is the directive, or the directive followed by a space, OR any comment containing the legacy marker.
|
||||
- In funcRefs, for the command scan kind only, accept zero parameters or exactly one parameter field with at most one name whose type is a pointer (ast.StarExpr) to the selector backpack.App. astTypeName strips the star, so check for the StarExpr explicitly.
|
||||
- Record a TakesApp flag on registryRef.
|
||||
- Every other kind still accepts zero parameters only.
|
||||
- In stubs/registry.tmpl:
|
||||
- Always import modules/backpack.
|
||||
- Declare generatedCommands with a variadic app parameter of type *backpack.App. A variadic parameter keeps every existing plugin.go that calls generatedCommands() compiling; do not use a plain parameter.
|
||||
- Call zero-argument refs as they are called now.
|
||||
- Call TakesApp refs with the first variadic value, or nil when none was passed. Declare that local only when at least one TakesApp ref exists.
|
||||
- Keep the header line and the sort order unchanged.
|
||||
- In stubs/artifacts.tmpl:
|
||||
- Swap the first line of each of the five editable stubs for the matching //summer:make <kind> directive line.
|
||||
- The command stub imports modules/backpack, and the function takes app *backpack.App. Add a doc comment saying that app gives the command the config and published services (app.Lookup), and that it is the app the plugin kept from Boot. The Run body stays unchanged.
|
||||
- In stubs/plugin.tmpl:
|
||||
- The Plugin struct holds the app.
|
||||
- Boot stores it.
|
||||
- Commands returns generatedCommands(p.app), with a comment that Commands is collected after Boot.
|
||||
- Regenerate the summer new goldens with -update and review the diff.
|
||||
- In artifact.go MakeCommand: when the plugin's plugin.go contains a generatedCommands call with empty parentheses (a whitespace-tolerant regexp), append a note to the result Hint. The note says that <Func> receives a nil *backpack.App until Boot keeps the app and Commands returns generatedCommands(p.app).
|
||||
- Tests in internal/build/build_test.go cover each behavior line.
|
||||
- Run the built app binary's demo:purge after the App build in TestScaffoldAllArtifacts. If that binary cannot start without services the test environment lacks, write a throwaway _test.go into the temporary plugin instead. It calls Boot with an app and finds demo:purge in Commands(); run it with execGo go test.
|
||||
- Legacy plugin.go case: scaffold, rewrite plugin.go to the older shape, run MakeCommand, then go build ./... in the plugin and check the hint text.
|
||||
- docs/examples/blog:
|
||||
- Switch the five editable files' first line to their //summer:make <kind> directive.
|
||||
- Update registry.gen.go by hand to what the new template renders for this plugin: the backpack import and the variadic generatedCommands. It lists no commands, because PublishCommand takes withDB.
|
||||
- plugin.go Commands appends to generatedCommands(p.app).
|
||||
- Run go run ./cmd/summer docs:sync to refresh the src= copies.
|
||||
- cmd/summer/main.go make:command Long: replace "the generated function takes no arguments" with: the function receives the application the plugin kept from Boot, and a function with another parameter (such as the porting guide's withDB) is returned from Commands by hand.
|
||||
- Docs in the same commit:
|
||||
- docs/console/scaffolding.md: describe the directive line and the note that registry.gen.go lists only files that carry it or the older header; describe app-taking commands.
|
||||
- docs/console/writing-commands.md: the "Registering commands" paragraph and the make:command starting point. A command that needs the database or services takes app *backpack.App.
|
||||
- docs/setup/porting-a-plugin.md:
|
||||
- Rewrite the generated-code-header bullet in "What the scaffolder leaves to you" around the directive. Keep the line, or the file drops out of registry.gen.go; linters now lint these files.
|
||||
- In the console section, explain that the generated accessor lists commands taking *backpack.App. This example passes the narrower withDB, which its tests inject, so it is still added by hand.
|
||||
- Any new go fence must be a src= copy of docs/examples/blog.
|
||||
- Run go vet ./... and go test -short ./... before committing.
|
||||
- Commit as feat(build): mark scaffolded files with //summer:make and pass the app to generated commands.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>go vet ./... && go test -short -count=1 ./internal/build/ ./cmd/summer/ ./docs/examples/blog/... && go test -count=1 ./cmd/summer -run TestDocsTree && go run ./cmd/summer docs:build --check</automated>
|
||||
</verify>
|
||||
<done>One commit exists. Editable stubs carry //summer:make <kind>. registry.gen.go keeps the Code generated header. Both markers are recognized. App-taking commands are listed and receive the app. New plugins pass p.app. Older plugin.go files compile and get a hint. The docs, the blog example and the help text match, and the checker is green.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 3: make:admin-controller names the model, embeds its YAML and offers delete (one commit)</name>
|
||||
<files>internal/build/artifact.go, internal/build/registry.go, internal/build/stubs/artifacts.tmpl, internal/build/stubs/plugin.tmpl, internal/build/stubs/registry.tmpl, internal/build/build_test.go, internal/build/testdata/new/*.golden, cmd/summer/main.go, docs/examples/blog/scaffold_layout_test.go, docs/examples/blog/plugin.go, docs/examples/blog/registry.gen.go, docs/examples/blog/controllers/posts/config_form.yaml, docs/examples/blog/controllers/posts/config_list.yaml, docs/examples/blog/models/post/fields.yaml, docs/examples/blog/models/post/columns.yaml, docs/console/scaffolding.md, docs/setup/porting-a-plugin.md, docs/backend/admin-controllers.md, modules/cabana/README.md</files>
|
||||
<behavior>
|
||||
- singularize: Posts→Post, Albums→Album, BlogPosts→BlogPost, Categories→Category, Boxes→Box, Matches→Match, Wishes→Wish, Addresses→Address, Genres→Genre, Status→Status, Post→Post. For every word pluralize handles, singularize(pluralize(w)) == w.
|
||||
- MakeAdminController(ctx, dir, "golem15.demo", "Albums", "") writes controllers/albums.go (ID golem15.demo.albums, ConfigDir controllers/albums, ModelName "Album"), controllers/albums/config_form.yaml and config_list.yaml (modelClass: Album, form/list paths ~/plugins/golem15/demo/models/album/...), and models/album/fields.yaml and columns.yaml. cabana.CompileForm and CompileList pass strictly.
|
||||
- A non-empty model argument overrides the default ("People" with "Person" gives models/person/ and modelClass: Person). An invalid model identifier fails before anything is written.
|
||||
- config_list.yaml has showCheckboxes: true and buttons: [create, delete], and CompileList accepts it.
|
||||
- With models.Album already present (make:model first), NewRecord returns &models.Album{} and the plugin builds. Without it, NewRecord keeps today's nil body with its TODO naming make:model, and the result Hint says so.
|
||||
- A fresh make:plugin implements pact.AdminAssets: AdminFS returns generatedAdminFS(), and registry.gen.go has no go:embed line while no admin YAML exists. After make:admin-controller, registry.gen.go embeds controllers/*/*.yaml models/*/*.yaml, and both YAML files can be read through (&Plugin{}).AdminFS() in the built plugin.
|
||||
- make:admin-controller in a plugin whose root .go files declare no AdminFS method appends a hint naming generatedAdminFS().
|
||||
</behavior>
|
||||
<action>
|
||||
Per the todo; the remaining items only, since NewRecord and RequiredPermissions already exist.
|
||||
- In internal/build/artifact.go:
|
||||
- Add singularize next to pluralize, with rules that invert pluralize: ies to y; the es suffix after s, x, z, ch and sh is removed; words ending in ss, us or is stay unchanged; a trailing s is removed. Irregular forms are covered by --model.
|
||||
- Change the signature to MakeAdminController(ctx, startDir, pluginID, name, model string). An empty model means singularize(name). Validate it with validateExportedIdent and identHint.
|
||||
- Add ModelIdent and ModelSnake to artifactData.
|
||||
- The YAML directory is models/<model snake>/. The controller ID, ConfigDir, redirect URLs and Permission keep the controller snake.
|
||||
- The duplicate-file checks cover the new paths.
|
||||
- Add Module to pluginRef, set from the go.mod that resolvePlugin already reads.
|
||||
- Decide NewRecord by parsing models/*.go (all files, not only marked ones; skip _test.go) for an exported struct named ModelIdent. When it exists, render NewRecord returning a pointer to it and import <module>/models. When it does not, render the current nil body and add a Hint line: run summer make:model <id> <Model>, then return &models.<Model>{}.
|
||||
- If no non-test .go file in the plugin root declares a method named AdminFS, append a Hint line that names generatedAdminFS() and pact.AdminAssets.
|
||||
- In stubs/artifacts.tmpl:
|
||||
- ModelName returns ModelIdent, and the NewRecord branch is conditional.
|
||||
- config_form.yaml: name is the model, modelClass is the model, and form points at models/<model snake>/fields.yaml.
|
||||
- config_list.yaml: modelClass is the model, title is the controller, the list path points at models/<model snake>/columns.yaml, showCheckboxes: true is added, and the buttons are [create, delete].
|
||||
- In internal/build/registry.go:
|
||||
- refreshRegistry sets AdminPatterns: each of controllers/*/*.yaml and models/*/*.yaml that has at least one filepath.Glob match in the plugin dir, in that order.
|
||||
- emptyRegistry sets none.
|
||||
- In stubs/registry.tmpl:
|
||||
- Always import embed and io/fs.
|
||||
- Declare var generatedAdminTree embed.FS, with a go:embed line listing AdminPatterns only when there are any; a zero embed.FS is an empty, non-nil tree.
|
||||
- Add func generatedAdminFS() fs.FS returning it.
|
||||
- Note in a template comment that the go:embed globs are re-evaluated at compile time, and that the line is rewritten by the next make: run.
|
||||
- In stubs/plugin.tmpl, add the pact.AdminAssets assertion and an AdminFS method returning generatedAdminFS(). Regenerate the summer new goldens with -update.
|
||||
- Update every MakeAdminController call site to pass "" or a model: cmd/summer/main.go, internal/build/build_test.go and docs/examples/blog/scaffold_layout_test.go.
|
||||
- In cmd/summer/main.go, add a --model flag to make:admin-controller (a bonfire.Flag with a value, defaulting to the singular of the name) and update the Long text and Examples (Posts to Post; People --model Person).
|
||||
- Tests in internal/build/build_test.go:
|
||||
- Update TestMakeAdminControllerLayout: Albums uses model Album and models/album; the duplicate-asset subtest pre-creates models/genre/fields.yaml; scaffoldAlbumsController.ModelName returns "Album"; buttons are [create, delete] with showCheckboxes.
|
||||
- Add a table test for singularize.
|
||||
- Add the --model override case.
|
||||
- Add the NewRecord-with-model case, with go build ./... in the plugin.
|
||||
- Add an AdminFS end-to-end case: write a throwaway _test.go into the temporary plugin that reads both YAML files through (&Plugin{}).AdminFS(), then run go test there with execGo.
|
||||
- assertWinterPluginShape must still pass for a fresh plugin, because no new files are written.
|
||||
- docs/examples/blog:
|
||||
- git mv models/posts to models/post.
|
||||
- Point config_form.yaml and config_list.yaml at ~/plugins/acme/blog/models/post/.
|
||||
- plugin.go AdminFS returns generatedAdminFS(); drop the hand-written adminFS embed var and update its comment.
|
||||
- registry.gen.go gets the generatedAdminTree embed of both patterns plus generatedAdminFS.
|
||||
- grep docs/examples/blog for models/posts and fix the remaining references, including the tests.
|
||||
- Run go run ./cmd/summer docs:sync.
|
||||
- Docs in the same commit:
|
||||
- docs/console/scaffolding.md: the make:admin-controller row (models/<model>/, --model, NewRecord, delete) and the make:plugin AdminFS.
|
||||
- docs/setup/porting-a-plugin.md:
|
||||
- The make:admin-controller table row becomes models/post/.
|
||||
- The sentence saying that fields sit under models/posts/ becomes models/post/, as in WinterCMS.
|
||||
- Point the src= paths at models/post/.
|
||||
- Replace the two admin bullets of "What the scaffolder leaves to you" with what is still manual: declare the permission in Permissions(), give the model Fillable and Rules, and fill fields.yaml and columns.yaml.
|
||||
- Update the sentence that says AdminFS was added by hand.
|
||||
- docs/backend/admin-controllers.md: the make:admin-controller paragraph.
|
||||
- modules/cabana/README.md: the scaffold mention.
|
||||
- Run go vet ./..., go test -short ./... and go -C examples/hello test ./... before committing.
|
||||
- Commit as feat(build): scaffold working admin controllers with model, AdminFS and delete.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>go vet ./... && go test -short -count=1 ./... && go -C examples/hello test ./... && go test -count=1 ./cmd/summer -run TestDocsTree && go run ./cmd/summer docs:build --check</automated>
|
||||
</verify>
|
||||
<done>One commit exists. make:admin-controller writes model-named YAML and controller, a real NewRecord when the model exists, and create/delete with showCheckboxes. --model overrides the singular. Scaffolded plugins serve AdminFS from registry.gen.go, empty until the first controller. The docs example, the porting page, the admin docs, the cabana README and the help text match. Full go test -short, examples/hello and the docs checker are green.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| CLI argument → filesystem | The plugin ID, artifact name and --model value from the developer's command line become directory and file paths |
|
||||
| scaffolded source → registry generator | Files in a plugin's leaf directories are parsed and decide what registry.gen.go calls and embeds |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-q261028-01 | Tampering | makePlugin / locatePluginDir vendor segment | medium | mitigate | The vendor comes from an ID that ValidatePluginID has already checked (lowercase vendor.plugin, no slashes). Every candidate dir (vendor and legacy) still goes through underRoot before use; TestMakePluginRejectsPathTraversalAndShellMeta stays green |
|
||||
| T-q261028-02 | Tampering | MakeAdminController --model | low | mitigate | The model goes through validateExportedIdent before any path is built, and rejectDuplicateFile/underRoot cover models/<model snake>/ |
|
||||
| T-q261028-03 | Information disclosure | generatedAdminTree go:embed | low | mitigate | The patterns are limited to controllers/*/*.yaml and models/*/*.yaml, so Go sources are never embedded in the binary (the same choice sm-faq-plugin made) |
|
||||
| T-q261028-04 | Denial of service | app-taking commands under legacy wiring | low | mitigate | The variadic generatedCommands passes nil only when plugin.go omits the app; make:command prints a hint naming the fix, and the default stub does not dereference app |
|
||||
| T-q261028-SC | Tampering | npm/pip/cargo installs | low | accept | No new dependencies; only stdlib embed/io/fs and the existing modules/backpack |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- go vet ./... and go test -short ./... are green after each of the four commits (the CLAUDE.md rule).
|
||||
- go -C examples/hello test ./... is green; examples/hello is not modified.
|
||||
- go test ./cmd/summer -run TestDocsTree and go run ./cmd/summer docs:build --check are green after each commit.
|
||||
- TestScaffoldLayout (docs/examples/blog) passes: the walkthrough file set equals the scaffolder output under plugins/acme/blog with models/post/.
|
||||
- git log shows four commits without co-author tags, each staging only its own paths; other sessions' STATE.md, state.json and phase-20 files are left alone.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All four todos' problems are fixed, as the must_haves truths describe.
|
||||
- Nothing breaks for existing apps: plugins/<name>, the old header and zero-argument generatedCommands() calls keep working.
|
||||
- Docs, help text and the porting walkthrough describe the new behavior and include the upgrading note.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/quick/261028-pq8-fix-the-four-internal-build-scaffolder-t/261028-pq8-SUMMARY.md` when done
|
||||
</output>
|
||||
@@ -0,0 +1,169 @@
|
||||
---
|
||||
phase: quick-261028-pq8
|
||||
plan: 01
|
||||
subsystem: internal/build (scaffolder)
|
||||
status: complete
|
||||
tags: [scaffolder, make-plugin, migrations, admin-controller, registry, docs]
|
||||
requires: []
|
||||
provides:
|
||||
- migration timestamps after every existing updates/ file
|
||||
- plugins/<vendor>/<name> layout with legacy plugins/<name> fallback
|
||||
- //summer:make <kind> marker; app-taking console commands
|
||||
- make:admin-controller with model name, real NewRecord, AdminFS and delete
|
||||
affects: [cmd/summer, docs/console, docs/setup, docs/backend, docs/plugins, docs/architecture, docs/examples/blog, modules/cabana/README.md]
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "variadic generatedCommands(app ...*backpack.App) keeps old zero-arg callers compiling"
|
||||
- "registry.gen.go embeds admin YAML only for globs that match (go:embed rejects empty patterns)"
|
||||
key-files:
|
||||
created:
|
||||
- internal/build/testdata/new/plugins__golem15__demo__plugin.go.golden (renamed from plugins__demo__)
|
||||
- internal/build/testdata/new/plugins__golem15__demo__routes.go.golden (renamed from plugins__demo__)
|
||||
- docs/examples/blog/models/post/ (moved from models/posts/)
|
||||
modified:
|
||||
- internal/build/artifact.go
|
||||
- internal/build/scaffold.go
|
||||
- internal/build/leaf.go
|
||||
- internal/build/new.go
|
||||
- internal/build/registry.go
|
||||
- internal/build/stubs/artifacts.tmpl
|
||||
- internal/build/stubs/plugin.tmpl
|
||||
- internal/build/stubs/registry.tmpl
|
||||
- internal/build/stubs/new.tmpl
|
||||
- internal/build/build_test.go
|
||||
- cmd/summer/main.go
|
||||
- docs/console/scaffolding.md
|
||||
- docs/setup/porting-a-plugin.md
|
||||
decisions:
|
||||
- "make:plugin writes plugins/<vendor>/<name>; make:* looks up summer.yaml, then plugins/<vendor>/<name>, then legacy plugins/<name> (OD-1)"
|
||||
- "Registry scan recognises both //summer:make and the old Code generated header (OD-2)"
|
||||
- "Moving an existing plugins/<name> is optional; documented, verified upgrading steps (OD-3)"
|
||||
- "New registry identifiers are generatedAdminTree / generatedAdminFS / bootApp to avoid collisions with handwritten adminFS vars"
|
||||
metrics:
|
||||
duration: ~75min
|
||||
completed: 2026-10-28
|
||||
estimate_ref: "plan estimate 150000 tokens, 3 tasks"
|
||||
actuals:
|
||||
tokens: 35400
|
||||
tasks: 3
|
||||
commits: 4
|
||||
commits: 4
|
||||
plan_head_before: 017e0e9
|
||||
plan_head_after: 8bc18c9
|
||||
---
|
||||
|
||||
# Quick 261028-pq8: Fix the four internal/build scaffolder todos Summary
|
||||
|
||||
`summer make:*` now writes vendor-namespaced plugins (`plugins/acme/blog`), orders migrations correctly within one second, marks editable files with `//summer:make <kind>` and passes the `*backpack.App` to generated commands, and produces admin controllers that boot: model-named YAML under `models/<model>/`, a real `NewRecord`, `AdminFS` from `registry.gen.go`, and create and delete buttons. Existing apps keep working: `plugins/<name>`, the old header and zero-argument `generatedCommands()` all still work.
|
||||
|
||||
## Commits
|
||||
|
||||
| Task | Commit | Message |
|
||||
|------|--------|---------|
|
||||
| 1a | a941ab2 | fix(build): order scaffolded migrations after every existing one |
|
||||
| 1b | a95a014 | fix(build): scaffold plugins under plugins/<vendor>/<name> |
|
||||
| 2 | 70e4b41 | feat(build): mark scaffolded files with //summer:make and pass the app to generated commands |
|
||||
| 3 | 8bc18c9 | feat(build): scaffold working admin controllers with model, AdminFS and delete |
|
||||
|
||||
## What was built
|
||||
|
||||
**1a: Migration order.** `nextMigrationFile` reads `updates/` once, takes the newest `\d{14}_*.go` timestamp, and starts one second after it when now is not later. Unit test `TestNextMigrationFileSortsAfterExisting` (frozen clock; same second, a future-dated existing file, non-migration files ignored, older existing). `TestScaffoldCoreArtifacts` freezes the clock and asserts `CreateAlbums` before `AddWidgetIndex` in `registry.gen.go`. The docs no longer mention the same-second caveat or the manual order check.
|
||||
|
||||
**1b: Vendor path.**
|
||||
- `makePlugin` writes `plugins/<vendor>/<name>` with module `<app>/plugins/<vendor>/<name>`. The package stays `<name>`.
|
||||
- `pluginDirCandidates` and `defaultPluginDir` in `scaffold.go` hold the shared lookup order (vendor dir, then legacy), used by both `locatePluginDir` and `checkModelsLeaf`.
|
||||
- `summer new` puts the starter plugin at `plugins/golem15/<name>`. The plugin's framework replace gets one more `../` (`../../../../summercms`), and the goldens were regenerated (paths only).
|
||||
- Updated: help text, the `new.tmpl` CLAUDE.md and next steps, and the docs. `scaffolding.md` gained an upgrading note ("Upgrading: plugins without a vendor directory").
|
||||
- New tests: `TestMakePluginVendorDirs` (acme.blog and pixelpixel.blog side by side, both build, the hint names `plugin:add plugins/acme/blog`) and `TestLocatePluginLegacyLayout`.
|
||||
|
||||
**2: Marker and app-taking commands.**
|
||||
- Each editable stub starts with `//summer:make model|migration|command|job|admin-controller`. `registry.gen.go` keeps `// Code generated by summer make. DO NOT EDIT.`
|
||||
- `isScaffoldedFile` accepts the directive (alone, or followed by a space) and the old header.
|
||||
- The command scan also accepts exactly one `*backpack.App` parameter, named or unnamed, and records it as `TakesApp`.
|
||||
- `generatedCommands(app ...*backpack.App)` calls `TakesApp` refs with `bootApp`. That local exists only when at least one such ref does.
|
||||
- The plugin stub keeps the app from `Boot` and returns `generatedCommands(p.app)`.
|
||||
- `MakeCommand` adds a nil-app note when `plugin.go` matches `generatedCommands\(\s*\)`.
|
||||
- Tests:
|
||||
- `TestRegistryScanMarkersAndAppCommands`
|
||||
- `TestMakeCommandLegacyPluginGo`: older plugin.go plus make:command, then `go build` and `go vet`, and the hint is checked.
|
||||
- `TestScaffoldAllArtifacts` checks the markers, patches `purge.go` to print `app=%t`, and runs `bin/hello demo:purge` on the built app, which must print `app=true`.
|
||||
|
||||
**3: Admin controller.**
|
||||
- `singularize` sits next to `pluralize`. `MakeAdminController(ctx, dir, id, name, model)` takes the new model argument, and the CLI has a `--model` flag.
|
||||
- `ModelName`, `name:` and `modelClass:` use the model, and the YAML goes under `models/<model snake>/`. The controller ID, ConfigDir, redirects and permission keep the controller snake case.
|
||||
- `NewRecord` returns `&models.<Model>{}` when `models/*.go` (package models, non-test) declares the struct. Otherwise the nil body plus TODO stays, and a hint names `make:model`.
|
||||
- `config_list.yaml` has `showCheckboxes: true` and `buttons: [create, delete]`.
|
||||
- `registry.gen.go` declares `var generatedAdminTree embed.FS`. It only gets a `//go:embed controllers/*/*.yaml models/*/*.yaml` line for globs that match a file, and `generatedAdminFS()` returns the tree.
|
||||
- The plugin stub asserts `pact.AdminAssets` and returns `generatedAdminFS()` from `AdminFS`. Plugins with no `AdminFS` method get a hint.
|
||||
- The blog example moved to `models/post/`, and its hand-written `adminFS` embed var was dropped.
|
||||
- Tests:
|
||||
- `TestSingularize`: the table plus the round trip.
|
||||
- `TestMakeAdminControllerModel`: `--model`, an invalid model writes nothing, the AdminFS hint on the greeter plugin.
|
||||
- `TestScaffoldAdminFSEndToEnd`: a probe `_test.go` runs with `go test` inside the plugin. It sees an empty tree first, then reads all four YAML files, and checks that no Go source is embedded.
|
||||
- Plus a CLI `--model` check and updated layout and strict-compile tests.
|
||||
|
||||
## Verification
|
||||
|
||||
- `go vet ./...` passed and `go test -short ./...` was green, both after each task.
|
||||
- `go -C examples/hello test ./...` was green, and examples/hello is unchanged.
|
||||
- `go test ./cmd/summer -run TestDocsTree` and `go run ./cmd/summer docs:build --check` passed: no problems found.
|
||||
- The non-short `go test ./internal/build -run TestNew` was green (TestNewAppSmoke, TestNewNoPluginBuilds).
|
||||
- I checked the upgrading note by hand on a copy of examples/hello under the scratchpad:
|
||||
- Set-up: relative framework replaces, go.work from `plugin:add`.
|
||||
- Moved `plugins/greeter` to `plugins/golem15/greeter`, ran `go work edit -dropuse ./plugins/greeter`, then `summer plugin:add plugins/golem15/greeter`.
|
||||
- The build failed until the plugin `go.mod` replace got one more `../`. After that, `summer build` worked, `bin/hello greeter:hello` ran, and `make:model` and `make:job` (the latter from inside the moved directory) found the plugin.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 2 - Missing guard] make:plugin refuses a nested or duplicate plugin**
|
||||
- **Found during:** Task 1b
|
||||
- **Issue:** With the vendor layout, an ID whose vendor matches a legacy `plugins/<vendor>` plugin module (for example `greeter.extra` in examples/hello) would nest a module inside another plugin. Separately, a legacy `plugins/<name>` that already holds the same ID would get a second copy.
|
||||
- **Fix:** `makePlugin` errors with a hint in both cases. Both are covered in `TestMakePluginVendorDirs` and `TestLocatePluginLegacyLayout`.
|
||||
- **Commit:** a95a014
|
||||
|
||||
**2. [Plan detail] singularize rules refined**
|
||||
- The plan's literal rule ("es after s removed") would turn Courses into Cours, Houses into Hous and Responses into Respons.
|
||||
- The rules now drop `es` only after `ss`, `us` (but not `ous`/`aus`), `sis`, `x`, `ch`, `sh` and `zz`. `quizzes` gives `quiz`, and otherwise a trailing `s` is dropped.
|
||||
- All plan cases pass. The round trip `singularize(pluralize(w)) == w` holds for the tested word list. It fails for words like `gas`, where pluralize gives `gases` and that singularizes to `gase`; `--model` covers those.
|
||||
- **Commit:** 8bc18c9
|
||||
|
||||
**3. [Docs] Fixed example timestamps kept**
|
||||
- The fixed example timestamps (20260101000000/20260101000100) were explained only in the bullet the plan removed.
|
||||
- I moved that explanation into the porting page's migration paragraph.
|
||||
- **Commit:** a941ab2
|
||||
|
||||
**4. [Docs] Extra layout mentions updated**
|
||||
- The summer.yaml examples in `docs/architecture/go-modules-and-workspaces.md` and `docs/services/storage.md` now use `.../plugins/acme/blog`, to match what make:plugin writes.
|
||||
- `docs/plugins/registration.md` gained the admin YAML embed in the tree.
|
||||
- **Commits:** a95a014, 8bc18c9
|
||||
|
||||
**5. [Docs] Upgrading note step added**
|
||||
- The verified steps include `mkdir -p plugins/<vendor>` before the `mv`.
|
||||
- The note also says that other relative replaces in the plugin `go.mod` need the extra `../` too.
|
||||
- **Commit:** a95a014
|
||||
|
||||
### Out of scope (not fixed)
|
||||
- `cmd/summer/main.go`, `cmd/summer/runtime.go` and `cmd/summer/runtime_test.go` were already not gofmt-clean at HEAD 017e0e9. I left them alone.
|
||||
- `internal/build/registry.go`'s const-block alignment got fixed in passing, because Task 2 rewrote that block.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None in this plan's code. The scaffolded admin controller deliberately keeps `NewRecord() any { return nil }` with a TODO when the model does not exist yet. That is intended scaffold output, and the command prints a hint about it.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None. The threat-model mitigations are in place:
|
||||
- **T-01:** Vendor and legacy candidates both go through `underRoot`, and the existing traversal tests stay green.
|
||||
- **T-02:** `--model` is validated by `validateExportedIdent` before any path is built.
|
||||
- **T-03:** The embed patterns are limited to `*.yaml` under `controllers/*/` and `models/*/`; the probe checks that no Go source is embedded.
|
||||
- **T-04:** The variadic `generatedCommands` keeps existing callers compiling, and the stub never dereferences `app`.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- FOUND: internal/build/testdata/new/plugins__golem15__demo__plugin.go.golden
|
||||
- FOUND: internal/build/testdata/new/plugins__golem15__demo__routes.go.golden
|
||||
- FOUND: docs/examples/blog/models/post/fields.yaml, docs/examples/blog/models/post/columns.yaml
|
||||
- FOUND commits: a941ab2, a95a014, 70e4b41, 8bc18c9 (none carry a co-author line)
|
||||
Reference in New Issue
Block a user