@nakedev/go-scaffold 0.4.0 → 0.5.0

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 (192) hide show
  1. package/README.md +598 -306
  2. package/dist/commands/auth.js +65 -23
  3. package/dist/commands/check.js +281 -0
  4. package/dist/commands/config.js +50 -0
  5. package/dist/commands/create.js +33 -2
  6. package/dist/commands/generate.js +29 -3
  7. package/dist/commands/method.js +74 -63
  8. package/dist/commands/migration.js +2 -2
  9. package/dist/commands/observability.js +4 -53
  10. package/dist/commands/rbac.js +21 -10
  11. package/dist/commands/undo.js +11 -3
  12. package/dist/commands/worker.js +15 -5
  13. package/dist/index.js +198 -59
  14. package/dist/prompts/auth-wizard.js +40 -6
  15. package/dist/prompts/create-wizard.js +42 -1
  16. package/dist/prompts/generate-wizard.js +89 -9
  17. package/dist/templates/auth-manifest.js +50 -19
  18. package/dist/templates/create-manifest.js +8 -0
  19. package/dist/templates/module-manifest.js +84 -26
  20. package/dist/templates/rbac-manifest.js +16 -11
  21. package/dist/templates/worker-manifest.js +4 -1
  22. package/dist/types.js +8 -0
  23. package/dist/utils/auth-patcher.js +124 -33
  24. package/dist/utils/config.js +167 -4
  25. package/dist/utils/docs-patcher.js +68 -0
  26. package/dist/utils/hexagonal-method-patcher.js +334 -0
  27. package/dist/utils/main-patcher.js +32 -30
  28. package/dist/utils/marker-patch.js +7 -1
  29. package/dist/utils/module-location.js +17 -11
  30. package/dist/utils/module-profile.js +32 -0
  31. package/dist/utils/platform-patcher.js +56 -7
  32. package/dist/utils/rbac-patcher.js +89 -210
  33. package/package.json +7 -2
  34. package/templates/add/auth/cmd/seed/main.go.hbs +15 -3
  35. package/templates/add/auth/docs/login.yaml.hbs +11 -1
  36. package/templates/add/auth/docs/mfa-verify.yaml.hbs +19 -0
  37. package/templates/add/auth/docs/provider-exchange.yaml.hbs +40 -0
  38. package/templates/add/auth/docs/provider-login.yaml.hbs +31 -0
  39. package/templates/add/auth/docs/refresh.yaml.hbs +7 -0
  40. package/templates/add/auth/docs/register.yaml.hbs +7 -0
  41. package/templates/add/auth/docs/reset-password.yaml.hbs +1 -1
  42. package/templates/add/auth/docs/schemas.yaml.hbs +59 -1
  43. package/templates/add/auth/docs/users-me-mfa-confirm.yaml.hbs +19 -0
  44. package/templates/add/auth/docs/users-me-mfa-disable.yaml.hbs +15 -0
  45. package/templates/add/auth/docs/users-me-mfa-setup.yaml.hbs +14 -0
  46. package/templates/add/auth/docs/users-me-mfa.yaml.hbs +12 -0
  47. package/templates/add/auth/internal/app/user/adapters/inbound/http/browser_policy.go.hbs +98 -0
  48. package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
  49. package/templates/add/auth/internal/app/user/adapters/inbound/http/handler.go.hbs +228 -0
  50. package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_local.go.hbs +76 -0
  51. package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_mfa.go.hbs +83 -0
  52. package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_oauth.go.hbs +70 -0
  53. package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_recovery.go.hbs +49 -0
  54. package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_test.go.hbs +311 -0
  55. package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_user.go.hbs +41 -0
  56. package/templates/add/auth/internal/app/user/adapters/inbound/http/session_cookie.go.hbs +35 -0
  57. package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs +35 -0
  58. package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs +20 -0
  59. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/mfa_store.go.hbs +129 -0
  60. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/mfa_store_test.go.hbs +174 -0
  61. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +84 -0
  62. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +211 -0
  63. package/templates/add/auth/internal/app/user/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +18 -19
  64. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg.go.hbs +213 -0
  65. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg_test.go.hbs +103 -0
  66. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_recovery.go.hbs +84 -0
  67. package/templates/add/auth/internal/app/user/adapters/outbound/redis/tokenstore.go.hbs +228 -0
  68. package/templates/add/auth/internal/app/user/adapters/outbound/redis/tokenstore_test.go.hbs +196 -0
  69. package/templates/add/auth/internal/app/user/application/contracts.go.hbs +52 -0
  70. package/templates/add/auth/internal/app/user/application/dto.go.hbs +75 -0
  71. package/templates/add/auth/internal/app/user/application/errors.go.hbs +62 -0
  72. package/templates/add/auth/internal/app/user/application/external_login.go.hbs +198 -0
  73. package/templates/add/auth/internal/app/user/application/jwt.go.hbs +58 -0
  74. package/templates/add/auth/internal/app/user/application/local_auth.go.hbs +96 -0
  75. package/templates/add/auth/internal/app/user/application/mfa_service.go.hbs +449 -0
  76. package/templates/add/auth/internal/app/user/application/mfa_service_test.go.hbs +200 -0
  77. package/templates/add/auth/internal/app/user/application/oauth.go.hbs +132 -0
  78. package/templates/add/auth/internal/app/user/application/provider_test.go.hbs +285 -0
  79. package/templates/add/auth/internal/app/user/application/recovery.go.hbs +82 -0
  80. package/templates/add/auth/internal/app/user/application/recovery_service.go.hbs +112 -0
  81. package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
  82. package/templates/add/auth/internal/app/user/application/service_test.go.hbs +891 -0
  83. package/templates/add/auth/internal/app/user/application/sessions.go.hbs +99 -0
  84. package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
  85. package/templates/add/auth/internal/app/user/application/user_query.go.hbs +65 -0
  86. package/templates/add/auth/internal/app/user/composition.go.hbs +168 -0
  87. package/templates/add/auth/internal/app/user/domain/entity.go.hbs +41 -0
  88. package/templates/add/auth/internal/app/user/domain/errors.go.hbs +32 -0
  89. package/templates/add/auth/internal/app/user/ports/password.go.hbs +9 -0
  90. package/templates/add/auth/internal/app/user/ports/repository.go.hbs +90 -0
  91. package/templates/add/auth/internal/platform/authprovider/google/google.go.hbs +389 -0
  92. package/templates/add/auth/internal/platform/authprovider/google/google_test.go.hbs +312 -0
  93. package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +10 -5
  94. package/templates/add/auth/migrations/create_identities.up.sql.hbs +1 -1
  95. package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +1 -1
  96. package/templates/add/auth/migrations/create_mfa.down.sql.hbs +3 -0
  97. package/templates/add/auth/migrations/create_mfa.up.sql.hbs +29 -0
  98. package/templates/add/auth/migrations/create_users.up.sql.hbs +4 -3
  99. package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs +142 -0
  100. package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs +19 -0
  101. package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs +48 -0
  102. package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs +127 -0
  103. package/templates/add/rbac/internal/app/role/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +8 -8
  104. package/templates/add/rbac/internal/app/role/application/dto.go.hbs +47 -0
  105. package/templates/add/rbac/internal/app/role/application/errors.go.hbs +19 -0
  106. package/templates/add/rbac/internal/app/role/application/service.go.hbs +157 -0
  107. package/templates/add/rbac/internal/app/role/{service_test.go.hbs → application/service_test.go.hbs} +26 -19
  108. package/templates/add/rbac/internal/app/role/composition.go.hbs +48 -0
  109. package/templates/add/rbac/internal/app/role/domain/entity.go.hbs +23 -0
  110. package/templates/add/rbac/internal/app/role/domain/errors.go.hbs +26 -0
  111. package/templates/add/rbac/internal/app/role/ports/repository.go.hbs +25 -0
  112. package/templates/add/rbac/migrations/add_roles.down.sql.hbs +3 -11
  113. package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -6
  114. package/templates/add/worker/internal/platform/queue/river_test.go.hbs +84 -0
  115. package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +358 -121
  116. package/templates/create/base/.env.example.hbs +0 -1
  117. package/templates/create/base/.golangci.yml.hbs +2 -2
  118. package/templates/create/base/AGENTS.md.hbs +279 -67
  119. package/templates/create/base/Makefile.hbs +2 -1
  120. package/templates/create/base/README.md.hbs +115 -32
  121. package/templates/create/base/cmd/api/wiring.go.hbs +13 -9
  122. package/templates/create/base/internal/composition/doc.go.hbs +7 -0
  123. package/templates/create/base/internal/platform/database/database.go.hbs +3 -3
  124. package/templates/create/base/internal/shared/apperror/apperror.go.hbs +15 -2
  125. package/templates/create/base/internal/shared/config/config.go.hbs +0 -8
  126. package/templates/create/base/internal/shared/middleware/cors_test.go.hbs +40 -0
  127. package/templates/create/base/internal/shared/middleware/error.go.hbs +15 -5
  128. package/templates/create/features/docs/architecture.md.hbs +92 -32
  129. package/templates/create/features/docs/patterns.md.hbs +137 -91
  130. package/templates/create/features/docs/techstack.md.hbs +18 -3
  131. package/templates/generate/module/hexagonal/adapters/inbound/http/dto.go.hbs +45 -0
  132. package/templates/generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs +28 -0
  133. package/templates/generate/module/hexagonal/adapters/inbound/http/handler.go.hbs +182 -0
  134. package/templates/generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs +83 -0
  135. package/templates/generate/module/hexagonal/adapters/inbound/http/handler_crud_test.go.hbs +18 -0
  136. package/templates/generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs +30 -0
  137. package/templates/generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs +37 -0
  138. package/templates/generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs +95 -0
  139. package/templates/generate/module/{repository_test.go.hbs → hexagonal/adapters/outbound/postgres/repository_test.go.hbs} +8 -8
  140. package/templates/generate/module/hexagonal/application/commands.crud.go.hbs +54 -0
  141. package/templates/generate/module/hexagonal/application/commands.go.hbs +25 -0
  142. package/templates/generate/module/hexagonal/application/cqrs_test.go.hbs +66 -0
  143. package/templates/generate/module/hexagonal/application/dto.go.hbs +35 -0
  144. package/templates/generate/module/hexagonal/application/dto.minimal.go.hbs +25 -0
  145. package/templates/generate/module/hexagonal/application/queries.crud.go.hbs +33 -0
  146. package/templates/generate/module/hexagonal/application/queries.go.hbs +25 -0
  147. package/templates/generate/module/hexagonal/application/service.crud.go.hbs +73 -0
  148. package/templates/generate/module/hexagonal/application/service.go.hbs +29 -0
  149. package/templates/generate/module/hexagonal/application/service_test.go.hbs +62 -0
  150. package/templates/generate/module/hexagonal/composition.go.hbs +27 -0
  151. package/templates/generate/module/hexagonal/domain/entity.go.hbs +20 -0
  152. package/templates/generate/module/hexagonal/domain/errors.go.hbs +11 -0
  153. package/templates/generate/module/hexagonal/ports/repository.go.hbs +38 -0
  154. package/templates/generate/module/migration.up.sql.hbs +1 -1
  155. package/dist/utils/method-patcher.js +0 -357
  156. package/templates/add/auth/docs/google-callback.yaml.hbs +0 -22
  157. package/templates/add/auth/docs/google-login.yaml.hbs +0 -7
  158. package/templates/add/auth/internal/app/user/dto.go.hbs +0 -77
  159. package/templates/add/auth/internal/app/user/errors.go.hbs +0 -43
  160. package/templates/add/auth/internal/app/user/handler.go.hbs +0 -276
  161. package/templates/add/auth/internal/app/user/jwt.go.hbs +0 -108
  162. package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -39
  163. package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -31
  164. package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
  165. package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -30
  166. package/templates/add/auth/internal/app/user/repository.go.hbs +0 -137
  167. package/templates/add/auth/internal/app/user/service.go.hbs +0 -531
  168. package/templates/add/auth/internal/app/user/service_test.go.hbs +0 -316
  169. package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -30
  170. package/templates/add/auth/internal/app/user/tokenstore_pg.go.hbs +0 -144
  171. package/templates/add/auth/internal/app/user/tokenstore_redis.go.hbs +0 -147
  172. package/templates/add/rbac/internal/app/role/dto.go.hbs +0 -45
  173. package/templates/add/rbac/internal/app/role/errors.go.hbs +0 -39
  174. package/templates/add/rbac/internal/app/role/handler.go.hbs +0 -104
  175. package/templates/add/rbac/internal/app/role/model/permission.go.hbs +0 -12
  176. package/templates/add/rbac/internal/app/role/model/role.go.hbs +0 -22
  177. package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +0 -11
  178. package/templates/add/rbac/internal/app/role/repository.go.hbs +0 -97
  179. package/templates/add/rbac/internal/app/role/service.go.hbs +0 -217
  180. package/templates/generate/module/dto.go.hbs +0 -36
  181. package/templates/generate/module/errors.go.hbs +0 -33
  182. package/templates/generate/module/handler.go.hbs +0 -134
  183. package/templates/generate/module/handler_test.go.hbs +0 -174
  184. package/templates/generate/module/minimal/dto.go.hbs +0 -28
  185. package/templates/generate/module/minimal/handler.go.hbs +0 -48
  186. package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
  187. package/templates/generate/module/minimal/service.go.hbs +0 -45
  188. package/templates/generate/module/minimal/service_test.go.hbs +0 -77
  189. package/templates/generate/module/model/model.go.hbs +0 -36
  190. package/templates/generate/module/repository.go.hbs +0 -103
  191. package/templates/generate/module/service.go.hbs +0 -108
  192. package/templates/generate/module/service_test.go.hbs +0 -161
