@nakedev/go-scaffold 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +567 -513
  2. package/dist/commands/auth.js +12 -1
  3. package/dist/commands/check.js +281 -0
  4. package/dist/commands/create.js +2 -1
  5. package/dist/commands/generate.js +5 -2
  6. package/dist/commands/method.js +66 -70
  7. package/dist/commands/observability.js +4 -53
  8. package/dist/commands/rbac.js +19 -8
  9. package/dist/commands/undo.js +6 -3
  10. package/dist/commands/worker.js +14 -4
  11. package/dist/index.js +13 -1
  12. package/dist/templates/auth-manifest.js +46 -45
  13. package/dist/templates/create-manifest.js +4 -0
  14. package/dist/templates/module-manifest.js +82 -60
  15. package/dist/templates/rbac-manifest.js +15 -11
  16. package/dist/templates/worker-manifest.js +4 -1
  17. package/dist/types.js +2 -0
  18. package/dist/utils/auth-patcher.js +22 -22
  19. package/dist/utils/config.js +24 -5
  20. package/dist/utils/docs-patcher.js +68 -0
  21. package/dist/utils/hexagonal-method-patcher.js +334 -0
  22. package/dist/utils/main-patcher.js +3 -3
  23. package/dist/utils/module-location.js +17 -11
  24. package/dist/utils/platform-patcher.js +27 -0
  25. package/dist/utils/rbac-patcher.js +73 -216
  26. package/package.json +1 -1
  27. package/templates/add/auth/cmd/seed/main.go.hbs +2 -0
  28. package/templates/add/auth/internal/app/user/{browser_policy.go.hbs → adapters/inbound/http/browser_policy.go.hbs} +3 -3
  29. package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
  30. package/templates/add/auth/internal/app/user/{handler.go.hbs → adapters/inbound/http/handler.go.hbs} +103 -8
  31. package/templates/add/auth/internal/app/user/{handler_local.go.hbs → adapters/inbound/http/handler_local.go.hbs} +8 -7
  32. package/templates/add/auth/internal/app/user/{handler_mfa.go.hbs → adapters/inbound/http/handler_mfa.go.hbs} +6 -6
  33. package/templates/add/auth/internal/app/user/{handler_oauth.go.hbs → adapters/inbound/http/handler_oauth.go.hbs} +17 -17
  34. package/templates/add/auth/internal/app/user/{handler_recovery.go.hbs → adapters/inbound/http/handler_recovery.go.hbs} +4 -4
  35. package/templates/add/auth/internal/app/user/{handler_test.go.hbs → adapters/inbound/http/handler_test.go.hbs} +52 -31
  36. package/templates/add/auth/internal/app/user/{handler_user.go.hbs → adapters/inbound/http/handler_user.go.hbs} +4 -4
  37. package/templates/add/auth/internal/app/user/{session_cookie.go.hbs → adapters/inbound/http/session_cookie.go.hbs} +5 -3
  38. package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs +35 -0
  39. package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs +20 -0
  40. package/templates/add/auth/internal/app/user/{mfa_store.go.hbs → adapters/outbound/postgres/mfa_store.go.hbs} +22 -20
  41. package/templates/add/auth/internal/app/user/{mfa_store_test.go.hbs → adapters/outbound/postgres/mfa_store_test.go.hbs} +7 -7
  42. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +84 -0
  43. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +211 -0
  44. package/templates/add/auth/internal/app/user/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +18 -19
  45. package/templates/add/auth/internal/app/user/{tokenstore_pg.go.hbs → adapters/outbound/postgres/tokenstore_pg.go.hbs} +36 -32
  46. package/templates/add/auth/internal/app/user/{tokenstore_pg_test.go.hbs → adapters/outbound/postgres/tokenstore_pg_test.go.hbs} +10 -3
  47. package/templates/add/auth/internal/app/user/{tokenstore_recovery.go.hbs → adapters/outbound/postgres/tokenstore_recovery.go.hbs} +29 -3
  48. package/templates/add/auth/internal/app/user/{tokenstore_redis.go.hbs → adapters/outbound/redis/tokenstore.go.hbs} +43 -36
  49. package/templates/add/auth/internal/app/user/{tokenstore_redis_test.go.hbs → adapters/outbound/redis/tokenstore_test.go.hbs} +14 -3
  50. package/templates/add/auth/internal/app/user/application/contracts.go.hbs +52 -0
  51. package/templates/add/auth/internal/app/user/application/dto.go.hbs +75 -0
  52. package/templates/add/auth/internal/app/user/application/errors.go.hbs +62 -0
  53. package/templates/add/auth/internal/app/user/{external_login.go.hbs → application/external_login.go.hbs} +30 -40
  54. package/templates/add/auth/internal/app/user/{jwt.go.hbs → application/jwt.go.hbs} +6 -3
  55. package/templates/add/auth/internal/app/user/{local_auth.go.hbs → application/local_auth.go.hbs} +17 -19
  56. package/templates/add/auth/internal/app/user/{mfa_service.go.hbs → application/mfa_service.go.hbs} +27 -28
  57. package/templates/add/auth/internal/app/user/{mfa_service_test.go.hbs → application/mfa_service_test.go.hbs} +12 -11
  58. package/templates/add/auth/internal/app/user/application/oauth.go.hbs +3 -3
  59. package/templates/add/auth/internal/app/user/{provider_test.go.hbs → application/provider_test.go.hbs} +55 -56
  60. package/templates/add/auth/internal/app/user/application/recovery.go.hbs +16 -47
  61. package/templates/add/auth/internal/app/user/{recovery_service.go.hbs → application/recovery_service.go.hbs} +13 -15
  62. package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
  63. package/templates/add/auth/internal/app/user/{service_test.go.hbs → application/service_test.go.hbs} +94 -75
  64. package/templates/add/auth/internal/app/user/{sessions.go.hbs → application/sessions.go.hbs} +14 -14
  65. package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
  66. package/templates/add/auth/internal/app/user/{user_query.go.hbs → application/user_query.go.hbs} +16 -16
  67. package/templates/add/auth/internal/app/user/composition.go.hbs +89 -86
  68. package/templates/add/auth/internal/app/user/domain/entity.go.hbs +41 -0
  69. package/templates/add/auth/internal/app/user/domain/errors.go.hbs +32 -0
  70. package/templates/add/auth/internal/app/user/ports/password.go.hbs +9 -0
  71. package/templates/add/auth/internal/app/user/ports/repository.go.hbs +90 -0
  72. package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +1 -1
  73. package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +1 -1
  74. package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -1
  75. package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs +142 -0
  76. package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs +19 -0
  77. package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs +48 -0
  78. package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs +127 -0
  79. package/templates/add/rbac/internal/app/role/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +8 -8
  80. package/templates/add/rbac/internal/app/role/application/dto.go.hbs +47 -0
  81. package/templates/add/rbac/internal/app/role/application/errors.go.hbs +19 -0
  82. package/templates/add/rbac/internal/app/role/application/service.go.hbs +157 -0
  83. package/templates/add/rbac/internal/app/role/{service_test.go.hbs → application/service_test.go.hbs} +26 -19
  84. package/templates/add/rbac/internal/app/role/composition.go.hbs +23 -10
  85. package/templates/add/rbac/internal/app/role/domain/entity.go.hbs +23 -0
  86. package/templates/add/rbac/internal/app/role/domain/errors.go.hbs +26 -0
  87. package/templates/add/rbac/internal/app/role/ports/repository.go.hbs +25 -0
  88. package/templates/add/rbac/migrations/add_roles.down.sql.hbs +3 -11
  89. package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -6
  90. package/templates/add/worker/internal/platform/queue/river_test.go.hbs +84 -0
  91. package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +54 -36
  92. package/templates/create/base/.golangci.yml.hbs +2 -2
  93. package/templates/create/base/AGENTS.md.hbs +55 -31
  94. package/templates/create/base/README.md.hbs +77 -22
  95. package/templates/create/base/cmd/api/wiring.go.hbs +3 -2
  96. package/templates/create/base/internal/composition/doc.go.hbs +7 -0
  97. package/templates/create/features/docs/architecture.md.hbs +64 -26
  98. package/templates/create/features/docs/patterns.md.hbs +117 -90
  99. package/templates/create/features/docs/techstack.md.hbs +17 -2
  100. package/templates/generate/module/hexagonal/adapters/inbound/http/dto.go.hbs +45 -0
  101. package/templates/generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs +28 -0
  102. package/templates/generate/module/hexagonal/adapters/inbound/http/handler.go.hbs +182 -0
  103. package/templates/generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs +83 -0
  104. package/templates/generate/module/hexagonal/adapters/inbound/http/handler_crud_test.go.hbs +18 -0
  105. package/templates/generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs +30 -0
  106. package/templates/generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs +37 -0
  107. package/templates/generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs +95 -0
  108. package/templates/generate/module/{repository_test.go.hbs → hexagonal/adapters/outbound/postgres/repository_test.go.hbs} +8 -8
  109. package/templates/generate/module/hexagonal/application/commands.crud.go.hbs +54 -0
  110. package/templates/generate/module/hexagonal/application/commands.go.hbs +25 -0
  111. package/templates/generate/module/hexagonal/application/cqrs_test.go.hbs +66 -0
  112. package/templates/generate/module/hexagonal/application/dto.go.hbs +35 -0
  113. package/templates/generate/module/hexagonal/application/dto.minimal.go.hbs +25 -0
  114. package/templates/generate/module/hexagonal/application/queries.crud.go.hbs +33 -0
  115. package/templates/generate/module/hexagonal/application/queries.go.hbs +25 -0
  116. package/templates/generate/module/hexagonal/application/service.crud.go.hbs +73 -0
  117. package/templates/generate/module/hexagonal/application/service.go.hbs +29 -0
  118. package/templates/generate/module/hexagonal/application/service_test.go.hbs +62 -0
  119. package/templates/generate/module/hexagonal/composition.go.hbs +27 -0
  120. package/templates/generate/module/hexagonal/domain/entity.go.hbs +20 -0
  121. package/templates/generate/module/hexagonal/domain/errors.go.hbs +11 -0
  122. package/templates/generate/module/hexagonal/ports/repository.go.hbs +38 -0
  123. package/templates/generate/module/migration.up.sql.hbs +1 -1
  124. package/dist/utils/method-patcher.js +0 -537
  125. package/templates/add/auth/internal/app/user/contracts.go.hbs +0 -88
  126. package/templates/add/auth/internal/app/user/dto.go.hbs +0 -134
  127. package/templates/add/auth/internal/app/user/errors.go.hbs +0 -68
  128. package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -45
  129. package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -32
  130. package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
  131. package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +0 -17
  132. package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +0 -20
  133. package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +0 -17
  134. package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -31
  135. package/templates/add/auth/internal/app/user/repository.go.hbs +0 -139
  136. package/templates/add/auth/internal/app/user/service.go.hbs +0 -135
  137. package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -58
  138. package/templates/add/rbac/internal/app/role/dto.go.hbs +0 -45
  139. package/templates/add/rbac/internal/app/role/errors.go.hbs +0 -39
  140. package/templates/add/rbac/internal/app/role/handler.go.hbs +0 -104
  141. package/templates/add/rbac/internal/app/role/model/permission.go.hbs +0 -12
  142. package/templates/add/rbac/internal/app/role/model/role.go.hbs +0 -22
  143. package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +0 -11
  144. package/templates/add/rbac/internal/app/role/repository.go.hbs +0 -97
  145. package/templates/add/rbac/internal/app/role/service.go.hbs +0 -217
  146. package/templates/generate/module/commands.go.hbs +0 -95
  147. package/templates/generate/module/composition.go.hbs +0 -23
  148. package/templates/generate/module/cqrs_test.go.hbs +0 -7
  149. package/templates/generate/module/dto.go.hbs +0 -36
  150. package/templates/generate/module/errors.go.hbs +0 -33
  151. package/templates/generate/module/handler.go.hbs +0 -179
  152. package/templates/generate/module/handler_test.go.hbs +0 -174
  153. package/templates/generate/module/minimal/commands.go.hbs +0 -34
  154. package/templates/generate/module/minimal/dto.go.hbs +0 -28
  155. package/templates/generate/module/minimal/handler.go.hbs +0 -82
  156. package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
  157. package/templates/generate/module/minimal/queries.go.hbs +0 -45
  158. package/templates/generate/module/minimal/service.go.hbs +0 -71
  159. package/templates/generate/module/minimal/service_test.go.hbs +0 -77
  160. package/templates/generate/module/model/model.go.hbs +0 -36
  161. package/templates/generate/module/queries.go.hbs +0 -62
  162. package/templates/generate/module/repository.go.hbs +0 -103
  163. package/templates/generate/module/service.go.hbs +0 -164
  164. package/templates/generate/module/service_test.go.hbs +0 -161
