Files
summercms/.planning/phases/04-cli-scaffolding-i18n-and-mail/04-01-PLAN.md
2026-09-18 13:24:54 +02:00

14 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
04-cli-scaffolding-i18n-and-mail 01 execute 1
cmd/summer/main.go
cmd/summer/main_test.go
internal/build/scaffold.go
internal/build/registry.go
internal/build/leaf.go
internal/build/build.go
internal/build/build_test.go
internal/build/stubs/plugin.tmpl
internal/build/stubs/artifacts.tmpl
internal/build/stubs/registry.tmpl
pact/capabilities.go
true
CLI-02
truths artifacts key_links
D-10: A generated plugin has root plugin.go and routes.go, Go leaf packages models/, classes/, controllers/, console/, jobs/, middleware/, updates/, and embedded lang/, views/mail/, config/ assets in the WinterCMS directory shape.
D-11: summer build rejects a models/ import of any sibling package in the same plugin, naming the plugin ID, source file, and offending import.
D-12: Each make command updates sorted registry.gen.go slices for migrations, commands, jobs, admin controllers, and models without rewriting hand-written plugin.go; an existing plugin receives a one-time accessor instruction.
D-13: make:model creates a GORM model with vendor_plugin_names table, timestamps, and a gormigrate create-table migration; --no-migration suppresses only that migration; make:migration appends a separate migration.
D-14: make:admin-controller produces a pact.AdminController with ID, model name, config directory, and Winter-shaped controllers/<name>/fields.yaml and columns.yaml.
D-15: make:job produces args with Kind() and a job with Work(context.Context, args) error behind pact.Job, without a River import.
D-16: All Go stubs render from embedded text/template files and pass go/format; a temporary hello plugin with all six artifact types passes go build and go vet.
D-17: make:<kind> accepts vendor.plugin and Name, or infers the plugin ID from plugin.go when invoked inside its directory.
path provides
internal/build/registry.go deterministic per-plugin registry generation
path provides
internal/build/leaf.go models sibling-import enforcement
path provides
internal/build/stubs/plugin.tmpl named Winter-shaped plugin root, routes, leaf, and go.mod templates
path provides
internal/build/stubs/artifacts.tmpl separately named model, migration, command, job, admin-controller, and YAML templates
path provides
internal/build/stubs/registry.tmpl separately named generated-registry template
path provides
cmd/summer/main.go six make commands
path provides
pact/capabilities.go job, admin, language, and mail capability contracts
from to via
cmd/summer/main.go internal/build/scaffold.go make commands call the shared scaffold entry point
from to via
internal/build/scaffold.go internal/build/registry.go successful artifact creation refreshes registry.gen.go
from to via
internal/build/build.go internal/build/leaf.go pre-build check before go build subprocess

Phase Goal

As a plugin developer, I want to generate compiling plugin artifacts, resolve translated strings, and send registered mail, so that I can port WinterCMS plugins into one SummerCMS binary.

Generate every Phase 4 plugin artifact through the summer CLI and compile the resulting plugin.

Purpose: A porting agent can move each WinterCMS artifact into its corresponding Go package without hand-maintaining a plugin registry. Output: six make commands, embedded stub templates, generated registry, and a build-time models leaf check.

<execution_context> @/home/jin/.codex/get-shit-done/workflows/execute-plan.md @/home/jin/.codex/get-shit-done/templates/summary.md </execution_context>

@CLAUDE.md @.planning/ROADMAP.md @.planning/REQUIREMENTS.md @.planning/phases/04-cli-scaffolding-i18n-and-mail/04-CONTEXT.md @.planning/phases/04-cli-scaffolding-i18n-and-mail/04-RESEARCH.md @.planning/phases/04-cli-scaffolding-i18n-and-mail/04-PATTERNS.md @.planning/notes/plugin-layout-winter-directories.md @.planning/phases/01-framework-kernel-foundation/01-CONTEXT.md @.planning/phases/03-first-vertical-slice-genres-end-to-end/03-CONTEXT.md Existing: build.MakePlugin(ctx, startDir, id) (string, error); build.AddPlugin(ctx, startDir, pluginDir) error; build.App(ctx, appDir, out) error. pluginIDFromGo(path) reads a literal Plugin.ID return. pact.HasMigrations.Migrations() returns []*gormigrate.Migration; pact.HasCommands.Commands() returns []bonfire.Command; pact.HasModels.Models() returns []any. A scaffolded plugin implements party.Plugin with ID, Requires, Register, Boot. The app manifest lists plugin ID and module path. Keep the current plugin:add and go.work behavior.

