Files
summercms/docs/console/scaffolding.md
Jakub Zych 33460607db feat(summer): help, examples and hints for every tool command
- build runs go build under a spinner on a terminal and ends with
  'Built bin/<name> in <time>'; make:* print 'Created <path>' per file
- make:*, plugin:add, new, parity:*, docs:build, docs:sync and every
  delegate get long help and examples; delegates note that global options
  pass through to the application binary
- make:* failures (no summer.yaml, invalid plugin ID or name, existing
  artifact, unknown plugin) explain themselves and suggest a command
- docs:build and docs:sync return bonfire.Reported after the problem list
- TestCommandsDocumented covers the tool commands too
- docs: Upgrading to 0.4, delegates and global options, make: results and
  hints, the checklist for a finished command
2026-10-21 13:25:53 +02:00

9.3 KiB

title, description, section, order
title description section order
Scaffolding Create an application with summer new, build and watch it, and generate plugins, models, migrations, commands, jobs and admin controllers. console 30

Scaffolding

The summer tool creates and builds applications and generates the files a plugin is made of, the way create:plugin, create:model and the other create: commands do in WinterCMS. Every generated file compiles as written, so you can build straight after running a command.

Building and watching

Command Purpose
summer build Reads summer.yaml, generates plugins.gen.go and main.go, and builds the binary into bin/, passing the manifest's build.tags to go build as -tags. On a terminal a spinner runs while go build works; it ends with Built bin/<binary> in <time>. Run it from the application directory or any directory below it.
summer dev Builds the application as summer build does (build tags included), runs bin/<binary> serve, and rebuilds and restarts it whenever a Go or YAML source, go.mod, go.work, .env or summer.yaml changes. A failed build keeps the running server.
summer build
summer dev

summer dev passes its global options on to serve: summer dev -v shows the per-plugin lines of the startup summary and every component's info logs, summer dev -q only warnings and errors. Ctrl+C stops serve, which prints its shutdown report, then summer dev prints its own report (rebuilds, failed builds, restarts, uptime) and exits 0.

Do not edit main.go or plugins.gen.go: summer build rewrites them from the manifest.

Creating an application

summer new creates a new application in the current directory, the way composer create-project starts a WinterCMS project. It takes the application name and writes sm-<name>-app:

summer new acme

The name must be lower-case letters and digits, starting with a letter. Go keywords such as go or type, and the names main and internal, are refused because the starter plugin package would not compile or could not be imported. summer new also refuses any existing sm-<name>-app, even an empty directory, and never merges into one. When a step fails after it created the directory, it removes the directory again, so you can rerun the same command.

Flag Effect
--framework <path> The framework checkout to build against. Without it, summer new looks for a sibling summercms or summercms.go checkout, then the source tree the summer binary was built from. Go modules and workspaces describes the detection.
--no-plugin Skip the starter plugin; summer.yaml then lists no plugins, and the application still builds and serves.
--no-git Skip git init and the first commit.

The application gets these files:

File Contents
go.mod Module git.golem15.com/golem15/sm-<name>-app, requiring the framework through a relative replace.
go.work The workspace with the application and its plugin modules, as relative paths. It is committed.
summer.yaml The manifest: module path, binary name, plugin list and optional build.tags (for example summercms_s3 for an S3 uploads bucket).
config/ app.yaml, database.yaml, http.yaml and storage.yaml, enough for serve and route:list.
.env, .env.example The database DSN, a freshly generated application key and a random admin token secret (SUMMER_ADMIN__JWT__SECRET). .env is readable by its owner only and never committed; .env.example lists the variables without the key and the secret.
compose.yaml PostgreSQL 16 on 127.0.0.1:54320, with credentials matching .env.
.gitignore bin/, tmp/, storage/, .env and go.work.sum.
CLAUDE.md, AGENTS.md Instructions for AI coding agents: the commands, the layout, the admin, the links to the documentation for agents and the naming convention. AGENTS.md is a symlink to CLAUDE.md.
plugins/<name> The starter plugin golem15.<name>, created by the same code as summer make:plugin and registered by the same code as summer plugin:add. Its routes.go answers GET / with a JSON greeting.
main.go, plugins.gen.go Generated from summer.yaml, as summer build does.