package/README.md CHANGED
@@ -1,635 +1,689 @@
1
1
  # @nakedev/go-scaffold
2
2
 
3
- A CLI that scaffolds a Gin + GORM + PostgreSQL Go backend, then keeps
4
- generating consistent domain modules into that project as it grows — the Go
5
- counterpart to nest-scaffold.
3
+ @nakedev/go-scaffold is an npm CLI for creating Gin + GORM + PostgreSQL Go
4
+ backend projects and extending them with consistent domain modules, endpoints,
5
+ migrations, authentication, background jobs, RBAC, and observability.
6
6
 
7
- You don't hand-wire a new domain's repository/service/handler composition into
8
- `cmd/api/wiring.go`, write the
9
- handler/service/repository boilerplate, or decide error-handling conventions
10
- each time — the CLI does that, and every module it generates follows the
11
- same shape as the last one.
7
+ The normal workflow is simple: install the CLI with npm, run the wizard, then
8
+ run the same CLI from the generated project whenever the backend grows.
12
9
 
13
- ## Install
10
+ ## Install with npm
14
11
 
15
- ```bash
16
- npm install -g @nakedev/go-scaffold
17
- ```
12
+ Install the CLI globally when you expect to use it repeatedly:
18
13
 
19
- Or run it without installing:
14
+ ~~~bash
15
+ npm install --global @nakedev/go-scaffold
20
16
 
21
- ```bash
17
+ go-scaffold --version
18
+ go-scaffold --help
19
+ ~~~
20
+
21
+ npx is also supported when you do not want a global installation:
22
+
23
+ ~~~bash
22
24
  npx @nakedev/go-scaffold create my-api
23
- ```
25
+ npx @nakedev/go-scaffold --help
26
+ ~~~
27
+
28
+ ### Requirements
24
29
 
25
- Working on the CLI itself (not just using it)? Clone the repo, then:
30
+ - Node.js >=22.13 to run the CLI. npm and npx are included with Node.js.
31
+ - Go >=1.25 to build and run the generated project.
32
+ - PostgreSQL to run the application. Docker is optional; create can generate
33
+ a Docker Compose PostgreSQL service for local development.
34
+
35
+ The CLI itself only needs Node.js. Go and PostgreSQL are needed after a project
36
+ has been generated.
37
+
38
+ ## Developing the CLI
39
+
40
+ This repository is the source for the CLI and its generated-project contract.
41
+ Keep command logic in `src/`, emitted files in `templates/`, and behavior
42
+ covered by `tests/`. Generated-project `AGENTS.md`, Claude skill guidance, and
43
+ `docs/architect/` files are template outputs; update the template and a
44
+ regression test when their contract changes.
26
45
 
27
46
  ```bash
28
- pnpm install
47
+ pnpm install --frozen-lockfile
29
48
  pnpm run build
49
+ pnpm run verify
30
50
  node bin/go-scaffold.js create my-api --defaults
31
51
  ```
32
52
 
33
- `npm link` / `pnpm link --global` may not put the binary on your `PATH`
34
- depending on your machine's npm/pnpm global-bin config — running
35
- `node bin/go-scaffold.js ...` directly sidesteps that.
53
+ `pnpm run verify` is the required local gate: it builds the TypeScript CLI,
54
+ runs unit and integration tests, and runs the generated-project smoke suite.
55
+ The smoke suite needs the Go toolchain, PostgreSQL/migrate, and golangci-lint;
56
+ do not treat an unavailable external check as a passing generated-project
57
+ check. See [`AGENTS.md`](AGENTS.md) for the source/template/test workflow.
36
58
 
37
- ## Release: npm is locked to Git
59
+ ### Release contract
38
60
 
39
- Git is the release source of truth. The package version, annotated tag, and
40
- tagged commit must agree before npm can publish:
61
+ `package.json` is the version source. Releases are published by
62
+ [`.github/workflows/release.yml`](.github/workflows/release.yml) only from a
63
+ clean, matching annotated `vX.Y.Z` tag. Before pushing a release tag, verify
64
+ the exact commit with:
41
65
 