New contract to define before generated consumers: pact.JobArgs has Kind() string; pact.Job has Work(context.Context, pact.JobArgs) error; pact.HasJobs returns []pact.Job; pact.AdminController has ID() string, ModelName() string, ConfigDir() string; pact.HasAdminControllers returns []pact.AdminController. Add pact.HasLang.LangFS() fs.FS and pact.HasMailTemplates with MailTemplatesFS() fs.FS, MailTemplates() []string, MailLayouts() map[string]string for Plans 02 and 03. Generated root plugin.go calls generatedModels, generatedMigrations, generatedCommands, generatedJobs, and generatedAdminControllers from registry.gen.go; existing plugins opt in by appending those same accessors.

Task 1: Prove and create the compiling Winter-shaped plugin path internal/build/build_test.go, internal/build/scaffold.go, internal/build/registry.go, internal/build/stubs/plugin.tmpl, internal/build/stubs/registry.tmpl, pact/capabilities.go internal/build/build_test.go; internal/build/scaffold.go; internal/build/build.go; internal/build/manifest.go; pact/capabilities.go; examples/hello/plugins/base/plugin.go; examples/hello/go.mod; .planning/notes/plugin-layout-winter-directories.md; 04-CONTEXT.md D-10, D-12, D-16 Start with a failing end-to-end TestScaffoldPluginSmoke in internal/build/build_test.go that copies examples/hello, creates and adds a plugin, then builds it. Per D-16, migrate make:plugin's source and go.mod string builders to embedded text/template under internal/build/stubs: plugin.tmpl defines separately executable named templates for plugin.go, routes.go, each leaf doc.go, and go.mod; registry.tmpl defines the registry.go template. Parse embedded files once and execute each named definition, formatting rendered Go before writes. Per D-10, create root routes.go and a doc.go in each leaf package, plus nonhidden embed-compatible lang and mail assets. Per D-12, generate deterministic registry.gen.go with empty accessors. Define the pact contracts in the interfaces block so generated plugin.go compiles and its capability methods can return registry slices. Preserve party.Register, the current go.mod/toolchain/replacement flow, and hand-written plugin.go ownership. Record the initial red test result, then make it green before committing so every commit keeps root go vet and go test ./... green. go test ./internal/build -run TestScaffoldPluginSmoke -short -count=1 && go vet ./... && go test ./... A new plugin has the Winter directory shape, working embedded assets and registry.gen.go; build succeeds before any individual artifact is added. TestScaffoldPluginSmoke passes for plugin creation and existing scaffold behavior remains green. Task 2: Generate model, migration, and command slices internal/build/scaffold.go, internal/build/registry.go, internal/build/stubs/artifacts.tmpl, cmd/summer/main.go, cmd/summer/main_test.go, internal/build/build_test.go internal/build/scaffold.go; internal/build/registry.go; internal/build/stubs/plugin.tmpl; internal/build/stubs/registry.tmpl; cmd/summer/main.go; cmd/summer/main_test.go; internal/build/build_test.go; pact/capabilities.go; lagoon/migrations.go; examples/hello/plugins/greeter/plugin.go; 04-CONTEXT.md D-12, D-13, D-17 Add make:model, make:migration, and make:command to bonfire with the D-17 argument forms and pluginIDFromGo inference. Validate identifier, path containment, duplicate artifact, and module ownership before writes. In artifacts.tmpl, define separately executable named templates for model.go, migration.go, and command.go. Per D-13, render model table vendor_plugin_names with timestamps; emit an explicit gormigrate Up/Down create-table migration by default, with --no-migration omitting only that file. A standalone make:migration creates an ordered gormigrate entry; make:command creates a console/ bonfire.Command with plugin-prefixed colon name. Regenerate sorted slices and imports atomically, preserving handwritten plugin.go per D-12. Add TestScaffoldCoreArtifacts to build/vet these artifacts and check repeated generation leaves registry bytes unchanged. go test ./internal/build ./cmd/summer -run 'TestScaffoldCoreArtifacts|TestToolCommandNames' -short -count=1 && go vet ./... && go test ./... The three commands create compiling artifacts and expose them through the generated registry; --no-migration omits only the model migration. A temporary generated plugin builds and vets after model, migration, and command creation. Task 3: Generate jobs and admin controllers, then enforce model imports internal/build/scaffold.go, internal/build/registry.go, internal/build/leaf.go, internal/build/build.go, internal/build/stubs/artifacts.tmpl, cmd/summer/main.go, cmd/summer/main_test.go, internal/build/build_test.go internal/build/scaffold.go; internal/build/registry.go; internal/build/stubs/artifacts.tmpl; internal/build/build.go; internal/build/manifest.go; cmd/summer/main.go; cmd/summer/main_test.go; internal/build/build_test.go; pact/capabilities.go; .planning/notes/plugin-layout-winter-directories.md; 04-CONTEXT.md D-11, D-14, D-15 Add make:job and make:admin-controller with the same validated argument resolution. In artifacts.tmpl, add separately executable named definitions for job.go, admin_controller.go, fields.yaml, and columns.yaml. Per D-15, job args implement pact.JobArgs.Kind and the job implements pact.Job.Work, returning an error for an unexpected args type; no River import. Per D-14, admin controller implements the pact interface and has controllers//fields.yaml and columns.yaml in the Winter shape. Extend registry slices for both. Per D-11, in build.App inspect Go imports in each manifest plugin's models/ package before invoking go build; reject an import of its own classes/, controllers/, console/, jobs/, middleware/, or updates/ package with plugin ID, source file and import path. Keep the check limited to this rule, including manually written models. Complete the smoke case for all six artifact types and explicit leaf rejection. go test ./internal/build ./cmd/summer -run 'TestScaffoldAllArtifacts|TestModelsLeaf|TestToolCommandNames' -short -count=1 && go vet ./... && go test ./... All six make commands generate buildable, vet-clean output; a sibling import in models fails before go build with the required diagnostic. The full scaffold smoke case passes, registry output is deterministic, and handwritten plugin.go remains byte-identical.