@@ -3,140 +3,377 @@ name: go-scaffold
3
3
  description: >
4
4
  Use whenever the user asks to add, create, scaffold, wire up, or generate a
5
5
  new domain/feature/resource OR a new endpoint/method in THIS project —
6
- including indirect or paraphrased requests that imply new backend surface
7
- area without saying "module"/"method" literally, e.g. "add a products
8
- feature", "let admins approve orders", "we need an endpoint for X", "look
9
- up a user by email". This project was scaffolded by go-scaffold and has a
10
- go-scaffold.config.json at its root check for that file before assuming
11
- this applies. Do NOT use for: business logic inside an already-generated
12
- method body, model/DTO field edits, foreign keys/relations between
13
- domains, bug fixes, refactors of existing code, or any project that lacks
14
- go-scaffold.config.json at its root.
6
+ including indirect requests such as "add products", "let admins approve
7
+ orders", "add an endpoint for X", or "look up a user by email". This project
8
+ was scaffolded by go-scaffold and has a go-scaffold.config.json at its root;
9
+ check for that file before assuming this applies. Use the generated
10
+ modular/Hexagonal boundaries and runtime safety contract described below.
11
+ Do NOT use for business logic inside an existing method, model/DTO field
12
+ edits, foreign keys/relations, bug fixes, refactors, or a project without
13
+ go-scaffold.config.json.
15
14
  ---
