fix(build): scaffold plugins under plugins/<vendor>/<name> (quick-261028-pq8)
- make:plugin writes plugins/<vendor>/<name> with module <app>/plugins/<vendor>/<name>; the package name stays <name> - make:* and the models-leaf check look in summer.yaml, then plugins/<vendor>/<name>, then the older plugins/<name> - summer new puts its starter plugin at plugins/golem15/<name> - refuse a vendor directory that is a legacy plugin module and a second copy of a plugin that already sits in plugins/<name> - help text and docs, with an upgrading note: moving is optional
This commit is contained in:
@@ -76,9 +76,9 @@ module: git.golem15.com/acme/acme
|
||||
binary: acme
|
||||
plugins:
|
||||
- id: acme.user
|
||||
module: git.golem15.com/acme/acme/plugins/user
|
||||
module: git.golem15.com/acme/acme/plugins/acme/user
|
||||
- id: acme.blog
|
||||
module: git.golem15.com/acme/acme/plugins/blog
|
||||
module: git.golem15.com/acme/acme/plugins/acme/blog
|
||||
build:
|
||||
tags:
|
||||
- summercms_s3
|
||||
@@ -90,17 +90,17 @@ build:
|
||||
|
||||
## Local plugins in a workspace
|
||||
|
||||
A plugin you develop inside the application lives in `plugins/<name>` as its own module. `summer make:plugin acme.blog` creates it there, and `summer plugin:add plugins/blog` registers it:
|
||||
A plugin you develop inside the application lives in `plugins/<vendor>/<name>` as its own module. `summer make:plugin acme.blog` creates it in `plugins/acme/blog`, and `summer plugin:add plugins/acme/blog` registers it:
|
||||
|
||||
- it adds the plugin to `summer.yaml`;
|
||||
- it adds a `require` and a `replace` pointing at the local directory to the application's `go.mod`;
|
||||
- it adds the plugin directory to the nearest `go.work` as a path relative to that file, such as `./plugins/blog`, creating a `go.work` in the application root when there is none.
|
||||
- it adds the plugin directory to the nearest `go.work` as a path relative to that file, such as `./plugins/acme/blog`, creating a `go.work` in the application root when there is none.
|
||||
|
||||
With a `go.work` in place, `go build`, `go test` and your editor see every local plugin module together. `summer build` uses the nearest `go.work` it finds; without one it builds in module mode.
|
||||
|
||||
```sh
|
||||
summer make:plugin acme.blog
|
||||
summer plugin:add plugins/blog
|
||||
summer plugin:add plugins/acme/blog
|
||||
summer build
|
||||
```
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ The application gets these files:
|
||||
| `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. |
|
||||
| `plugins/golem15/<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`:
|
||||
@@ -79,15 +79,30 @@ Golem15 names repositories by what they hold: `sm-<name>-app` for an application
|
||||
|
||||
## 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](../plugins/registration.md):
|
||||
`summer make:plugin` takes a plugin ID in `vendor.plugin` form and creates the plugin module in `plugins/<vendor>/<plugin>` of the current application, with the directory layout described in [Plugin registration](../plugins/registration.md). `acme.blog` becomes `plugins/acme/blog` with the module path `<application module>/plugins/acme/blog` and `package blog`, so `acme.blog` and `pixelpixel.blog` can live in one application:
|
||||
|
||||
```sh
|
||||
summer make:plugin acme.blog
|
||||
summer plugin:add plugins/blog
|
||||
summer plugin:add plugins/acme/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.
|
||||
|
||||
### Upgrading: plugins without a vendor directory
|
||||
|
||||
Before this layout, `summer make:plugin` wrote `plugins/<plugin>` without the vendor, and `summer new` put its starter plugin there. Such a plugin keeps working: `summer build` follows `summer.yaml` and the `replace` lines of the application's `go.mod`, and the `make:` commands look for a plugin in `summer.yaml` first, then in `plugins/<vendor>/<plugin>`, then in `plugins/<plugin>`.
|
||||
|
||||
Moving it is optional. To move `acme.blog` from `plugins/blog` to `plugins/acme/blog`:
|
||||
|
||||
```sh
|
||||
mkdir -p plugins/acme
|
||||
mv plugins/blog plugins/acme/blog
|
||||
go work edit -dropuse ./plugins/blog
|
||||
summer plugin:add plugins/acme/blog
|
||||
```
|
||||
|
||||
`summer plugin:add` points the application's `replace` and `go.work` at the new directory; the module path and the `summer.yaml` entry stay as they were. If the plugin's own `go.mod` has a relative `replace` for the framework (or another local module), add one `../` to it, for example `../../../summercms` becomes `../../../../summercms`. Then run `summer build`.
|
||||
|
||||
## Generating plugin parts
|
||||
|
||||
The other `make:` commands add one artifact to an existing plugin. Each takes the plugin ID and an exported Go name:
|
||||
@@ -103,7 +118,7 @@ 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:
|
||||
|
||||
```sh
|
||||
cd plugins/blog
|
||||
cd plugins/acme/blog
|
||||
summer make:model Comment
|
||||
```
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestScaffoldLayout(t *testing.T) {
|
||||
t.Fatalf("MakeCommand: %v", err)
|
||||
}
|
||||
|
||||
scaffolded := fileSet(t, filepath.Join(app, "plugins", "blog"), func(rel string) bool {
|
||||
scaffolded := fileSet(t, filepath.Join(app, "plugins", "acme", "blog"), func(rel string) bool {
|
||||
return rel != "go.mod" && rel != "go.sum"
|
||||
})
|
||||
walkthrough := fileSet(t, ".", func(rel string) bool {
|
||||
|
||||
@@ -12,7 +12,7 @@ Plugins are the foundation of every SummerCMS application. A plugin adds models,
|
||||
|
||||
Every plugin has an ID in `vendor.plugin` form: two lower-case parts, each starting with a letter and containing only letters and digits, such as `acme.blog`. The ID is how the manifest lists the plugin, how other plugins require it and how its configuration is namespaced (`acme.blog.posts_per_page`). WinterCMS writes the same identifier as `Acme.Blog`; in SummerCMS it is always lower case.
|
||||
|
||||
The scaffolder derives the package and directory name from the second part, so `summer make:plugin acme.blog` creates `plugins/blog` with `package blog`.
|
||||
The scaffolder puts the plugin in a directory per vendor and names the package after the second part, so `summer make:plugin acme.blog` creates `plugins/acme/blog` with `package blog`, and `acme.blog` and `pixelpixel.blog` can sit side by side.
|
||||
|
||||
## The plugin type
|
||||
|
||||
@@ -104,7 +104,7 @@ Configuration defaults, translations and mail templates ship inside the binary t
|
||||
`summer make:plugin acme.blog` writes a plugin that compiles and follows the WinterCMS directory layout, with each directory as a Go subpackage:
|
||||
|
||||
```text
|
||||
plugins/blog/
|
||||
plugins/acme/blog/
|
||||
├── go.mod the plugin module, requiring the framework
|
||||
├── plugin.go the Plugin type, its capabilities and init registration
|
||||
├── routes.go the Routes method
|
||||
|
||||
@@ -23,7 +23,7 @@ Tests that need Docker, such as database tests, skip themselves in short mode. U
|
||||
go test -short ./...
|
||||
```
|
||||
|
||||
In an application with local plugins in a `go.work` workspace, run the tests of one plugin by its directory, for example `go test ./plugins/blog/...`.
|
||||
In an application with local plugins in a `go.work` workspace, run the tests of one plugin by its directory, for example `go test ./plugins/acme/blog/...`.
|
||||
|
||||
## Unit tests without a database
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ module: git.golem15.com/acme/acme
|
||||
binary: acme
|
||||
plugins:
|
||||
- id: acme.blog
|
||||
module: git.golem15.com/acme/acme/plugins/blog
|
||||
module: git.golem15.com/acme/acme/plugins/acme/blog
|
||||
build:
|
||||
tags:
|
||||
- summercms_s3
|
||||
|
||||
@@ -20,18 +20,18 @@ 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 plugin:add plugins/acme/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:plugin acme.blog` | `plugins/acme/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 plugin:add plugins/acme/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.
|
||||
@@ -39,7 +39,7 @@ Each `make:` command also rewrites `registry.gen.go` and runs `go mod tidy` in t
|
||||
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
|
||||
module example.com/acme/plugins/acme/blog
|
||||
|
||||
go 1.27.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user