42
- ```text
43
- package.json 0.4.3
44
-
45
- └── annotated tag v0.4.3 ──> the exact release commit
46
-
47
- └── npm @nakedev/go-scaffold@0.4.3
66
+ ```bash
67
+ pnpm run release:check -- v0.5.0
48
68
  ```
49
69
 
50
- `npm publish` runs `release:check` first, so publishing from an untagged,
51
- lightweight-tagged, dirty, or mismatched checkout fails before anything is
52
- sent to npm. The GitHub Actions workflow at
53
- `.github/workflows/release.yml` repeats that check, runs the full verification
54
- gate, publishes from the tag, and creates the GitHub Release.
70
+ The release workflow runs the full verification gate before npm publish and
71
+ GitHub Release creation.
55
72
 
56
- ### One-time npm setup
73
+ ## Quick start
57
74
 
58
- The preferred setup is npm Trusted Publishing with GitHub Actions (OIDC):
75
+ ~~~bash
76
+ npm install --global @nakedev/go-scaffold
77
+ go-scaffold create my-api
59
78
 
60
- 1. In npm package settings for `@nakedev/go-scaffold`, add a GitHub Actions
61
- trusted publisher for `NakePranob/go-scaffold`.
62
- 2. Set the workflow filename to `release.yml` and environment to
63
- `npm-release`.
64
- 3. Allow `npm publish`. The workflow already requests the required OIDC
65
- permission and uses Node 24.
79
+ cd my-api
80
+ cp .env.example .env # optional: edit local settings
81
+ make docker-up # only when create included Docker + PostgreSQL
82
+ make db-create
83
+ go mod tidy
84
+ make run
85
+ ~~~
66
86
 
67
- If Trusted Publishing is not used, add an npm granular token as the GitHub
68
- repository/environment secret `NPM_TOKEN`. Never commit a token or put it in
69
- `.npmrc`.
87
+ The default create wizard includes Docker and OpenAPI files. If you choose
88
+ --no-docker, start PostgreSQL separately before running make db-create.
70
89
 
71
- ### Branch policy
90
+ Add a first domain from the generated project directory:
72
91
 
73
- `develop` is the integration branch. A PR into `main` must come from
74
- `develop` or `release/*`, and must bump `package.json` to a semver greater than
75
- the version on `main`. PRs into `develop` do not need a version bump.
92
+ ~~~bash
93
+ go-scaffold generate module orders
94
+ go-scaffold generate method orders approve --type patch
95
+ ~~~
76
96
 
77
- The `main-merge-policy` workflow checks this automatically. In GitHub branch
78
- rules, protect `main`, require a pull request, require the
79
- `main-merge-policy`, `verify`, and `packaged-artifact` checks, and disable
80
- force-pushes. Protect the `v*` tag pattern from updates and deletion as well.
97
+ The generated project starts with shared infrastructure and no business
98
+ domains. generate module adds the domain; generate method adds endpoints one
99
+ at a time.
81
100
 
82
- ### Release flow
101
+ ## How the wizard works
83
102
 
84
- ```bash
85
- git switch develop
86
- git pull --ff-only origin develop
87
- git switch -c release/v0.4.3
88
- npm version 0.4.3 --no-git-tag-version
89
- pnpm run verify
90
- git add package.json
91
- git commit -m "chore: release v0.4.3"
92
- git push -u origin release/v0.4.3
93
- # open a PR from release/v0.4.3 into main
94
-
95
- # after the PR is merged, tag the merge commit on main
96
- git switch main
97
- git pull --ff-only origin main
98
- git tag -a v0.4.3 -m "v0.4.3"
99
- git push origin v0.4.3
100
- ```
103
+ You can use the CLI interactively or provide answers as arguments and flags.
101
104
 
102
- Pushing the tag starts the release workflow. To retry a failed publish for a
103
- tag created after this workflow landed, use GitHub Actions' **Run workflow**
104
- with that tag; do not create a second tag for the same package version. A tag
105
- created before this guard existed should not be moved after it has been
106
- pushed — use the next patch version instead.
105
+ ### Start with the top-level wizard
107
106
 
108
- ## Requirements
107
+ Running the CLI without a command opens a menu:
109
108
 
110
- - Node.js `>=22.13` to run the CLI
111
- - Go `>=1.25` for the generated project
112
- - A running PostgreSQL instance; Docker is optional, but either `psql` or a
113
- Docker container is needed by `make db-create`
109
+ ~~~bash
110
+ go-scaffold
111
+ ~~~
114
112
 
115
- ## Quick start
113
+ From a generated project directory, the menu offers:
116
114
 
117
- ```bash
118
- go-scaffold create my-api
119
- cd my-api
120
- make docker-up # if you kept Docker + PostgreSQL
121
- make db-create # create the database itself (safe to re-run)
122
- go mod tidy
123
- make run
124
- ```
115
+ 1. Create a new project
116
+ 2. Generate a module, method, or migration
117
+ 3. Configure defaults for future modules
118
+ 4. Add auth, worker, RBAC, or observability
119
+ 5. Undo a generated module
125
120
 
126
- Then grow the project without leaving the CLI:
121
+ When run outside a generated project, only create can run, so the CLI goes
122
+ straight to the project-creation flow.
127
123
 
128
- ```bash
129
- go-scaffold generate module orders
130
- go-scaffold generate method orders approve --type patch
131
- ```
124
+ You can also open a more focused wizard:
125
+
126
+ ~~~bash
127
+ go-scaffold generate # choose module, method, or migration
128
+ go-scaffold add # choose worker, auth, RBAC, or observability
129
+ go-scaffold undo # choose a generated module and confirm
130
+ ~~~
132
131
 
133
- ## The 30-second flow
132
+ ### Answer only what is missing
134
133
 
135
- The CLI has two layers of interaction: the command you choose, then only the
136
- questions that command still needs. Flags are answers, not requests to ask the
137
- same question again.
134
+ The command is the first choice, followed by only the questions that still need
135
+ an answer. A value passed as a flag is not asked again.
138
136
 
139
- ```text
137
+ For example:
138
+
139
+ ~~~text
140
140
  go-scaffold create my-api
141
141
  1. Docker + PostgreSQL?
142
142
  2. OpenAPI files?
143
143
  3. Metrics + tracing?
144
144
  4. API route prefix?
145
145
  5. Default module profile: Lean / CRUD / CQRS / Advanced?
146
- 6. Summary confirmation
146
+ 6. Create project with these settings?
147
147
 
148
148
  cd my-api
149
- go-scaffold generate module users
149
+ go-scaffold generate module orders
150
150
  1. Module profile: Lean / CRUD / CQRS / Advanced?
151
- 2. Require an access token? # only when auth is installed
152
- 3. Permission code? # only when auth + RBAC are installed
153
- -> writes internal/app/user/, its migration, wiring, and config metadata
154
- ```
151
+ 2. Require an access token? # only when auth is installed
152
+ 3. Permission code? # only when auth + RBAC are installed
153
+ ~~~
155
154
 
156
- `Advanced` is the only path that asks the two lower-level architecture
157
- questions separately. `--profile` and `--defaults` are the scripted forms; the
158
- latter uses the project defaults recorded in `go-scaffold.config.json`.
155
+ Advanced is the wizard option that exposes the two lower-level module choices
156
+ separately: module surface and application boundary.
159
157
 
160
- ## Commands
158
+ ### Non-interactive usage
161
159
 
162
- Every `add` command shows what it's about to do and asks before writing;
163
- `-y/--yes` skips that (and `--defaults` implies it) for CI and scripts.
164
- Running `go-scaffold` with no arguments picks the command from a menu.
160
+ Use flags for scripts and CI. --defaults skips the optional wizard questions
161
+ where the command supports it. A project name is still required for scripted
162
+ create usage.
165
163
 
166
- ### Wizard coverage
164
+ ~~~bash
165
+ go-scaffold create my-api --defaults
166
+ go-scaffold generate module orders --defaults
167
+ go-scaffold add worker --defaults
168
+ go-scaffold add auth --defaults
169
+ ~~~
167
170
 
168
- The interactive path is deliberately available from both the bare command and
169
- the direct command form:
171
+ Important behavior:
170
172
 
171
- | Command | Wizard coverage |
173
+ - In a terminal, omitted values open the relevant wizard.
174
+ - Without an interactive terminal, omitted values cause an error before files
175
+ are written. Pass the missing flags or use --defaults.
176
+ - --yes / -y skips an add or undo confirmation. It does not necessarily answer
177
+ every other wizard question; pass the choice flags too.
178
+ - Every add command shows a summary and asks for confirmation unless --yes or
179
+ --defaults is used.
180
+ - Run any command with --help to see its current options.
181
+
182
+ ## Command overview
183
+
184
+ | Command | Purpose | Alias |
185
+ |---|---|---|
186
+ | create [name] | Create a new Go backend project | c |
187
+ | check | Validate hexagonal layout, layer dependencies, and service/CQRS contract | — |
188
+ | generate | Open the module/method/migration wizard | g |
189
+ | generate module [name] | Add a domain module | g m |
190
+ | generate method [module] [name] | Add one endpoint to an existing module | g me |
191
+ | generate migration [name] | Create a timestamped SQL migration pair | g mig |
192
+ | config | Set defaults for future modules | — |
193
+ | config show | Print the resolved project configuration | — |
194
+ | config validate | Validate configuration without changing files | — |
195
+ | add | Open the infrastructure-feature wizard | — |
196
+ | add worker | Add background jobs, mail, and cmd/worker | — |
197
+ | add auth | Add email/password and provider authentication | — |
198
+ | add rbac | Add roles, permissions, and authorization middleware | — |
199
+ | add observability | Add Prometheus metrics and OpenTelemetry tracing | — |
200
+ | undo module [name] | Remove a generated module and its wiring | undo m |
201
+
202
+ All commands except create are intended to run from the generated project
203
+ directory.
204
+
205
+ ## create [name] — create a project
206
+
207
+ ~~~bash
208
+ go-scaffold create my-api # interactive wizard
209
+ go-scaffold create my-api --defaults # use documented defaults
210
+ go-scaffold create my-api --defaults --no-docker
211
+ go-scaffold create my-api --api-prefix v1
212
+ go-scaffold c my-api
213
+ ~~~
214
+
215
+ If [name] is omitted, the wizard asks for it. With a simple name, it becomes
216
+ both the project directory and the Go module path in the generated go.mod. A
217
+ full Go module path is also accepted; in that case the project directory uses
218
+ the path's final segment.
219
+
220
+ ### Creation options
221
+
222
+ | Option | Effect |
172
223
  |---|---|
173
- | `create [name]` | asks for the project name and settings that were not passed as flags |
174
- | `generate` / `generate module [name]` | chooses a target, module name, and module profile; `Advanced` asks the two underlying architecture questions |
175
- | `generate method [module] [name]` | asks for the existing module, method name, HTTP verb, GET mode, and lookup field when needed |
176
- | `generate migration [name]` | asks for the migration name when omitted |
177
- | `config` | edits future module defaults; existing modules are unchanged |
178
- | `config show` / `config validate` | intentionally no wizard: read-only print/validation commands |
179
- | `add` / `add worker` / `add auth` | chooses the feature, backend/topology, and confirmation where applicable |
180
- | `add rbac` / `add observability` | no parameter choice is needed; the direct command confirms, while bare `add` selects the target |
181
- | `undo` / `undo module [name]` | asks for the generated module and confirmation when omitted |
224
+ | --defaults | Skip settings prompts; use Docker and OpenAPI, no API prefix, and Lean defaults for future modules |
225
+ | --no-docker | Do not create docker-compose.yml or a local PostgreSQL service |
226
+ | --no-openapi-docs | Do not create docs/openapi.yaml or OpenAPI files for generated features/modules |
227
+ | --observability | Include Prometheus /metrics and OpenTelemetry tracing at creation time |
228
+ | --api-prefix <prefix> | Put every API route under a prefix such as v1 or api/v1 |
229
+ | --module-profile <lean\|crud\|cqrs> | Set the default profile for future generate module commands |
230
+ | --module-surface <minimal\|crud> | Legacy way to set the future module surface; prefer --module-profile |
231
+ | --application-style <service\|cqrs> | Legacy way to set the future application boundary; prefer --module-profile |
182
232
 
183
- Run any command with `--help` for the non-interactive equivalent. If a value is
184
- omitted in a non-TTY shell, the CLI exits before writing and tells you which
185
- flag or `--defaults` is required.
233
+ The route prefix is a project-wide choice. For example, --api-prefix v1 puts a
234
+ module route under /v1/orders.
186
235
 
236
+ ### What create generates
187
237
 
188
- ### `create <name>` scaffold a new project
238
+ create produces a runnable base skeleton, not a business domain. It includes
239
+ the API entrypoint, shared packages, database connection, migrations folder,
240
+ development commands, and architecture documentation. Add domains later with
241
+ generate module.
189
242
 
190
- ```bash
191
- go-scaffold create my-api # interactive wizard
192
- go-scaffold create my-api --defaults # no prompts, CI-friendly
193
- go-scaffold create my-api --defaults --no-docker --api-prefix beta
194
- ```
243
+ Every project also gets go-scaffold.config.json. It stores the project defaults,
244
+ installed features, API prefix, and the resolved profile of each module so later
245
+ CLI commands can continue from the same choices.
195
246
 
196
- Produces a **bare skeleton only** — `cmd/api`, the shared platform packages
197
- (config/apperror/dberr/httpx/id/middleware/pagination/tx), Docker+Postgres,
198
- migrations folder, and the standards docs (`docs/architect/`, `AGENTS.md`,
199
- `CLAUDE.md`, `.claude/skills/go-scaffold/`). No domain modules — add those
200
- with `generate module`.
247
+ ## Module profiles
201
248
 
202
- | Option | Effect |
203
- |---|---|
204
- | `--defaults` | Skip settings prompts; use Docker/OpenAPI on, no prefix, and Lean (`minimal + service`) defaults for future modules |
205
- | `--no-docker` | Do not create `docker-compose.yml` or include a local Postgres service |
206
- | `--no-openapi-docs` | Do not create `docs/openapi.yaml` or per-module OpenAPI files |
207
- | `--observability` | Include Prometheus `/metrics` + OpenTelemetry tracing; off unless passed |
208
- | `--api-prefix <prefix>` | Group every API route under a prefix such as `v1` or `api/v1`; omit for no prefix |
209
- | `--module-profile <lean\|crud\|cqrs>` | Default profile for future modules; replaces the two architecture questions |
210
- | `--module-surface <minimal\|crud>` | Legacy axis flag for future modules; use `--module-profile` for a clearer preset |
211
- | `--application-style <service\|cqrs>` | Legacy axis flag for future modules; use `--module-profile` for a clearer preset |
212
-
213
- Without `--defaults`, an interactive wizard asks the project questions —
214
- skipping any a flag already answered, so `create my-api --no-docker` never asks
215
- about Docker and never scaffolds it. It also asks for the default module
216
- profile so future `generate module` commands start with the project's
217
- conventions. Choose `Advanced` when you intentionally want the less common
218
- CRUD + CQRS combination.
219
- The prefix is a single project-wide choice made once at `create` time —
220
- there's no per-domain versioning (a domain that needs a real breaking change
221
- gets a new domain package or a new DTO field, not a duplicated model pointed
222
- at the same table under a different URL — see "Why no per-domain versioning"
223
- below).
224
-
225
- **Config file** — every `create` writes `go-scaffold.config.json` to the
226
- project root; `generate` reads it back (or auto-detects from `go.mod` /
227
- directory layout if missing). It records project defaults and the resolved
228
- surface/application style of each generated module:
229
-
230
- ```json
231
- {
232
- "schemaVersion": 1,
233
- "architecture": {
234
- "style": "modular-monolith",
235
- "defaultModuleSurface": "minimal",
236
- "defaultApplicationStyle": "service"
237
- },
238
- "modules": {
239
- "order": { "surface": "crud", "applicationStyle": "cqrs" }
240
- }
241
- }
242
- ```
249
+ The module wizard presents a useful profile before exposing lower-level
250
+ architecture choices:
243
251
 
244
- Run these from the generated project root. Use the wizard again later without
245
- recreating the project:
252
+ | Profile | Generated shape | Use it when |
253
+ |---|---|---|
254
+ | lean | Minimal surface + one service | The domain should start small and gain endpoints as requirements become real |
255
+ | crud | CRUD surface + one service | The domain needs list/get/create/update/delete starter endpoints |
256
+ | cqrs | Minimal surface + command/query handlers | Reads and writes have different application concerns |
257
+ | Advanced | Choose surface and application style separately | You need a custom combination, such as CRUD + CQRS |
246
258
 
247
- ```bash
248
- go-scaffold config # interactive project-default wizard
249
- go-scaffold config show # print the resolved config
250
- go-scaffold config validate # validate without changing anything
251
- ```
259
+ minimal means no default CRUD endpoints are invented. CQRS separates the
260
+ command and query application paths inside the same modular monolith; it does
261
+ not add a second database, broker, or event-sourcing system.
252
262
 
253
- ### Module profiles
263
+ The generated code is DDD-shaped: each domain has a package boundary,
264
+ repository port, application boundary, delivery adapter, and shared error
265
+ conventions. The CLI does not invent your aggregates, fields, value objects,
266
+ events, or business invariants.
254
267
 
255
- The wizard asks for one useful profile instead of forcing everyone to reason
256
- about two implementation axes up front:
268
+ ## generate module [name] add a domain
257
269
 
258
- | Profile | Resolves to | Use it when |
259
- |---|---|---|
260
- | `lean` | minimal surface + one service | the domain should start small and grow endpoint by endpoint |
261
- | `crud` | CRUD surface + one service | the domain genuinely needs the standard list/get/create/update/delete starter |
262
- | `cqrs` | minimal surface + command/query handlers | reads and writes have different business models, invariants, or scaling pressure |
263
- | `Advanced` (wizard only) | choose both axes separately | you deliberately want a custom mix, including CRUD + CQRS |
270
+ ~~~bash
271
+ go-scaffold generate module orders # profile wizard
272
+ go-scaffold generate module orders --profile lean
273
+ go-scaffold generate module orders --profile crud
274
+ go-scaffold generate module orders --profile cqrs
275
+ go-scaffold generate module orders --defaults # use project defaults
276
+ go-scaffold g m orders --profile crud
277
+ ~~~
264
278
 
265
- The scaffold is DDD-shaped rather than a complete tactical DDD implementation:
266
- it gives each domain a package boundary, repository port, application boundary,
267
- feature-local composition, and shared error conventions. It does not invent
268
- aggregates, value objects, domain events, or business invariants for you; those
269
- belong to the domain team.
279
+ When no name is supplied, the wizard asks for a singular module name such as
280
+ order or product. The package name is normalised for Go, while routes and
281
+ tables use plural names such as orders and order_items.
270
282
 
271
- `minimal` means the generator does not invent five endpoints before the domain
272
- has real requirements. `CQRS` does not mean a second database, broker, or event
273
- bus here. It only separates command and query application handlers inside the
274
- same modular monolith.
283
+ ### Module options
275
284
 
276
- ### `generate module <name>` (alias `m`) — add a domain module
285
+ | Option | Effect |
286
+ |---|---|
287
+ | --profile <lean\|crud\|cqrs> | Select a named profile without opening the profile question |
288
+ | --full | Legacy alias for the CRUD surface |
289
+ | --cqrs | Legacy axis flag for command/query handlers; combine with --full for CRUD + CQRS |
290
+ | --auth | Require a valid access token for this module's routes; needs add auth |
291
+ | --permission <code> | Also require an RBAC permission such as orders:manage; needs add rbac and --auth |
292
+ | --defaults | Use the project's recorded module defaults, skip prompts, and keep the module public |
277
293
 
278
- ```bash
279
- go-scaffold generate module orders # asks for profile (and auth, if installed)
280
- go-scaffold generate module orders --profile lean # explicit Lean profile, no architecture prompt
281
- go-scaffold generate module orders --profile crud # explicit CRUD profile, no architecture prompt
282
- go-scaffold generate module orders --profile cqrs # explicit CQRS profile, no architecture prompt
283
- go-scaffold generate module orders --full --cqrs # legacy flags: CRUD + separate command/query handlers
284
- go-scaffold generate module orders --defaults # use project defaults, no prompt (CI/scripting)
285
- ```
294
+ --profile cannot be combined with the legacy --full or --cqrs flags. With
295
+ --defaults, a fresh project generates the Lean shape. If the project has auth
296
+ installed, the module remains public unless --auth is explicitly passed.
286
297
 
287
- Anything you don't pass as a flag is asked for; the prompt starts with the
288
- project defaults. `--defaults` uses those defaults without asking anything
289
- (fresh and legacy projects default to Lean, with no auth). `--profile` is the
290
- non-interactive equivalent of choosing a named profile for this module. The
291
- older `--full` and `--cqrs` flags remain supported for existing scripts; do not
292
- combine them with `--profile`.
298
+ ### Module output
293
299
 
294
- `--full` scaffolds:
300
+ For orders, the module package is internal/app/order/:
295
301
 
296
- ```text
302
+ ~~~text
297
303
  internal/app/order/
298
- ├── model/model.go # domain model + GORM table (id/created_at/updated_at — add real fields yourself; a folder so multi-table domains can add more files)
299
- ├── dto.go # request/response structs (empty stubs — add real fields yourself)
300
- ├── errors.go # ORDER_NOT_FOUND / ORDER_CONFLICT / ORDER_HAS_REFERENCES / ORDER_STALE
301
- ├── repository.go # GORM data access
302
- ├── service.go # business logic + repository interface (mockable)
303
- ├── composition.go # feature-local repository → service → handler wiring
304
- ├── handler.go # Gin routes, registered under the project's API prefix
305
- ├── service_test.go # unit test, function-backed repository stub
306
- ├── handler_test.go # HTTP unit test, service stub, no DB
307
- └── repository_test.go # Postgres integration test against migrated schema
308
- ```
304
+ ├── domain/
305
+ ├── entity.go # business state and invariants
306
+ │ └── errors.go # domain error sentinels
307
+ ├── ports/
308
+ │ └── repository.go # consumer-owned persistence ports
309
+ ├── application/
310
+ ├── dto.go # use-case inputs and response mapping
311
+ ├── service.go # service-style application boundary
312
+ │ └── service_test.go # application unit tests
313
+ ├── adapters/
314
+ │ ├── inbound/http/
315
+ │ │ ├── handler.go # Gin delivery adapter
316
+ │ │ └── handler_test.go # HTTP adapter tests
317
+ │ └── outbound/postgres/
318
+ │ ├── model.go # persistence model + mapping
319
+ │ ├── repository.go # GORM adapter
320
+ │ └── repository_test.go # PostgreSQL integration tests
321
+ └── composition.go # feature-local object graph
322
+ ~~~
323
+
324
+ CRUD modules contain the starter list/get/create/update/delete methods. Lean
325
+ modules keep the endpoint surface small so it can be extended with
326
+ generate method.
327
+
328
+ CQRS modules additionally contain:
329
+
330
+ ~~~text
331
+ internal/app/order/
332
+ └── application/
333
+ ├── commands.go # command port + state-changing handlers
334
+ ├── queries.go # query port + read-only handlers
335
+ └── cqrs_test.go # command/query boundary tests
336
+ ~~~
309
337
 
310
- With `--cqrs`, the module also adds separate command/query application files:
338
+ `application/service.go` OR `application/commands.go` plus
339
+ `application/queries.go` is mutually exclusive inside one module. Service and
340
+ CQRS can coexist across modules in the same modular monolith. The root package
341
+ contains only `composition.go`; inbound and outbound adapters are the
342
+ framework/database edges.
311
343
 
312
- ```text
313
- internal/app/order/
314
- ├── commands.go # command port + state-changing application handlers
315
- ├── queries.go # query port + read-only application handlers
316
- ├── service.go # compatibility facade; new wiring uses both handlers
317
- ├── composition.go # constructs command/query handlers separately
318
- └── cqrs_test.go # command/query boundary tests
319
- ```
344
+ Both shapes register the module in cmd/api/wiring.go, create a module-owned
345
+ PostgreSQL schema such as order_svc, append a timestamped create migration, and
346
+ record the resolved module profile in go-scaffold.config.json.
320
347
 
321
- `--cqrs` works with both minimal and `--full` modules. It keeps one modular
322
- monolith and one database by default; CQRS here means separate application
323
- paths, not mandatory separate databases, brokers, or event sourcing. The
324
- default remains the simpler layered module because an empty command/query
325
- split adds ceremony without a business reason.
326
-
327
- The default minimal mode scaffolds the same `model`/`errors`/`repository` (so `generate
328
- method` always has a full data-access surface to call), but `dto`/`service`/
329
- `handler` start empty — no default CRUD, no routes, just the plumbing
330
- (`Register()`, the `repository` interface, `wrapFindErr`) that `generate
331
- method` patches into. Use it when a domain doesn't need the full REST
332
- surface, or you'd rather add endpoints one at a time.
333
-
334
- Both modes also:
335
-
336
- - Register the module through its feature-local composition and the root
337
- registration markers in `cmd/api/wiring.go` — full wires an actual route,
338
- minimal wires an empty route group
339
- - Create the module's own Postgres schema (`<module>_svc`, e.g. `order_svc`)
340
- and add the model to the development schema bootstrap
341
- - Append `migrations/<timestamp>_create_<plural>.{up,down}.sql`, which creates that
342
- same schema for production
343
- - Record the resolved `minimal|crud` and `service|cqrs` choices in
344
- `go-scaffold.config.json`; changing project defaults does not rewrite existing modules
345
-
346
- What it does **not** do: invent your fields or wire foreign keys between
347
- domains — see `docs/architect/patterns.md` in the generated project for the
348
- conventions to follow by hand.
349
-
350
- ### `generate method <module> <name>` (alias `me`) — add one endpoint
348
+ The CLI creates the structural TODOs, not your domain rules. Add real model and
349
+ DTO fields, implement business behavior, and review the generated migration
350
+ before applying it.
351
351
 
352
- ```bash
352
+ ## generate method [module] [name] — add one endpoint
353
+
354
+ ~~~bash
353
355
  go-scaffold generate method orders approve --type patch
354
356
  go-scaffold generate method orders findByStatus --type get --get-mode one --field status
355
357
  go-scaffold g me orders findOverdue --type get --get-mode all
356
- ```
358
+ ~~~
357
359
 
358
- Patches an *existing* module's `handler.go`/`service.go` in place at the
359
- `// go-scaffold:*` markers never a whole new module. Never overwrites a method
360
- with the same name; pick a different one or the command errors.
361
- For a module generated with `--cqrs`, it also patches `commands.go` for
362
- state-changing endpoints and `queries.go` for read endpoints, while keeping
363
- the compatibility facade in sync.
360
+ If the module, method name, or endpoint details are omitted, the wizard asks
361
+ for them. The module selector lists modules that exist on disk, so the command
362
+ does not require memorising the normalised Go package name.
363
+
364
+ ### Method options
364
365
 
365
366
  | Option | Effect |
366
367
  |---|---|
367
- | `--type <get\|post\|put\|patch\|delete>` | HTTP verb |
368
- | `--get-mode <all\|one>` | For `get` only list-style vs. single-record lookup |
369
- | `--field <name>` | For `get --get-mode one` the lookup field (e.g. `email`, `status`); can't be `id` |
368
+ | --type <get\|post\|put\|patch\|delete> | HTTP verb |
369
+ | --get-mode <all\|one> | For GET only: list endpoint or single-record lookup |
370
+ | --field <name> | For get --get-mode one: lookup column such as email, status, or slug; id is reserved |
371
+
372
+ The generated route and code depend on the method type:
370
373
 
371
- | `--type` | Route | What's generated |
374
+ | Input | Route shape | Result |
372
375
  |---|---|---|
373
- | `get --get-mode all` | `GET /<plural>/<kebab-name>` | reuses `FindAll` TODO to add real filtering |
374
- | `get --get-mode one --field <f>` | `GET /<plural>/<f>/:<f>` | a real `FindBy<F>` query added to the repository (+ its interface + function-backed repository test stub) |
375
- | `post` | `POST /<plural>/<kebab-name>` | adds a body DTO; service is a TODO stub |
376
- | `put` / `patch` | `<VERB> /<plural>/:id/<kebab-name>` | finds by id, TODO before saving (safe no-op until implemented) |
377
- | `delete` | `DELETE /<plural>/:id/<kebab-name>` | TODO stub |
376
+ | get --get-mode all | GET /<plural>/<method> | Uses the module's list query; add real filtering yourself |
377
+ | get --get-mode one --field <field> | GET /<plural>/<field>/:<field> | Adds a FindBy<Field> query and a column/index migration |
378
+ | post | POST /<plural>/<method> | Adds a request body DTO and a TODO service method |
379
+ | put / patch | <VERB> /<plural>/:id/<method> | Loads by ID and leaves the update behavior as a TODO |
380
+ | delete | DELETE /<plural>/:id/<method> | Adds a delete endpoint stub |
378
381
 
379
- Business logic is always left as a `TODO`-marked stub that compiles and
380
- returns a clean `500` rather than inventing behavior — see
381
- `docs/architect/patterns.md` in the generated project.
382
+ For CQRS modules, GET methods are added to application/queries.go; other
383
+ methods are added to application/commands.go. Service modules use
384
+ application/service.go. Existing method names are never overwritten.
382
385
 
383
- When OpenAPI docs are enabled, `generate method` also creates a valid TODO stub
384
- under `docs/<plural>/methods/` and wires the route into `docs/openapi.yaml`.
385
- Replace its placeholder request/response schemas while implementing the TODO.
386
+ The generated business logic is deliberately a compiling TODO and returns a
387
+ clean not-implemented response until you implement it. If OpenAPI files were
388
+ enabled, a method document is also added under docs/<plural>/methods/ and
389
+ linked from docs/openapi.yaml.
386
390
 
387
- **Drift check** — `generate` type-checks the project (`go vet ./...`) before and
388
- after it writes. If the project was fine beforehand and the generated code
389
- doesn't compile, it stops with the compiler output instead of leaving you to
390
- find it later:
391
+ ## generate migration [name] reserve a migration pair
391
392
 
392
- ```text
393
- the generated code doesn't compile, but this project was fine a moment ago.
393
+ ~~~bash
394
+ go-scaffold generate migration add_status_to_orders
395
+ go-scaffold g mig add_status_to_orders
396
+ ~~~
394
397
 
395
- The most likely cause is drift: this project's internal/shared layer has been edited
396
- since it was scaffolded, so the templates this CLI emits no longer match it.
398
+ This creates:
397
399
 
398
- scaffolded with: go-scaffold <project-version>
399
- this CLI: go-scaffold <cli-version>
400
- ```
400
+ ~~~text
401
+ migrations/<timestamp>_add_status_to_orders.up.sql
402
+ migrations/<timestamp>_add_status_to_orders.down.sql
403
+ ~~~
401
404
 
402
- That happens because `generate`'s templates are written against the `shared/`
403
- layer `create` emits editing that layer is normal work, but it moves the
404
- project away from what this CLI's templates expect. `create` records its own
405
- version in `go-scaffold.config.json` so the message can name both sides. A
406
- project that was *already* broken (mid-refactor, or `go mod tidy` not run yet)
407
- is left alone — only a passed-before/broken-after transition is reported. No Go
408
- on `PATH` means the check is skipped.
405
+ Both files contain TODO comments. The CLI reserves the timestamp and filename;
406
+ you write the SQL and then apply it with:
409
407
 
410
- ### `generate migration <name>` (alias `mig`) — reserve a SQL migration pair
408
+ ~~~bash
409
+ make migrate-up
410
+ ~~~
411
411
 
412
- ```bash
413
- go-scaffold generate migration add_status_to_orders
414
- ```
412
+ ## config — configure future module defaults
415
413
 
416
- Creates timestamped `migrations/<version>_<name>.up.sql` and `.down.sql` TODO
417
- stubs. The CLI reserves the names; you own the SQL and should apply it with
418
- `make migrate-up` (or `migrate -path migrations -database "$DB_DSN" up`).
414
+ Run this from the generated project directory:
419
415
 
420
- ### `add worker` — add background job processing
416
+ ~~~bash
417
+ go-scaffold config # profile wizard
418
+ go-scaffold config show # print JSON; no wizard
419
+ go-scaffold config validate # validate; no file changes
420
+ ~~~
421
421
 
422
- ```bash
423
- go-scaffold add worker # asks where jobs should live
424
- go-scaffold add worker --queue postgres # River (default)
425
- go-scaffold add worker --queue redis # Asynq
426
- go-scaffold add worker --defaults # no prompt, Postgres
427
- ```
422
+ config changes defaults for future modules only. Existing modules keep their
423
+ recorded surface and application style.
428
424
 
429
- Adds `internal/platform/queue` (a backend-neutral contract plus one adapter),
430
- async email delivery, and `cmd/worker`.
425
+ ## add add optional project features
431
426
 
432
- | | `--queue postgres` (River) | `--queue redis` (Asynq) |
433
- |---|---|---|
434
- | Extra service to run | none | Redis |
435
- | Needed by `add auth` | no | no — `add auth --store` decides that separately |
436
- | Enqueue joins your DB transaction | yes | **no** — needs an outbox |
437
- | Inspect pending jobs | plain SQL | asynqmon |
438
-
439
- The default is Postgres because a job enqueued inside `tx.Do` is then only
440
- delivered if that transaction commits — no more welcome emails for signups
441
- that rolled back. Run `make river-migrate` once per database to create
442
- River's tables, then `make worker`.
443
-
444
- Application code only ever sees `queue.Job`, `queue.Enqueuer` and
445
- `queue.Handler` — no backend package appears outside its own adapter file, so
446
- switching later means writing one adapter, not touching every module that
447
- enqueues something.
448
-
449
- ```go
450
- type WelcomeEmail struct{ To string `json:"to"` }
451
- func (WelcomeEmail) Kind() string { return "email:welcome" }
452
-
453
- // cmd/api — the job is discarded with the transaction if this fails
454
- tx.Do(ctx, db, func(ctx context.Context) error {
455
- if err := repo.Create(ctx, u); err != nil { return err }
456
- return jobs.Enqueue(ctx, WelcomeEmail{To: u.Email}, nil)
457
- })
458
- ```
427
+ Running go-scaffold add opens a feature wizard. It shows features already
428
+ installed as unavailable and explains that RBAC requires auth. Direct feature
429
+ commands are useful for scripts and explicit usage.
459
430
 
460
- ### `add auth` add email/password authentication
431
+ After an incremental feature is added, the CLI refreshes the generated
432
+ README and architect docs to reflect the resolved configuration. It only does
433
+ so when each file still matches the version it would have generated; edited
434
+ docs are preserved and reported for manual maintenance.
461
435
 
462
- ```bash
463
- go-scaffold add auth # asks token store + browser topology, then confirms
464
- go-scaffold add auth --store postgres # tokens in Postgres, no extra service
465
- go-scaffold add auth --store redis # tokens in Redis, exact across replicas
466
- go-scaffold add auth --defaults # Postgres + local same-site topology (CI/scripting)
467
- go-scaffold add auth --browser-topology cross-site --yes
468
- ```
436
+ ### add worker — background jobs and mail
437
+
438
+ ~~~bash
439
+ go-scaffold add worker # queue wizard + confirmation
440
+ go-scaffold add worker --queue postgres # River in PostgreSQL
441
+ go-scaffold add worker --queue redis # Asynq in Redis
442
+ go-scaffold add worker --defaults # PostgreSQL/River, no prompts
443
+ go-scaffold add worker --queue redis --yes # skip confirmation
444
+ ~~~
445
+
446
+ The command adds internal/platform/queue, mail delivery, and cmd/worker.
469
447
 
470
- Adds JWT access tokens, refresh-token rotation with reuse detection,
471
- registration/login/logout, password reset, email verification, failed-login
472
- lockout, and generic provider OAuth routes (Google is the first adapter). Apply
473
- the generated migrations. Development may bootstrap tables for convenience;
474
- production must run `migrate up` first.
475
-
476
- The browser frontend owns its single provider callback route. It generates
477
- `state` and an S256 PKCE verifier/challenge, starts
478
- `GET /auth/{provider}/login`, handles both success and provider-cancel/error
479
- responses in that route, then sends `code`, `state`, and `code_verifier` to
480
- `POST /auth/{provider}/exchange`. The API uses the exact
481
- `GOOGLE_OAUTH_REDIRECT_URI` registered with the provider, creates the local
482
- session, sets the HttpOnly refresh cookie, and returns JSON. The backend also
483
- consumes a one-time transaction binding provider, state, S256 challenge, and
484
- OIDC nonce before completing the exchange. It never accepts a
485
- request-supplied `redirect_uri`/`return_to`, redirects to a configured frontend
486
- URL, or places tokens/code/state in a URI. Native/mobile flow is out of scope
487
- for this scaffold phase.
488
-
489
- `AUTH_BROWSER_TOPOLOGY` is only the cookie/CORS deployment policy, separate
490
- from the provider redirect URI. For a genuinely cross-site frontend use
491
- `--browser-topology cross-site`, deploy over HTTPS, set
492
- `COOKIE_SAMESITE=none` and `COOKIE_SECURE=true`, and add the exact frontend
493
- origin to `CORS_ALLOWED_ORIGINS` separately. SameSite=None requests also pass
494
- an exact Origin guard because CORS alone is not CSRF protection. Token
495
- responses use `Cache-Control: no-store` and `Pragma: no-cache`; configure
496
- `JWT_REFRESH_MAX_TTL_MIN` so refresh rotation cannot extend beyond its absolute
497
- lifetime.
498
-
499
- No prerequisites. On a project with no worker, the registration-verification,
500
- resend-verification, and password-reset mail flows are sent inline, and `add
501
- worker` later moves them onto the queue for you. Until then, those auth flows
502
- block on SMTP when a mail server is configured.
503
-
504
- | `--store` | Refresh/recovery tokens and rate-limit counters | Extra service |
448
+ | Queue option | Storage | Operational note |
505
449
  |---|---|---|
506
- | `postgres` (default) | `user_svc.auth_tokens`, counters in-process | none |
507
- | `redis` | refresh + rate-limit counters in Redis; recovery in `user_svc.auth_tokens` | Redis |
450
+ | postgres (River) | Project PostgreSQL database | No extra service; enqueueing can join the database transaction |
451
+ | redis (Asynq) | Redis | Run Redis separately; enqueueing cannot join a PostgreSQL transaction |
508
452
 
509
- The rate limiter follows the store rather than being chosen separately, because
510
- "I want this exact across replicas" is one decision. With `postgres` the per-IP
511
- budget is per-replica; the failed-login lockout is in Postgres either way, since
512
- that one can't be approximate.
453
+ For River:
513
454
 
514
- ### `add rbac` — add roles and permissions
455
+ ~~~bash
456
+ make river-migrate
457
+ make river-migrate-test # before required real-store tests
458
+ make worker
459
+ ~~~
515
460
 
516
- ```bash
517
- go-scaffold add rbac
518
- go-scaffold generate module secrets --auth --permission secret:manage
519
- ```
461
+ For Redis, start Redis first and then run make worker. make dev runs the API and
462
+ worker together after worker support has been added.
520
463
 
521
- Requires `add auth`. Adds role/permission administration, cached authorization
522
- middleware, and role assignment. Its migration seeds the default roles and
523
- permissions, so apply it with `migrate up`; table creation does not run SQL
524
- seed statements.
464
+ ### add auth email/password and provider auth
525
465
 
526
- ### `add observability` — add metrics + tracing
466
+ ~~~bash
467
+ go-scaffold add auth # store + browser topology wizard
468
+ go-scaffold add auth --store postgres # no extra service
469
+ go-scaffold add auth --store redis # Redis for shared refresh state
470
+ go-scaffold add auth --browser-topology same-origin
471
+ go-scaffold add auth --browser-topology same-site
472
+ go-scaffold add auth --browser-topology cross-site
473
+ go-scaffold add auth --defaults # Postgres + same-site defaults
474
+ ~~~
527
475
 
528
- ```bash
529
- go-scaffold add observability # on an existing project
530
- go-scaffold create my-api --defaults --observability # or at creation time
531
- ```
476
+ Auth adds:
532
477
 
533
- Adds Prometheus metrics at `/metrics` and OpenTelemetry tracing for Gin +
534
- GORM, patched into `cmd/api/wiring.go` and `internal/platform/database` the same
535
- way `add worker`/`add auth`/`add rbac` patch an existing project. Tracing is
536
- disabled until `OTEL_EXPORTER_OTLP_ENDPOINT` is configured; `/metrics` works
537
- either way. `create --observability` is exactly this command run right after
538
- scaffolding the two produce the same project.
478
+ - JWT access tokens and refresh-token rotation with reuse detection
479
+ - registration, login, logout, refresh, password reset, and email verification
480
+ - generic provider OAuth routes, with Google as the first adapter
481
+ - failed-login lockout and user-session management
482
+ - MFA endpoints and configuration hooks
483
+ - internal/app/user, auth middleware, cmd/seed, migrations, and OpenAPI
484
+ documents when OpenAPI is enabled
539
485
 
540
- ### `undo module <name>` (alias `undo m`) take back a `generate module`
486
+ The --store choice controls refresh-token storage and rate-limit counters:
541
487
 
542
- ```bash
543
- go-scaffold undo module orders # confirms first
544
- go-scaffold undo m orders --yes # skip the confirm
545
- ```
488
+ | --store | Refresh/recovery token storage | Extra service |
489
+ |---|---|---|
490
+ | postgres (default) | PostgreSQL; rate-limit counters are in-process | None |
491
+ | redis | Refresh state and rate-limit counters in Redis; recovery remains in PostgreSQL | Redis |
546
492
 
547
- The inverse of `generate module`, for the case it's actually the inverse of:
548
- a module you didn't mean to generate — a typo'd name, a domain you decided
549
- against. It deletes `internal/app/<name>/`, the per-module docs folder, **and
550
- the module's migration files**, and reverses the import/bootstrap/route in
551
- `cmd/api/wiring.go` plus the paths/schemas in `docs/openapi.yaml`. Restores the
552
- `_ = api` placeholder if it was the last module, so the project still builds.
553
-
554
- Deleting the migrations is the point. `migrations/embed.go` is a `//go:embed
555
- *`, so a typo'd `create_oders` left behind once ran on every database created
556
- from then on. That's only safe while those files exist nowhere but your
557
- working tree, so `undo` proves it first and refuses loudly otherwise:
558
-
559
- - **any of them is tracked by git** — it may already have been pulled or
560
- deployed somewhere, so nothing is deleted. Retire that domain the explicit
561
- way instead: `go-scaffold generate migration drop_<name>`.
562
- - **your database is already at or past that version** (read via the `migrate`
563
- CLI when it's on `PATH` and a DSN is configured) deleting the files would
564
- strand `schema_migrations` at a version with no migration behind it. Run
565
- `migrate ... down` first, then try again.
566
-
567
- The table itself is never dropped either way — `undo` only reverses what the
568
- CLI wrote. Prefer it to hand-deleting the folder: it also un-wires
569
- `cmd/api/wiring.go`, `.golangci.yml` and the OpenAPI index, and it refuses when
570
- another domain still imports this one rather than leaving you an un-compilable
571
- project.
572
-
573
- ## Why no per-domain versioning
574
-
575
- Earlier versions of this CLI let a domain live in a `v1/`/`v2/` folder with
576
- its own route group and import alias, so the same domain name could exist
577
- twice with different behavior. It was cut: the migration (and usually the
578
- DB table) is shared between "versions" of the same domain, but each version
579
- got its own physically-copied `model.go` nothing stopped the two structs
580
- from drifting apart. Verified against a real Postgres instance:
581
- development schema bootstrapping silently accepted a column typed `int` in
582
- one version's model and `float64` in the other for the *same* column, converging it to
583
- `numeric` with no error — the two versions would then read/write the same
584
- data with different, silently incompatible interpretations.
585
-
586
- Instead, every route in a project is grouped under a single project-wide
587
- `--api-prefix` chosen once at `create` time — opt-in, no prefix unless you ask. A domain that
588
- needs a real breaking change gets a new domain package, or a new field on
589
- the existing DTO — not a duplicated model pointed at a table it can drift
590
- out of sync with.
591
-
592
- ## Project structure produced by `create`
593
-
594
- ```text
595
- cmd/api/
596
- ├── main.go # process entry point and exit handling
597
- └── wiring.go # composition root: infrastructure + domain registration
598
- internal/
599
- ├── platform/database/
600
- ├── shared/{config,apperror,dberr,httpx,id,middleware,pagination,tx}/
601
- └── app/ # empty until you `generate module`
602
- docs/
603
- ├── architect/{architecture,patterns,techstack}.md
604
- └── openapi.yaml + common/ + health/ # if openapi docs enabled
605
- migrations/
606
- .github/workflows/ci.yml # build, vet, gofmt check, golangci-lint, go test (with a Postgres service)
607
- Makefile
608
- .env.example
609
- .gitignore
610
- .golangci.yml
611
- redocly.yaml # if openapi docs enabled
612
- docker-compose.yml # if Docker enabled
613
- .vscode/settings.json
614
- README.md
615
- AGENTS.md
616
- CLAUDE.md
617
- .claude/skills/go-scaffold/SKILL.md
618
- go-scaffold.config.json
619
- ```
493
+ --browser-topology describes how a browser frontend and API are deployed:
494
+
495
+ | Topology | Typical setup |
496
+ |---|---|
497
+ | same-origin | Same scheme, host, and port |
498
+ | same-site (default) | Different origin on the same site, such as localhost:3000 and localhost:8080 |
499
+ | cross-site | Different sites; use HTTPS, SameSite=None, secure cookies, and exact CORS origins |
500
+
501
+ After adding auth:
502
+
503
+ ~~~bash
504
+ go mod tidy
505
+ make migrate-up
506
+ SEED_ADMIN_EMAIL=admin@example.com SEED_ADMIN_PASSWORD='change-me' make seed
507
+ ~~~
508
+
509
+ Without add worker, verification and password-reset mail is sent inline. If a
510
+ worker is already installed, those jobs use the queue instead.
511
+
512
+ ### add rbac — roles and permissions
513
+
514
+ ~~~bash
515
+ go-scaffold add rbac
516
+ go-scaffold add rbac --yes
517
+ go-scaffold generate module secrets --profile lean --auth --permission secret:manage
518
+ ~~~
519
+
520
+ RBAC requires add auth first. It adds:
521
+
522
+ - role and permission administration
523
+ - cached authorization middleware
524
+ - role assignment endpoints
525
+ - the internal/app/role module and its migration
526
+
527
+ Apply the migration before using the seeded roles and permissions:
528
+
529
+ ~~~bash
530
+ make migrate-up
531
+ SEED_ADMIN_EMAIL=admin@example.com SEED_ADMIN_PASSWORD='change-me' make seed
532
+ ~~~
533
+
534
+ ### add observability metrics and tracing
535
+
536
+ ~~~bash
537
+ go-scaffold add observability
538
+ go-scaffold add observability --yes
539
+ go-scaffold create my-api --defaults --observability
540
+ ~~~
541
+
542
+ This adds Prometheus GET /metrics and OpenTelemetry tracing for Gin and GORM.
543
+ Run go mod tidy after adding it. Metrics work immediately; set
544
+ OTEL_EXPORTER_OTLP_ENDPOINT when you want to export traces.
545
+
546
+ create --observability and create followed by add observability produce the same
547
+ feature configuration.
548
+
549
+ ## undo module [name] — remove a generated module
550
+
551
+ ~~~bash
552
+ go-scaffold undo module orders # asks for confirmation
553
+ go-scaffold undo m orders --yes # skip confirmation
554
+ go-scaffold undo # choose the module in a wizard
555
+ ~~~
556
+
557
+ undo module removes the module package, its generated OpenAPI folder, owned
558
+ migrations, configuration entry, and wiring from cmd/api/wiring.go. It does not
559
+ drop a database table.
560
+
561
+ The command is intentionally conservative: it refuses when the module's
562
+ migrations may already have been shared or applied, or when another module
563
+ still depends on it. Use an explicit hand-written migration to retire a domain
564
+ that is already in use.
565
+
566
+ ## Generated project structure
567
+
568
+ After go-scaffold create my-api, the important runtime structure is:
569
+
570
+ ~~~text
571
+ my-api/
572
+ ├── cmd/
573
+ │ └── api/
574
+ │ ├── main.go # process entrypoint and shutdown
575
+ │ └── wiring.go # infrastructure and module composition
576
+ ├── internal/
577
+ │ ├── platform/
578
+ │ │ └── database/ # GORM connection and pool
579
+ │ ├── shared/
580
+ │ │ ├── config/ # environment configuration
581
+ │ │ ├── apperror/ # consistent application errors
582
+ │ │ ├── dberr/ # database error classification
583
+ │ │ ├── httpx/ # HTTP parsing and binding helpers
584
+ │ │ ├── id/ # UUID generation
585
+ │ │ ├── middleware/ # request ID, logging, errors, CORS
586
+ │ │ ├── pagination/ # pagination parsing and responses
587
+ │ │ └── tx/ # transaction context helpers
588
+ │ └── app/ # empty until generate module is used
589
+ ├── migrations/ # embedded, versioned SQL migrations
590
+ ├── docs/
591
+ │ ├── architect/ # architecture, patterns, tech stack
592
+ │ └── openapi.yaml # optional API index and referenced files
593
+ ├── go-scaffold.config.json # CLI defaults, features, and module choices
594
+ ├── go.mod
595
+ ├── Makefile
596
+ ├── .env.example
597
+ ├── Dockerfile
598
+ └── docker-compose.yml # optional, when Docker was selected
599
+ ~~~
600
+
601
+ Optional commands add these areas:
602
+
603
+ ~~~text
604
+ add worker -> internal/platform/{queue,mail}/ and cmd/worker/
605
+ add auth -> internal/app/user/, auth middleware, and cmd/seed/
606
+ add rbac -> internal/app/role/ and authorization middleware
607
+ add observability -> internal/platform/telemetry/ and metrics/tracing middleware
608
+ ~~~
609
+
610
+ After generate module orders, the domain sits behind its own package boundary:
611
+
612
+ ~~~text
613
+ internal/app/order/
614
+ ├── domain/ # entities, invariants, and domain errors
615
+ ├── ports/ # consumer-owned application dependencies
616
+ ├── application/ # service OR commands + queries, plus DTOs/tests
617
+ ├── adapters/inbound/http/ # Gin delivery adapter and tests
618
+ ├── adapters/outbound/postgres/ # persistence model, adapter, and tests
619
+ └── composition.go # module-local dependency wiring
620
+ ~~~
621
+
622
+ The generated service and CQRS styles are exclusive within a module: a
623
+ service module has `application/service.go`; a CQRS module has
624
+ `application/commands.go` and `application/queries.go` and no service facade.
625
+ Different modules may choose different styles. `go-scaffold check` enforces the
626
+ physical layout and dependency direction.
627
+
628
+ The module name orders produces the Go package order, REST collection /orders,
629
+ plural table names such as order_items, and a module-owned schema such as
630
+ order_svc.
620
631
 
621
- ## Supported stack
632
+ ## Useful commands after scaffolding
622
633
 
623
- Pinned base dependencies in the generated `go.mod` — this table mirrors
624
- `templates/create/base/go.mod.hbs`, which is the source of truth. `add auth`,
625
- `add worker`, and `add observability` append their optional dependencies.
634
+ Run these from the generated project directory:
626
635
 
627
- | Package | Version |
636
+ | Command | Purpose |
628
637
  |---|---|
629
- | Gin | v1.10.1 |
630
- | GORM + postgres driver | v1.31.2 / v1.6.2 |
631
- | validator/v10 | v10.30.3 |
632
- | google/uuid | v1.6.0 |
638
+ | make docker-up | Start the generated local PostgreSQL service |
639
+ | make docker-down | Stop the generated local services |
640
+ | make db-create | Create the project database; safe to run again |
641
+ | make run | Run cmd/api |
642
+ | make river-migrate | Apply River's own schema when the worker uses Postgres/River |
643
+ | make river-migrate-test | Apply River's own schema to TEST_DB_DSN for worker integration tests |
644
+ | make build | Build cmd/api into bin/api |
645
+ | make test | Run Go tests |
646
+ | make fmt | Format Go code |
647
+ | make vet | Run go vet ./... |
648
+ | make tidy | Run go mod tidy |
649
+ | make migrate-up | Apply migrations from migrations/ |
650
+ | make migrate-down | Roll back one migration |
651
+ | make migrate-verify | Check that migrations can roll forward and back |
652
+ | make openapi-bundle | Bundle OpenAPI $ref files when OpenAPI is enabled |
653
+
654
+ make run, make test, and migration targets load .env when it exists. Copy
655
+ .env.example to .env and adjust DB_DSN, PORT, CORS, mail, auth, or telemetry
656
+ settings as needed.
657
+
658
+ ## What to edit after generation
659
+
660
+ The CLI gives you a compiling structure and explicit TODOs. Your application
661
+ still owns:
662
+
663
+ - domain fields and invariants in `domain/entity.go`
664
+ - request/use-case and response fields in `application/dto.go`
665
+ - persistence fields and mapping in `adapters/outbound/postgres/model.go`
666
+ - business rules in `application/service.go` or command/query handlers
667
+ - SQL bodies in generated migration files
668
+ - OpenAPI schemas and method descriptions
669
+ - tests for the behavior you add
670
+
671
+ Use go-scaffold generate method for the repetitive endpoint shape, then fill in
672
+ the generated TODO before treating the endpoint as production behavior.
673
+
674
+ ## Architecture and migration note
675
+
676
+ Schema version 2 is the canonical hexagonal split layout. Every generated
677
+ feature, including auth and RBAC, uses `domain/`, `application/`, `ports/`,
678
+ `adapters/`, and a feature-local `composition.go`; one canonical implementation
679
+ tree is generated. Projects with the old schema 1 manifest are
680
+ rejected with a migration hint instead of silently generating a conflicting
681
+ architecture.
682
+
683
+ The generated tree has no `compat/` directory and no feature-level `model/`
684
+ directory. The word “compatibility” below refers only to CLI command aliases:
685
+ the hidden `remove module` alias remains supported for old scripts, while new
686
+ commands should use `go-scaffold undo module`.
633
687
 
634
688
  ## License
635
689