<threat_model>

Trust Boundaries

Boundary Description
CLI input to filesystem Plugin IDs and artifact names choose generated paths and source identifiers.
Plugin source to build subprocess User-written imports enter the compiled plugin dependency graph.

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-04-01 Tampering make:* path/source generation mitigate Validate Go identifiers, module path and underRoot containment before writing; reject duplicates; format before atomic registry rename.
T-04-02 Tampering models/ imports mitigate Parse imports before build and fail with plugin ID, file, and sibling import; test an injected violation.
T-04-03 Denial of service repeated make:* mitigate Refuse duplicate artifact names and keep registry generation deterministic/idempotent.
T-04-SC Tampering Go module resolution mitigate Use only research-named existing dependencies for this plan; no npm, pip, or cargo install.
</threat_model>
After every task, run its focused smoke plus root go vet ./... and go test ./.... In a temporary copy of examples/hello, run go build and go vet after generating every artifact, including --no-migration and repeated make commands. Inspect a deliberately forbidden models/ sibling import diagnostic.

<success_criteria> CLI-02 is observable through six commands; each generated artifact compiles and vets; registry.gen.go is stable and auto-wired; a models/ sibling import fails with an actionable message. No file outside summercms.go is edited. </success_criteria>

Create .planning/phases/04-cli-scaffolding-i18n-and-mail/04-01-SUMMARY.md when done.