Unless you pass --no-git, summer new initialises a git repository on main and commits everything except .env. When git has no user name or email configured, that first commit uses the author SummerCMS <summer@localhost> and prints a warning; your git configuration is not changed. Finally it prints the next steps to run inside sm-acme-app:

docker compose up -d --wait
summer build
summer migrate
summer serve

The next steps also explain the admin. It opens at /backend once a plugin registers an admin controller; until then the Admin line of summer serve's startup summary reads off, followed by these steps:

summer make:admin-controller golem15.acme Posts
summer build
summer migrate
summer admin:create --email you@example.com --superuser

Admin controllers explains the permission and model the generated controller needs.

Golem15 names repositories by what they hold: sm-<name>-app for an application such as the one summer new writes, sm-<name>-plugin for a standalone plugin module and sm-<name>-theme for a Nuxt or Vue frontend.

Creating a plugin

summer make:plugin takes a plugin ID in vendor.plugin form and creates the plugin module in plugins/<plugin> of the current application, with the directory layout described in Plugin registration:

summer make:plugin acme.blog
summer plugin:add plugins/blog

summer plugin:add then registers the local module: it adds the plugin to summer.yaml, adds a require and a local replace to the application's go.mod, and adds the directory to go.work. The next summer build compiles the plugin in.

Generating plugin parts

The other make: commands add one artifact to an existing plugin. Each takes the plugin ID and an exported Go name:

summer make:model acme.blog Post
summer make:migration acme.blog AddPublishedAt
summer make:command acme.blog Publish
summer make:job acme.blog ImportPosts
summer make:admin-controller acme.blog Posts

When you run a command inside a plugin directory, leave the ID out and pass only the name. The tool finds the plugin from the nearest plugin.go above the current directory:

cd plugins/blog
summer make:model Comment
Command Writes Notes
summer make:model models/<name>.go and updates/<timestamp>_create_<table>.go The table name is the plugin ID and the plural name in snake case, such as acme_blog_posts. --no-migration skips the migration.
summer make:migration updates/<timestamp>_<name>.go An empty gormigrate migration with up and down steps to fill in.
summer make:command console/<name>.go A bonfire.Command named <plugin>:<name>, such as blog:publish.
summer make:job jobs/<name>.go A typed job built with conga.Job; the plugin never imports the queue library.
summer make:admin-controller controllers/<name>.go, controllers/<name>/config_form.yaml, controllers/<name>/config_list.yaml, models/<name>/fields.yaml, models/<name>/columns.yaml A pact.AdminController with WinterCMS-shaped form and list configuration. It already requires the permission <plugin>.access_<name>, which you declare in the plugin's Permissions(), and its NewRecord returns nil until you return your model, so the screens stay closed until you finish it.

File names are the snake-case form of the name: AddPublishedAt becomes add_published_at. Migration file names start with a 14-digit timestamp, so they sort in the order you created them. A second migration with the same name in the same second gets the next second's timestamp, but migrations with different names created in the same second share one timestamp and sort by name; check the order in updates/, as Porting a plugin describes.

After writing the files, every make: command regenerates the plugin's registry.gen.go, which lists the plugin's models, migrations, commands, jobs and admin controllers, and runs go mod tidy in the plugin. The capability methods of a scaffolded plugin.go return those generated lists. If your plugin.go was written by hand and does not call them, the command prints a note naming the accessors to add.

Each command prints Created <path> for every file it writes. A command refuses to overwrite an existing file or to declare a name the package already has.

When a make: command cannot do its work, the error says why and what to run instead. Outside an application (no summer.yaml in the directory or above it) it reads:

 ERROR  build: no summer.yaml found from /home/jane

  summer make:model runs inside an application: a directory with summer.yaml, or one below it.

  Try:
    cd <app>; summer make:model acme.blog Post

An invalid plugin ID (Acme.Blog), a name that is not an exported Go identifier (post), an artifact that already exists and a plugin that summer.yaml does not list get the same kind of explanation, with a valid command form to try.