16
15
 
17
16
  # go-scaffold
18
17
 
19
- This project's domain modules (model, dto, errors, repository, service,
20
- handler) and their endpoints are generated with the `go-scaffold` CLI — do
21
- not hand-write a new one. Hand-writing a new `internal/app/<name>/` package,
22
- or adding a method by editing the handler/service directly, produces a shape
23
- that doesn't match the rest of the codebase (missing route registration,
24
- missing AutoMigrate wiring, a `repository` interface out of sync with its
25
- `repositoryStub` test mock, inconsistent error-catalog naming, etc.).
18
+ Use this skill for generated backend surface only. A new module or endpoint
19
+ must be created with the CLI so route registration, feature-local composition,
20
+ application seams, test stubs, OpenAPI TODOs, and migration markers remain
21
+ consistent. After generation, the product's business logic and domain rules
22
+ are still the engineer's responsibility.
23
+
24
+ This file is emitted at `.claude/skills/go-scaffold/SKILL.md` for Claude Code.
25
+ Agents that do not load `.claude/skills` should follow the root `AGENTS.md`,
26
+ which carries the same project-wide safety contract.
26
27
 
27
28
  ## When to use this skill
28
29
 
29
- - Any request for a new feature/resource/domain, even named informally
30
- ("add invoices", "we need a way to track X")
31
- - Any request for a new endpoint on an existing feature, even phrased as a
32
- capability ("let users do X", "admins should be able to Y", "add a lookup
33
- by Z")
34
- - The user explicitly says "generate", "scaffold", or names the CLI
30
+ - A new feature, resource, or domain is requested, even if the user does not
31
+ say "module".
32
+ - A new endpoint or capability is requested on an existing feature, even if
33
+ the user does not say "method".
34
+ - The user explicitly asks to scaffold, generate, or use go-scaffold.
35
+
36
+ Examples that all use this skill:
37
+
38
+ - “add invoices” → a new module
39
+ - “let admins approve orders” → a new method on orders
40
+ - “list overdue invoices” → a GET-all method
41
+ - “look up a user by email” → a GET-one lookup with --field email
42
+
43
+ ## When not to use it
44
+
45
+ - Filling in business logic in a method the CLI already generated
46
+ - Editing model fields, DTO validation, or existing method behavior
47
+ - Adding a foreign key or relation between domains
48
+ - Bug fixes, refactors, or architecture work unrelated to generated surface
49
+ - Any repository without go-scaffold.config.json at its root
50
+
51
+ Those tasks still follow AGENTS.md and docs/architect/; this skill does
52
+ not authorize schema, auth/security, dependency, architecture, or deployment
53
+ changes without the project's required owner approval.
35
54
 
36
- ## When NOT to use this skill
55
+ ## Before running the CLI
37
56
 
38
- - Filling in business logic inside a method the CLI already generated
39
- - Editing model fields, DTO validation, or existing method bodies
40
- - Wiring a foreign key / relation between two domains
41
- - Bug fixes and refactors
42
- - The project has no `go-scaffold.config.json` at its root
57
+ 1. Read AGENTS.md, the relevant docs/architect/ notes, and the target
58
+ module's existing handler/service/repository.
59
+ 2. Run git status --short --branch and confirm the work is on a dedicated,
60
+ non-protected branch. Preserve unrelated local changes.
61
+ 3. Search for an existing module, endpoint, route, and migration before
62
+ generating anything. Do not generate a duplicate name.
63
+ 4. Decide which module profile matches the domain. The interactive wizard
64
+ offers Lean (`minimal + service`), CRUD (`crud + service`), CQRS
65
+ (`minimal + cqrs`), and Advanced for an explicit custom combination. Lean
66
+ is the safe starting point when the product only needs specific endpoints;
67
+ CRUD is for an intentional CRUD starter, and CQRS is for genuinely
68
+ different command/query needs. A profile is a generator preset, not a
69
+ claim about the domain's DDD maturity.
43
70
 
44
- ## How to use it
71
+ ## Commands
45
72
 
46
- Run from the project root:
73
+ Run from the project root and pass every value as a flag:
47
74
 
48
- ```bash
49
- go-scaffold generate module <name> --defaults [--full] [--auth] [--permission <code>]
75
+ ~~~bash
76
+ go-scaffold generate module <name> [--profile <lean|crud|cqrs>] [--defaults] [--auth] [--permission <code>]
50
77
  go-scaffold generate method <module> <name> --type <get|post|put|patch|delete> [--get-mode all|one] [--field <name>]
51
78
  go-scaffold generate migration <name>
79
+ go-scaffold check
80
+ go-scaffold config
81
+ go-scaffold add auth --store <postgres|redis> --browser-topology <same-origin|same-site|cross-site> [--defaults] [--yes]
82
+ go-scaffold add worker --queue <postgres|redis> [--defaults]
52
83
  go-scaffold undo module <name> -y
53
- ```
54
-
55
- **Every value has to be a flag.** These commands prompt for whatever you
56
- leave out, and a prompt in a non-interactive shell exits 1 having written
57
- nothing so a bare `generate module products` fails rather than taking the
58
- default. `--defaults` supplies the unasked-for answers (minimal, no auth);
59
- combine it with `--full`/`--auth`/`--permission` when you want those. The
60
- same applies to `--get-mode` (required with `--type get`) and `-y` on
61
- `undo module`. Every `add` command summarises what it will write and asks
62
- before writing: `-y` skips that confirmation, but `add auth` and `add worker`
63
- *also* ask which backing store to use so those two need `--defaults`
64
- (or their own `--store`/`--queue` flag plus `-y`), while `add rbac -y` and
65
- `add observability -y` are enough.
66
-
67
- `<name>` for a module is a domain noun in whatever form reads naturally —
68
- singular or plural, any case, hyphens or underscores are all accepted and
69
- normalized (`products` `product`, `Orders` `order`, `order-item`
70
- `orderitem`). From it the CLI derives the Go package name, the pluralized
71
- REST route (`/orders`, `/order-items`), the table, and the error-code prefix
72
- (`ORDER_ITEM_NOT_FOUND`). The only names it refuses are the ones that
73
- wouldn't compile: starting with a digit, or a Go keyword / predeclared type
74
- (`type`, `string`, `error`, ...).
75
-
76
- ### `generate module <name>`
77
-
78
- **Minimal is the default** — the module is created with no endpoints, and
79
- what lands in `cmd/api/wiring.go` is an *empty route group* under{{#if apiPrefix}} `/{{apiPrefix}}`{{else}} no prefix{{/if}}
80
- waiting for `generate method`. You still get every file:
81
- `internal/app/<pkg>/{model/,dto,errors,repository,service,handler,service_test,handler_test,repository_test}.go`
82
- the full data-access surface exists (the repository and its interface are
83
- complete) so `generate method` has something to call. Plus the model added
84
- to the `AutoMigrate` call and a new `migrations/<version>_create_<plural>.{up,down}.sql`.
85
-
86
- `--full` swaps in a CRUD skeleton instead: list/get/create/update/delete
87
- already routed and wired, with DTO fields and business rules left as TODOs.
88
- With OpenAPI enabled it also writes `docs/<plural>/{collection,item,schemas}.yaml`
89
- and wires them into `docs/openapi.yaml`. Use it only when a full CRUD surface
90
- is actually intended otherwise prefer minimal plus explicit `generate method`
91
- calls.
92
-
93
- `--auth` puts the module's routes behind a valid access token (requires
94
- `go-scaffold add auth -y` in this project first). `--permission <code>` also
95
- requires that permission via `authz.Require` and seeds it in its own
96
- migration it needs `add rbac`, and `--auth` must be passed alongside it.
97
-
98
- There's no per-domain versioning — the route prefix is a single
99
- project-wide choice made at `create` time.
100
-
101
- ### `generate method <module> <name>`
102
-
103
- Patches the existing module in place — never overwrites a method with the
104
- same name; if the name collides it asks for a different one instead. Route
105
- and shape depend on `--type`:
106
-
107
- | `--type` | Route | Notes |
84
+ ~~~
85
+
86
+ Omitted values prompt; in a non-interactive shell that exits 1 without
87
+ writing. For `generate module`, `--defaults` uses the recorded project
88
+ defaults and keeps the module public; fresh and legacy projects resolve to
89
+ Lean (`minimal + service`). `--profile` is the non-interactive equivalent of
90
+ choosing a named architecture preset. The older `--full` and `--cqrs` flags
91
+ remain supported for existing scripts, but cannot be combined with
92
+ `--profile`; `--full` means CRUD surface and `--cqrs` means separate command /
93
+ query handlers. Advanced is a wizard-only choice that asks the two axes
94
+ separately. Add `--auth` and `--permission <code>` only when they are wanted;
95
+ permission also requires `--auth`. `--get-mode` is required with `--type get`;
96
+ `--field` is required for GET-one and cannot be id. `undo` requires
97
+ `-y`. `add auth` accepts `--store postgres|redis` and
98
+ `--browser-topology same-origin|same-site|cross-site`; `--defaults` selects
99
+ Postgres and same-site. Postgres keeps refresh, recovery, and MFA state in
100
+ Postgres and uses an in-process rate limiter. Redis keeps refresh tokens and
101
+ rate-limit counters in Redis, while recovery and MFA state remain durable in
102
+ Postgres. Without `add worker`, auth mail is sent inline; with a worker it uses
103
+ the queue backend chosen by `add worker`.
104
+
105
+ Module names may be singular/plural, mixed case, hyphenated, or underscored.
106
+ The CLI normalizes the Go package, REST path, SQL table, and error-code prefix.
107
+ Reject names that would not compile, such as a leading digit, Go keyword, or
108
+ predeclared type.
109
+
110
+ ## Wizard and profile contract
111
+
112
+ `go-scaffold generate module <name>` asks for the module profile even when the
113
+ name is already supplied. If the name is omitted, it asks for the name first.
114
+ Choosing Advanced then asks for module surface and application style
115
+ separately. Auth and permission prompts appear only when the project has the
116
+ corresponding features installed. There is no final confirmation for module
117
+ generation; review the profile and flags before running it.
118
+
119
+ `go-scaffold config` changes defaults for future modules only. Existing modules
120
+ are not rewritten. `check`, `config show`, and `config validate` are
121
+ intentionally read-only and do not open a wizard. The bare `generate` command
122
+ first asks whether to generate a module, method, or migration; the bare `add`
123
+ command first asks which infrastructure feature to add.
124
+
125
+ ## Generated module contract
126
+
127
+ `generate module <name> --defaults` creates a module using the resolved project
128
+ defaults. On a fresh project that is a Lean module with:
129
+
130
+ ~~~text
131
+ internal/app/<pkg>/
132
+ domain/entity.go
133
+ domain/errors.go
134
+ ports/repository.go
135
+ application/dto.go
136
+ application/service.go
137
+ application/service_test.go
138
+ adapters/inbound/http/dto.go
139
+ adapters/inbound/http/handler.go
140
+ adapters/inbound/http/handler_test.go
141
+ adapters/outbound/postgres/model.go
142
+ adapters/outbound/postgres/repository.go
143
+ adapters/outbound/postgres/repository_test.go
144
+ composition.go
145
+ migrations/<version>_create_<plural>.{up,down}.sql
146
+ ~~~
147
+
148
+ The Lean/minimal module has no endpoint yet, but includes the complete
149
+ data-access surface and test seams needed by generate method. The CRUD profile
150
+ adds a CRUD starter and routes list/get/create/update/delete. The CQRS profile
151
+ replaces `application/service.go` and its test with
152
+ `application/commands.go`, `application/queries.go`, and
153
+ `application/cqrs_test.go`; Advanced can combine CRUD + CQRS, or use CQRS
154
+ alone before adding methods one at a time. Service and CQRS are mutually
155
+ exclusive within one module, but may coexist across modules.
156
+ With OpenAPI enabled it also creates the per-domain documents and updates
157
+ docs/openapi.yaml.
158
+
159
+ Feature-local composition.go constructs repository → application handlers →
160
+ handler. A module generated with `--cqrs` constructs separate command/query
161
+ handlers there; a regular module constructs its service there.
162
+ `internal/composition/` owns process-level adapters between feature ports.
163
+ `cmd/api/wiring.go` selects shared infrastructure, invokes those adapters, and
164
+ registers routes. Do not move business rules or feature-internal constructors
165
+ into either process-level location, or hand-edit root route markers for a new
166
+ endpoint.
167
+
168
+ ## Method shapes and stub safety
169
+
170
+ generate method patches the existing module without overwriting a same-named
171
+ method:
172
+
173
+ | Type | Shape | Generated behavior |
108
174
  |---|---|---|
109
- | `get --get-mode all` | `GET /<plural>/<kebab-name>` | list-style, reuses `FindAll` — TODO to add real filtering |
110
- | `get --get-mode one --field <f>` | `GET /<plural>/<f>/:<f>` | adds a real `FindBy<F>` query to the repository (and its `repository` interface + `repositoryStub` test stub); `--field` can't be `id` |
111
- | `post` | `POST /<plural>/<kebab-name>` | adds a body DTO; service body is a TODO stub (`apperror.NewInternal()` until implemented) |
112
- | `put` / `patch` | `<VERB> /<plural>/:id/<kebab-name>` | finds the record by id, TODO before saving (safe no-op until implemented) |
113
- | `delete` | `DELETE /<plural>/:id/<kebab-name>` | TODO stub (`apperror.NewInternal()` until implemented) |
114
-
115
- ### `generate migration <name>`
116
-
117
- Reserves a timestamped `migrations/<version>_<name>.{up,down}.sql` pair,
118
- both TODO stubs the CLI doesn't guess at columns, you write the SQL. This
119
- is the way to make any schema change that isn't a new module: adding a
120
- column, an index, a foreign key, a backfill, a drop.
121
-
122
- ### `undo module <name>`
123
-
124
- For a `generate module` that shouldn't have happened — a typo'd name, a
125
- domain decided against. Deletes `internal/app/<pkg>/`, the module's
126
- `migrations/<version>_create_<plural>.{up,down}.sql` pair, and reverses
127
- everything `generate module` wired up in `cmd/api/wiring.go` (and in
128
- `docs/openapi.yaml` + `docs/<plural>/` when OpenAPI is enabled). `-y` skips
129
- the confirmation prompt, and is required in a non-interactive shell.
130
-
131
- The migration files go because `migrations/embed.go` is a `//go:embed *`: a
132
- typo's migration left behind runs on every database created from then on.
133
- That's only safe while those files exist nowhere else, so `undo` refuses —
134
- deleting nothing when they're committed to git, or when the database is
135
- already at or past that version. Neither is a bug to work around: a domain
136
- that has shipped is retired with `generate migration drop_<table>` and a
137
- reviewed data removal, not with this command. The table is never dropped.
138
-
139
- What you still do by hand: real field names on the model/DTOs (the generated
140
- ones are placeholders), any foreign key to another domain (see
141
- `docs/architect/patterns.md` for the 3 rules), and the actual business logic
142
- behind every `TODO` the CLI leaves.
175
+ | get --get-mode all | GET /<plural>/<name> | Reuses FindAll; add real filtering |
176
+ | get --get-mode one --field <f> | GET /<plural>/<f>/:<f> | Adds a repository lookup and test seam |
177
+ | post | POST /<plural>/<name> | Body DTO plus internal-error TODO |
178
+ | put / patch | <VERB> /<plural>/:id/<name> | Safe 501 Not Implemented; no repository read/write |
179
+ | delete | DELETE /<plural>/:id/<name> | Internal-error TODO until implemented |
180
+
181
+ The 501 contract is intentional: a generated PUT/PATCH action must not
182
+ pretend to update a record. Implement the use case, validation, authorization,
183
+ repository operation, tests, and OpenAPI response together before changing
184
+ that behavior. Replace all generated TODOs before treating an endpoint as
185
+ production-ready.
186
+
187
+ ## Architecture guidance for generated code
188
+
189
+ This project is modular plus Hexagonal/DDD-friendly, with CQRS available via
190
+ the `cqrs` profile or the backwards-compatible
191
+ `go-scaffold generate module <name> --cqrs` flag:
192
+
193
+ - `adapters/inbound/http/dto.go` owns JSON names, binding/validation tags, and
194
+ request/response mapping; handlers are inbound adapters and know HTTP/Gin
195
+ only;
196
+ - `application/service.go` or `application/commands.go` plus
197
+ `application/queries.go` are application/use-case boundaries and depend on
198
+ narrow ports. The service and CQRS shapes are mutually exclusive inside one
199
+ module;
200
+ - `domain/` entities and invariants stay independent of Gin, GORM, Redis, and
201
+ HTTP; persistence models belong under `adapters/outbound/postgres`;
202
+ - repositories/token stores/mail/queue clients are outbound adapters;
203
+ - composition.go wires one feature locally, internal/composition/ wires
204
+ cross-feature capabilities, and cmd/api/wiring.go boots the process and
205
+ registers routes.
206
+
207
+ DDD does not require every CRUD row to become a large aggregate. CQRS optional:
208
+ use the CQRS profile when command and query models, consistency, or
209
+ read scaling differ; it creates separate command/query handlers while keeping
210
+ one modular monolith and database by default. For simple CRUD, a single
211
+ service and repository port are clearer. Do not add a broker, a second
212
+ database, or empty command/query layers just for naming.
213
+
214
+ Never import another feature's private model, repository, or handler. For
215
+ cross-feature behavior, define a narrow public application port, put a
216
+ synchronous adapter in internal/composition/ (importing only the feature's
217
+ public package root), use a documented feature API, or publish an explicit
218
+ event. Keep dependencies flowing inward:
219
+
220
+ ~~~text
221
+ handler -> application/use case -> domain + ports -> adapters
222
+ ^ |
223
+ +--- composition -+
224
+ ~~~
225
+
226
+ Auth and RBAC use the same canonical split as every other feature:
227
+ `domain/`, `application/`, `ports/`, `adapters/`, and a feature-local
228
+ `composition.go`. Auth owns user use cases and ports; RBAC owns role policy and
229
+ authorization adapters. There is no `compat/`, feature-level `model/`, or
230
+ second implementation tree. If an existing security flow needs a refactor,
231
+ split it at the application boundary and keep provider SDKs, Gin, and GORM
232
+ inside their respective adapters.
233
+
234
+ ## Authentication and browser OAuth contract
235
+
236
+ `add auth` provides email/password authentication and a generic provider
237
+ boundary. For browser OAuth, preserve this client-owned callback contract:
238
+
239
+ - The browser frontend owns one provider callback route. The API exposes
240
+ `GET /auth/{provider}/login` to start authorization and
241
+ `POST /auth/{provider}/exchange` to finish it; do not add an API callback
242
+ route or a server-side browser handoff after the exchange.
243
+ - The frontend creates `state` and an S256 PKCE verifier/challenge. It sends
244
+ `state`, `code_challenge`, and `code_challenge_method=S256` to the login
245
+ endpoint, handles provider success/cancel/error in its callback route, then
246
+ sends `code`, `state`, and `code_verifier` to the exchange endpoint.
247
+ - Provider adapters own OAuth configuration, code exchange, and claims/userinfo
248
+ validation. The application service persists a one-time server-side
249
+ transaction binding provider, state, S256 challenge, and OIDC nonce, then
250
+ consumes it before completion. Application services depend on the
251
+ `LoginProvider` port, provider registry, and normalized identity DTOs; they
252
+ must not branch on Google or import a provider's `oauth2.Config`.
253
+ - The server uses the exact provider-registered redirect URI from environment
254
+ configuration (`GOOGLE_OAUTH_REDIRECT_URI` for the Google adapter), validates
255
+ the provider response plus state, PKCE, and nonce, resolves the local
256
+ identity, creates the local session, sets an HttpOnly refresh cookie, and
257
+ returns JSON. Google is OIDC: validate the signed ID token's issuer,
258
+ audience/authorized party, time claims, nonce, and subject, and reconcile its
259
+ subject with UserInfo. Token responses must use `Cache-Control: no-store` and
260
+ `Pragma: no-cache`.
261
+ Never accept a request-supplied redirect destination or place an access token,
262
+ refresh token, authorization code, or state in a URI.
263
+ - Map provider and validation failures to controlled public codes only:
264
+ `oauth_denied`, `oauth_state_invalid`, `oauth_provider_unavailable`, and
265
+ `oauth_failed`. Do not expose raw provider descriptions or technical causes.
266
+ - `AUTH_BROWSER_TOPOLOGY` is the cookie/CORS deployment policy, not the
267
+ provider redirect configuration. Keep `CORS_ALLOWED_ORIGINS` as an exact
268
+ origin allowlist. Cross-site browser deployments require HTTPS,
269
+ `COOKIE_SAMESITE=none`, and `COOKIE_SECURE=true`; production browser cookies
270
+ always require `COOKIE_SECURE=true`. Because CORS is not CSRF protection,
271
+ cookie-authenticated state-changing endpoints also require an exact allowed
272
+ `Origin` (or equivalent validated `Referer`) when SameSite=None is used.
273
+ - Native/mobile OAuth is out of scope for this scaffold phase. Do not infer a
274
+ native callback or token handoff contract from the browser flow.
275
+
276
+ ### MFA contract
277
+
278
+ MFA has two independent switches: an operator capability and per-user
279
+ enrollment. Preserve both:
280
+
281
+ - `AUTH_MFA_ENABLED=false` is the default. When it is false, MFA endpoints
282
+ report unavailable and login does not require a challenge. Turning the
283
+ capability off does not delete stored enrollments; re-enabling it restores
284
+ the policy for users who had already enrolled.
285
+ - When the capability is enabled, the composition root must validate
286
+ `MFA_ENCRYPTION_KEY` as a base64-encoded 32-byte key before serving traffic.
287
+ Keep `MFA_ISSUER`, `MFA_CHALLENGE_TTL_MIN`, `MFA_TOTP_WINDOW`, and
288
+ `MFA_RECOVERY_CODE_COUNT` explicit in configuration; do not silently weaken
289
+ their safety limits.
290
+ - An authenticated user opts in with `GET /users/me/mfa`,
291
+ `POST /users/me/mfa/setup`, and `POST /users/me/mfa/confirm`; disabling it
292
+ requires the current TOTP code at `POST /users/me/mfa/disable`. Setup returns
293
+ the secret/otpauth URI only while pending, and confirmation returns recovery
294
+ codes only once.
295
+ - Password login and provider exchange may return
296
+ `{ "mfa_required": true, "challenge": "..." }` for an enrolled user. This
297
+ is a pre-session response: do not issue an access token or refresh cookie
298
+ until `POST /auth/mfa/verify` succeeds with a TOTP or recovery code.
299
+ - Store only an encrypted TOTP secret, hashed challenges, and hashed recovery
300
+ codes. Challenges are short-lived and one-use; a failed verification also
301
+ consumes its challenge. Recovery codes are atomic one-use login factors and
302
+ must not be accepted for disabling MFA. MFA state is durable in Postgres for
303
+ both refresh-token store choices, so `add auth`'s `create_mfa` migration must
304
+ be applied and included in real-store/concurrency tests.
305
+
306
+ Keep auth construction explicit: `NewService(deps Dependencies, cfg
307
+ AuthConfig)`. `Dependencies` owns narrow ports such as `MFA`, `Providers`,
308
+ `RefreshTokens`, `RecoveryTokens`, `Mailer`, `Roles`, and `Clock`; `AuthConfig`
309
+ owns JWT, OAuth, recovery, and `MFASettings` policy. Do not pass the generated
310
+ shared config package into the application service or replace these contracts
311
+ with a positional constructor.
312
+
313
+ When adding auth from a script, pass the choices explicitly, for example:
314
+
315
+ ~~~bash
316
+ go-scaffold add auth --store postgres --browser-topology same-site --defaults --yes
317
+ ~~~
318
+
319
+ ## Runtime, migration, and security guardrails
320
+
321
+ - APP_ENV is the only environment gate and accepts development or production.
322
+ Do not reintroduce an AUTO_MIGRATE setting.
323
+ - Development may use the convenience db.AutoMigrate(...) bootstrap.
324
+ Production must never bootstrap or mutate schema at API startup. Apply
325
+ versioned SQL with make migrate-up; the startup version check must fail
326
+ fast for a behind or dirty database.
327
+ - Use generate migration for schema changes outside a new module, write and
328
+ review both migration directions, and test them against a disposable DB.
329
+ - Keep SQL, Redis, queue, mail, and other external resources under the
330
+ composition root and close them on every exit path. Preserve
331
+ SIGINT/SIGTERM graceful shutdown and do not call os.Exit from a goroutine.
332
+ - Never log passwords, raw tokens, JWT secrets, OAuth secrets, or auth
333
+ headers. Client responses must not expose technical error causes.
334
+ - Refresh-token rotation is an atomic consume: Postgres uses one SQL unit and
335
+ Redis uses one Lua operation. Preserve active-session cleanup and the
336
+ reuse-detection tombstone; never replace it with read-then-delete.
337
+ - Password reset and email verification consume one-time tokens in the same
338
+ retry-safe transaction as the identity/user update. Preserve recovery on a
339
+ post-commit session-revocation failure.
340
+ - Wrap technical causes with %w or apperror.NewInternal(cause). Middleware
341
+ logs the cause with request_id; production strips it from the response.
342
+
343
+ ## Verification
344
+
345
+ After changing generated Go code or templates, run the relevant checks:
346
+
347
+ ~~~bash
348
+ gofmt -w <changed-go-files>
349
+ go test ./...
350
+ go test -race ./... # required for concurrency/auth/token changes
351
+ go vet ./...
352
+ golangci-lint run
353
+ ~~~
354
+
355
+ For repository, schema, or auth adapter changes, use a migrated disposable
356
+ PostgreSQL/Redis instance and set REQUIRE_TEST_DB=true or
357
+ REQUIRE_TEST_REDIS=true; a skipped integration test is not proof. MFA state
358
+ still requires PostgreSQL even when refresh tokens use Redis. When routes or
359
+ OpenAPI templates change, inspect the updated `docs/openapi.yaml` and run the
360
+ configured OpenAPI linter.
361
+
362
+ When the project uses River, run `make river-migrate-test` after
363
+ `make migrate-up-test`. The River integration test must enqueue a real job and
364
+ observe the worker handler; a process that only boots is not enough evidence.
365
+
366
+ Only in the `go-scaffold` source repository, run `pnpm run verify` for CLI or
367
+ template changes, then generate a fresh sample project and inspect both its
368
+ source and guidance files. A generated Go project does not contain the
369
+ generator's `package.json` or pnpm scripts; from that project run the Go checks
370
+ above and use the installed/local CLI for generation checks.
371
+ Review git diff --check, git diff, and git status --short before handoff,
372
+ and report exact output plus known limitations.
373
+
374
+ ## Migration and undo
375
+
376
+ generate migration <name> reserves a timestamped .up.sql/.down.sql pair; the
377
+ engineer writes the actual SQL. undo module <name> -y is only for an unshipped
378
+ generated module and refuses if its migration is committed or applied. Retire
379
+ a shipped domain with a reviewed drop migration and data plan, not undo.
@@ -6,7 +6,6 @@ APP_ENV=development # prod: production
6
6
  PORT=8080
7
7
  DB_DSN=postgres://postgres:postgres@localhost:5432/{{dbName}}?sslmode=disable
8
8
  LOG_LEVEL=info
9
- AUTO_MIGRATE=true
10
9
  DB_MAX_OPEN_CONNS=10
11
10
  DB_MAX_IDLE_CONNS=10
12
11
  DB_CONN_MAX_LIFETIME_MIN=5
@@ -15,8 +15,8 @@ linters:
15
15
  depguard:
16
16
  # What keeps this a modular monolith rather than a pile of folders: a
17
17
  # domain may not reach into another domain's package. It declares a
18
- # narrow interface for what it needs and cmd/api/wiring.go supplies the
19
- # concrete service — see docs/architect/patterns.md "Calling Another
18
+ # narrow interface for what it needs and internal/composition plus
19
+ # cmd/api/wiring.go supply the concrete service — see docs/architect/patterns.md "Calling Another
20
20
  # Domain's Logic". Without this, the first shortcut someone takes stays
21
21
  # invisible until the coupling is everywhere.
22
22
  #