@nakedev/go-scaffold 0.4.3 → 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 (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
@@ -15,6 +15,7 @@ const rbac_patcher_1 = require("../utils/rbac-patcher");
15
15
  const openapi_patcher_1 = require("../utils/openapi-patcher");
16
16
  const golangci_patcher_1 = require("../utils/golangci-patcher");
17
17
  const gocheck_1 = require("../utils/gocheck");
18
+ const docs_patcher_1 = require("../utils/docs-patcher");
18
19
  // URL (relative to the api prefix) -> docs file (relative to docs/) for every
19
20
  // route `add rbac` registers or adds onto the user handler.
20
21
  const RBAC_OPENAPI_PATHS = [
@@ -58,14 +59,14 @@ async function addRbac(projectDir = process.cwd()) {
58
59
  { template: "add/rbac/migrations/add_roles.down.sql.hbs", output: path_1.default.join("migrations", `${version}_add_roles.down.sql`) },
59
60
  ], {});
60
61
  (0, rbac_patcher_1.patchConfigForRbac)(path_1.default.join(projectDir, "internal", "shared", "config", "config.go"));
61
- (0, rbac_patcher_1.patchUserModelForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "model", "user.go"));
62
+ (0, rbac_patcher_1.patchUserModelForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "adapters", "outbound", "postgres", "model.go"));
62
63
  (0, rbac_patcher_1.patchMiddlewareAuthForRbac)(path_1.default.join(projectDir, "internal", "shared", "middleware", "auth.go"));
63
- (0, rbac_patcher_1.patchUserJWTForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "jwt.go"));
64
- (0, rbac_patcher_1.patchUserServiceForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "service.go"), config.goModule, path_1.default.join(projectDir, "internal", "app", "user", "sessions.go"));
65
- (0, rbac_patcher_1.patchUserServiceTestForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "service_test.go"));
66
- (0, rbac_patcher_1.patchUserDTOForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "dto.go"));
67
- (0, rbac_patcher_1.patchUserHandlerForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "handler.go"), config.goModule);
68
- (0, rbac_patcher_1.patchUserErrorsForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "errors.go"));
64
+ (0, rbac_patcher_1.patchUserJWTForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "application", "jwt.go"));
65
+ (0, rbac_patcher_1.patchUserServiceForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "application", "service.go"), path_1.default.join(projectDir, "internal", "app", "user", "application", "sessions.go"));
66
+ (0, rbac_patcher_1.patchUserServiceTestForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "application", "service_test.go"));
67
+ (0, rbac_patcher_1.patchUserDTOForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "adapters", "inbound", "http", "dto.go"));
68
+ (0, rbac_patcher_1.patchUserHandlerForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "adapters", "inbound", "http", "handler.go"));
69
+ (0, rbac_patcher_1.patchUserErrorsForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "application", "errors.go"));
69
70
  (0, golangci_patcher_1.patchGolangciForModule)(path_1.default.join(projectDir, ".golangci.yml"), config.goModule, "role");
70
71
  // projects scaffolded before --store existed are all Redis-backed
71
72
  (0, rbac_patcher_1.patchMainGoForRbac)(path_1.default.join(projectDir, "cmd", "api", "wiring.go"), config.goModule, config.features.authStore ?? "redis", config.features.worker ?? false);
@@ -94,9 +95,19 @@ async function addRbac(projectDir = process.cwd()) {
94
95
  didWhat: "added RBAC",
95
96
  recover: "internal/app/role/ and the rbac patches were left in place — reconcile cmd/api/wiring.go by hand.",
96
97
  });
97
- (0, config_1.writeConfig)(projectDir, { ...config, features: { ...config.features, rbac: true } });
98
+ const staleDocs = (0, docs_patcher_1.refreshProjectDocs)(projectDir, config, { rbac: true });
99
+ (0, config_1.writeConfig)(projectDir, {
100
+ ...config,
101
+ features: { ...config.features, rbac: true },
102
+ modules: {
103
+ ...config.modules,
104
+ role: { surface: "crud", applicationStyle: "service", boundary: "hexagonal", packageLayout: "split" },
105
+ },
106
+ });
98
107
  console.log(picocolors_1.default.green("\nadded internal/app/role/ and internal/shared/middleware/authz.go"));
99
108
  console.log("registered GET /users, GET /users/:id, PATCH /users/:id/set-role, /roles, and /permissions in cmd/api/wiring.go" + docsMessage);
109
+ if (staleDocs.length)
110
+ console.log(picocolors_1.default.yellow((0, docs_patcher_1.docsRefreshWarning)(staleDocs, "add rbac")));
100
111
  console.log(picocolors_1.default.yellow("\n⚠ The development table bootstrap does NOT seed role/permission data — it only creates the\n" +
101
112
  " tables from the Go structs. The \"staff\"/\"admin\" roles and their permissions live\n" +
102
113
  " in the migration's SQL (INSERT statements), which table creation never runs. Without\n" +
@@ -69,13 +69,16 @@ async function undoModule(rawName, opts, projectDir = process.cwd()) {
69
69
  if (!ok)
70
70
  throw new Error("undo cancelled");
71
71
  }
72
- // detect --auth/--permission from the generated handler.go itself (not
72
+ // detect --auth/--permission from the generated handler itself (not
73
73
  // stored anywhere else) — unpatchMainGo needs the exact same flags used at
74
74
  // generate-time to reconstruct the identical line it's removing.
75
- const handlerGoPath = path_1.default.join(moduleDir, "handler.go");
75
+ const handlerCandidates = [
76
+ path_1.default.join(moduleDir, "adapters", "inbound", "http", "handler.go"),
77
+ ];
78
+ const handlerGoPath = handlerCandidates.find((candidate) => fs_extra_1.default.existsSync(candidate));
76
79
  let auth;
77
80
  let permission;
78
- if (fs_extra_1.default.existsSync(handlerGoPath)) {
81
+ if (handlerGoPath) {
79
82
  const handlerContent = fs_extra_1.default.readFileSync(handlerGoPath, "utf8");
80
83
  auth = /jwtSecret\s+string/.test(handlerContent);
81
84
  permission = handlerContent.match(/h\.authz\.Require\("([^"]+)"\)/)?.[1];
@@ -14,6 +14,7 @@ const platform_patcher_1 = require("../utils/platform-patcher");
14
14
  const gocheck_1 = require("../utils/gocheck");
15
15
  const gomod_patcher_1 = require("../utils/gomod-patcher");
16
16
  const auth_patcher_1 = require("../utils/auth-patcher");
17
+ const docs_patcher_1 = require("../utils/docs-patcher");
17
18
  // addWorker scaffolds async job processing: the backend-neutral queue
18
19
  // contract (platform/queue), one adapter for the chosen backing store, SMTP
19
20
  // mail (platform/mail, with an email:send job as the one thing a fresh
@@ -35,6 +36,9 @@ async function addWorker(backend, projectDir = process.cwd()) {
35
36
  (0, platform_patcher_1.patchComposeForRedis)(path_1.default.join(projectDir, "docker-compose.yml"));
36
37
  (0, platform_patcher_1.patchCiForRedis)(path_1.default.join(projectDir, ".github", "workflows", "ci.yml"));
37
38
  }
39
+ else {
40
+ (0, platform_patcher_1.patchCiForRiver)(path_1.default.join(projectDir, ".github", "workflows", "ci.yml"));
41
+ }
38
42
  // pinned to what this scaffold was written against — see gomod-patcher
39
43
  (0, gomod_patcher_1.patchGoModRequires)(path_1.default.join(projectDir, "go.mod"), riverQueue
40
44
  ? ["github.com/riverqueue/river v0.43.0", "github.com/riverqueue/river/riverdriver/riverdatabasesql v0.43.0"]
@@ -48,6 +52,7 @@ async function addWorker(backend, projectDir = process.cwd()) {
48
52
  // parse-only: river/asynq aren't in go.mod until the user runs `go mod
49
53
  // tidy`, so `go vet` can't be the gate here.
50
54
  (0, gocheck_1.assertStillParses)(projectDir, parsedBefore, `added worker (${backend})`);
55
+ const staleDocs = (0, docs_patcher_1.refreshProjectDocs)(projectDir, config, { worker: true, queue: backend });
51
56
  (0, config_1.writeConfig)(projectDir, { ...config, features: { ...config.features, worker: true, queue: backend } });
52
57
  console.log(picocolors_1.default.green(`\nadded internal/platform/{queue,mail}/ and cmd/worker/ (queue backend: ${backend})`));
53
58
  if (riverQueue) {
@@ -61,6 +66,8 @@ async function addWorker(backend, projectDir = process.cwd()) {
61
66
  console.log(picocolors_1.default.yellow("note: a Redis enqueue cannot join a database transaction — see the warning on queue.Asynq"));
62
67
  console.log(picocolors_1.default.dim("\nnext: make worker (separate terminal, or `make dev` runs both), then go build ./... to confirm"));
63
68
  }
69
+ if (staleDocs.length)
70
+ console.log(picocolors_1.default.yellow((0, docs_patcher_1.docsRefreshWarning)(staleDocs, "add worker")));
64
71
  }
65
72
  // needsSmtp and needsRedis are checked independently: `add auth` run without
66
73
  // a worker already writes SMTP_HOST on its own (auth stands alone — see
@@ -105,15 +112,18 @@ function patchMakefile(makefilePath, opts) {
105
112
  console.error(picocolors_1.default.yellow(`skipped the Makefile \`dev\`/\`worker\` targets — no \`build:\` target to anchor them to in ${makefilePath}.\nAdd them by hand: \`worker:\` running \`go run ./cmd/worker\`.`));
106
113
  return;
107
114
  }
108
- content = content.replace(/^\.PHONY: /m, `.PHONY: dev worker${opts.river ? " river-migrate" : ""} `);
115
+ content = content.replace(/^\.PHONY: /m, `.PHONY: dev worker${opts.river ? " river-migrate river-migrate-test" : ""} `);
109
116
  // River keeps its own tables, versioned by River itself rather than by this
110
- // project's migrations/ directory — run its CLI once per database. Pinned
111
- // by nothing on purpose: it is a one-shot setup command, not a build input.
117
+ // project's migrations/ directory — run its pinned CLI once per database.
112
118
  const riverTarget = opts.river
113
119
  ? "\n# create River's job tables (run once per database, and after upgrading River)\n" +
114
120
  "river-migrate:\n" +
115
121
  "\t@set -a; [ -f $(ENV_FILE) ] && . ./$(ENV_FILE); set +a; \\\n" +
116
- '\tgo run github.com/riverqueue/river/cmd/river@latest migrate-up --line main --database-url "$$DB_DSN"\n'
122
+ '\tgo run github.com/riverqueue/river/cmd/river@v0.43.0 migrate-up --line main --database-url "$$DB_DSN"\n' +
123
+ "\n# apply River's own schema to TEST_DB_DSN for the real worker test\n" +
124
+ "river-migrate-test:\n" +
125
+ "\t@set -a; [ -f $(ENV_FILE) ] && . ./$(ENV_FILE); set +a; \\\n" +
126
+ '\tgo run github.com/riverqueue/river/cmd/river@v0.43.0 migrate-up --line main --database-url "$$TEST_DB_DSN"\n'
117
127
  : "";
118
128
  // Both load config exactly the way Makefile.hbs says every target does:
119
129
  // via $(ENV_FILE), and through the same sed that strips trailing comments.
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ const worker_wizard_1 = require("./prompts/worker-wizard");
21
21
  const auth_wizard_1 = require("./prompts/auth-wizard");
22
22
  const generate_wizard_1 = require("./prompts/generate-wizard");
23
23
  const config_1 = require("./commands/config");
24
+ const check_1 = require("./commands/check");
24
25
  const config_2 = require("./utils/config");
25
26
  const module_profile_1 = require("./utils/module-profile");
26
27
  // fail is every command's catch: one place so the two non-obvious cases stay
@@ -43,8 +44,19 @@ const program = new commander_1.Command();
43
44
  program
44
45
  .name("go-scaffold")
45
46
  .description("Scaffold Gin + GORM + Postgres Go backend projects with a consistent domain-module standard\n\n" +
46
- "Run `go-scaffold` with no arguments to pick what to do from a menu. Interactive commands ask for values you omit; read-only commands (`config show`, `config validate`) print or check state without prompts.")
47
+ "Run `go-scaffold` with no arguments to pick what to do from a menu. Interactive commands ask for values you omit; read-only commands (`check`, `config show`, `config validate`) print or check state without prompts.")
47
48
  .version((0, version_1.cliVersion)());
49
+ program
50
+ .command("check")
51
+ .description("validate the hexagonal split layout, process composition boundary, layer dependencies, and service/CQRS contract")
52
+ .action(() => {
53
+ try {
54
+ (0, check_1.checkProject)();
55
+ }
56
+ catch (err) {
57
+ fail(err);
58
+ }
59
+ });
48
60
  program
49
61
  .command("create [name]")
50
62
  .alias("c")
@@ -1,64 +1,65 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.authFiles = authFiles;
4
- // Everything that is the same whichever store backs the tokens.
4
+ // Auth is a complete hexagonal feature. The application package owns use
5
+ // cases and contracts, inbound owns Gin/HTTP translation, and outbound owns
6
+ // GORM/Redis details. The manifest is intentionally explicit: adding auth
7
+ // must create one canonical implementation tree.
5
8
  const SHARED = [
6
9
  { template: "add/auth/internal/shared/middleware/auth.go.hbs", output: "internal/shared/middleware/auth.go" },
7
10
  { template: "add/auth/internal/shared/middleware/ratelimit.go.hbs", output: "internal/shared/middleware/ratelimit.go" },
8
- { template: "add/auth/internal/app/user/model/user.go.hbs", output: "internal/app/user/model/user.go" },
9
- { template: "add/auth/internal/app/user/model/identity.go.hbs", output: "internal/app/user/model/identity.go" },
10
- { template: "add/auth/internal/app/user/model/loginthrottle.go.hbs", output: "internal/app/user/model/loginthrottle.go" },
11
- { template: "add/auth/internal/app/user/model/authtoken.go.hbs", output: "internal/app/user/model/authtoken.go" },
12
- { template: "add/auth/internal/app/user/model/mfa_enrollment.go.hbs", output: "internal/app/user/model/mfa_enrollment.go" },
13
- { template: "add/auth/internal/app/user/model/mfa_challenge.go.hbs", output: "internal/app/user/model/mfa_challenge.go" },
14
- { template: "add/auth/internal/app/user/model/mfa_recovery_code.go.hbs", output: "internal/app/user/model/mfa_recovery_code.go" },
15
- { template: "add/auth/internal/app/user/application/recovery.go.hbs", output: "internal/app/user/application/recovery.go" },
11
+ { template: "add/auth/internal/app/user/domain/entity.go.hbs", output: "internal/app/user/domain/entity.go" },
12
+ { template: "add/auth/internal/app/user/domain/errors.go.hbs", output: "internal/app/user/domain/errors.go" },
13
+ { template: "add/auth/internal/app/user/ports/repository.go.hbs", output: "internal/app/user/ports/repository.go" },
14
+ { template: "add/auth/internal/app/user/ports/password.go.hbs", output: "internal/app/user/ports/password.go" },
15
+ { template: "add/auth/internal/app/user/application/contracts.go.hbs", output: "internal/app/user/application/contracts.go" },
16
+ { template: "add/auth/internal/app/user/application/dto.go.hbs", output: "internal/app/user/application/dto.go" },
17
+ { template: "add/auth/internal/app/user/application/errors.go.hbs", output: "internal/app/user/application/errors.go" },
18
+ { template: "add/auth/internal/app/user/application/external_login.go.hbs", output: "internal/app/user/application/external_login.go" },
19
+ { template: "add/auth/internal/app/user/application/jwt.go.hbs", output: "internal/app/user/application/jwt.go" },
20
+ { template: "add/auth/internal/app/user/application/local_auth.go.hbs", output: "internal/app/user/application/local_auth.go" },
21
+ { template: "add/auth/internal/app/user/application/mfa_service.go.hbs", output: "internal/app/user/application/mfa_service.go" },
22
+ { template: "add/auth/internal/app/user/application/mfa_service_test.go.hbs", output: "internal/app/user/application/mfa_service_test.go" },
16
23
  { template: "add/auth/internal/app/user/application/oauth.go.hbs", output: "internal/app/user/application/oauth.go" },
17
- { template: "add/auth/internal/app/user/contracts.go.hbs", output: "internal/app/user/contracts.go" },
24
+ { template: "add/auth/internal/app/user/application/provider_test.go.hbs", output: "internal/app/user/application/provider_test.go" },
25
+ { template: "add/auth/internal/app/user/application/recovery.go.hbs", output: "internal/app/user/application/recovery.go" },
26
+ { template: "add/auth/internal/app/user/application/recovery_service.go.hbs", output: "internal/app/user/application/recovery_service.go" },
27
+ { template: "add/auth/internal/app/user/application/service.go.hbs", output: "internal/app/user/application/service.go" },
28
+ { template: "add/auth/internal/app/user/application/service_test.go.hbs", output: "internal/app/user/application/service_test.go" },
29
+ { template: "add/auth/internal/app/user/application/sessions.go.hbs", output: "internal/app/user/application/sessions.go" },
30
+ { template: "add/auth/internal/app/user/application/tokenstore_ports.go.hbs", output: "internal/app/user/application/tokenstore_ports.go" },
31
+ { template: "add/auth/internal/app/user/application/user_query.go.hbs", output: "internal/app/user/application/user_query.go" },
32
+ { template: "add/auth/internal/app/user/adapters/inbound/http/browser_policy.go.hbs", output: "internal/app/user/adapters/inbound/http/browser_policy.go" },
33
+ { template: "add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs", output: "internal/app/user/adapters/inbound/http/dto.go" },
34
+ { template: "add/auth/internal/app/user/adapters/inbound/http/handler.go.hbs", output: "internal/app/user/adapters/inbound/http/handler.go" },
35
+ { template: "add/auth/internal/app/user/adapters/inbound/http/handler_local.go.hbs", output: "internal/app/user/adapters/inbound/http/handler_local.go" },
36
+ { template: "add/auth/internal/app/user/adapters/inbound/http/handler_mfa.go.hbs", output: "internal/app/user/adapters/inbound/http/handler_mfa.go" },
37
+ { template: "add/auth/internal/app/user/adapters/inbound/http/handler_oauth.go.hbs", output: "internal/app/user/adapters/inbound/http/handler_oauth.go" },
38
+ { template: "add/auth/internal/app/user/adapters/inbound/http/handler_recovery.go.hbs", output: "internal/app/user/adapters/inbound/http/handler_recovery.go" },
39
+ { template: "add/auth/internal/app/user/adapters/inbound/http/handler_test.go.hbs", output: "internal/app/user/adapters/inbound/http/handler_test.go" },
40
+ { template: "add/auth/internal/app/user/adapters/inbound/http/handler_user.go.hbs", output: "internal/app/user/adapters/inbound/http/handler_user.go" },
41
+ { template: "add/auth/internal/app/user/adapters/inbound/http/session_cookie.go.hbs", output: "internal/app/user/adapters/inbound/http/session_cookie.go" },
42
+ { template: "add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs", output: "internal/app/user/adapters/outbound/password/bcrypt.go" },
43
+ { template: "add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs", output: "internal/app/user/adapters/outbound/password/bcrypt_test.go" },
44
+ { template: "add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs", output: "internal/app/user/adapters/outbound/postgres/model.go" },
45
+ { template: "add/auth/internal/app/user/adapters/outbound/postgres/mfa_store.go.hbs", output: "internal/app/user/adapters/outbound/postgres/mfa_store.go" },
46
+ { template: "add/auth/internal/app/user/adapters/outbound/postgres/mfa_store_test.go.hbs", output: "internal/app/user/adapters/outbound/postgres/mfa_store_test.go" },
47
+ { template: "add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs", output: "internal/app/user/adapters/outbound/postgres/repository.go" },
48
+ { template: "add/auth/internal/app/user/adapters/outbound/postgres/repository_test.go.hbs", output: "internal/app/user/adapters/outbound/postgres/repository_test.go" },
49
+ { template: "add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_recovery.go.hbs", output: "internal/app/user/adapters/outbound/postgres/tokenstore_recovery.go" },
18
50
  { template: "add/auth/internal/app/user/composition.go.hbs", output: "internal/app/user/composition.go" },
19
- { template: "add/auth/internal/app/user/provider_test.go.hbs", output: "internal/app/user/provider_test.go" },
20
- { template: "add/auth/internal/app/user/handler_test.go.hbs", output: "internal/app/user/handler_test.go" },
21
- { template: "add/auth/internal/app/user/tokenstore_recovery.go.hbs", output: "internal/app/user/tokenstore_recovery.go" },
22
- { template: "add/auth/internal/app/user/dto.go.hbs", output: "internal/app/user/dto.go" },
23
- { template: "add/auth/internal/app/user/errors.go.hbs", output: "internal/app/user/errors.go" },
24
- { template: "add/auth/internal/app/user/jwt.go.hbs", output: "internal/app/user/jwt.go" },
25
- { template: "add/auth/internal/app/user/tokenstore.go.hbs", output: "internal/app/user/tokenstore.go" },
26
- { template: "add/auth/internal/app/user/mfa_store.go.hbs", output: "internal/app/user/mfa_store.go" },
27
- { template: "add/auth/internal/app/user/mfa_store_test.go.hbs", output: "internal/app/user/mfa_store_test.go" },
28
- { template: "add/auth/internal/app/user/mfa_service.go.hbs", output: "internal/app/user/mfa_service.go" },
29
- { template: "add/auth/internal/app/user/mfa_service_test.go.hbs", output: "internal/app/user/mfa_service_test.go" },
30
- { template: "add/auth/internal/app/user/repository.go.hbs", output: "internal/app/user/repository.go" },
31
- { template: "add/auth/internal/app/user/service.go.hbs", output: "internal/app/user/service.go" },
32
- { template: "add/auth/internal/app/user/local_auth.go.hbs", output: "internal/app/user/local_auth.go" },
33
- { template: "add/auth/internal/app/user/sessions.go.hbs", output: "internal/app/user/sessions.go" },
34
- { template: "add/auth/internal/app/user/recovery_service.go.hbs", output: "internal/app/user/recovery_service.go" },
35
- { template: "add/auth/internal/app/user/external_login.go.hbs", output: "internal/app/user/external_login.go" },
36
- { template: "add/auth/internal/app/user/user_query.go.hbs", output: "internal/app/user/user_query.go" },
37
- { template: "add/auth/internal/app/user/service_test.go.hbs", output: "internal/app/user/service_test.go" },
38
- { template: "add/auth/internal/app/user/repository_test.go.hbs", output: "internal/app/user/repository_test.go" },
39
- { template: "add/auth/internal/app/user/handler.go.hbs", output: "internal/app/user/handler.go" },
40
- { template: "add/auth/internal/app/user/handler_local.go.hbs", output: "internal/app/user/handler_local.go" },
41
- { template: "add/auth/internal/app/user/handler_recovery.go.hbs", output: "internal/app/user/handler_recovery.go" },
42
- { template: "add/auth/internal/app/user/handler_oauth.go.hbs", output: "internal/app/user/handler_oauth.go" },
43
- { template: "add/auth/internal/app/user/handler_user.go.hbs", output: "internal/app/user/handler_user.go" },
44
- { template: "add/auth/internal/app/user/handler_mfa.go.hbs", output: "internal/app/user/handler_mfa.go" },
45
- { template: "add/auth/internal/app/user/session_cookie.go.hbs", output: "internal/app/user/session_cookie.go" },
46
- { template: "add/auth/internal/app/user/browser_policy.go.hbs", output: "internal/app/user/browser_policy.go" },
47
51
  { template: "add/auth/internal/platform/authprovider/google/google.go.hbs", output: "internal/platform/authprovider/google/google.go" },
48
52
  { template: "add/auth/internal/platform/authprovider/google/google_test.go.hbs", output: "internal/platform/authprovider/google/google_test.go" },
49
53
  { template: "add/auth/cmd/seed/main.go.hbs", output: "cmd/seed/main.go" },
50
54
  ];
51
- // Only the chosen store's implementation is written. Shipping both would drag
52
- // go-redis into every project's go.mod for a file it never constructs — the
53
- // same reason `add worker` writes one queue adapter, not two.
54
55
  const POSTGRES = [
55
- { template: "add/auth/internal/app/user/tokenstore_pg.go.hbs", output: "internal/app/user/tokenstore_pg.go" },
56
- { template: "add/auth/internal/app/user/tokenstore_pg_test.go.hbs", output: "internal/app/user/tokenstore_pg_test.go" },
56
+ { template: "add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg.go.hbs", output: "internal/app/user/adapters/outbound/postgres/tokenstore_pg.go" },
57
+ { template: "add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg_test.go.hbs", output: "internal/app/user/adapters/outbound/postgres/tokenstore_pg_test.go" },
57
58
  { template: "add/auth/internal/shared/middleware/ratelimit_memory.go.hbs", output: "internal/shared/middleware/ratelimit_memory.go" },
58
59
  ];
59
60
  const REDIS = [
60
- { template: "add/auth/internal/app/user/tokenstore_redis.go.hbs", output: "internal/app/user/tokenstore_redis.go" },
61
- { template: "add/auth/internal/app/user/tokenstore_redis_test.go.hbs", output: "internal/app/user/tokenstore_redis_test.go" },
61
+ { template: "add/auth/internal/app/user/adapters/outbound/redis/tokenstore.go.hbs", output: "internal/app/user/adapters/outbound/redis/tokenstore.go" },
62
+ { template: "add/auth/internal/app/user/adapters/outbound/redis/tokenstore_test.go.hbs", output: "internal/app/user/adapters/outbound/redis/tokenstore_test.go" },
62
63
  { template: "add/auth/internal/shared/middleware/ratelimit_redis.go.hbs", output: "internal/shared/middleware/ratelimit_redis.go" },
63
64
  ];
64
65
  function authFiles(store) {
@@ -25,6 +25,10 @@ exports.CREATE_MANIFEST = [
25
25
  template: "create/base/internal/platform/database/database.go.hbs",
26
26
  output: "internal/platform/database/database.go",
27
27
  },
28
+ {
29
+ template: "create/base/internal/composition/doc.go.hbs",
30
+ output: "internal/composition/doc.go",
31
+ },
28
32
  {
29
33
  template: "create/base/internal/shared/config/config.go.hbs",
30
34
  output: "internal/shared/config/config.go",
@@ -1,66 +1,88 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MODULE_FILES_MINIMAL_CQRS = exports.MODULE_FILES_MINIMAL = exports.MODULE_FILES_CQRS = exports.MODULE_FILES = void 0;
4
- // output paths are relative to the module's own directory
5
- // (internal/app/<pkg> or internal/app/v<version>/<pkg>)
6
- exports.MODULE_FILES = [
7
- { template: "generate/module/model/model.go.hbs", output: "model/model.go" },
8
- { template: "generate/module/dto.go.hbs", output: "dto.go" },
9
- { template: "generate/module/errors.go.hbs", output: "errors.go" },
10
- { template: "generate/module/repository.go.hbs", output: "repository.go" },
11
- { template: "generate/module/service.go.hbs", output: "service.go" },
12
- { template: "generate/module/composition.go.hbs", output: "composition.go" },
13
- { template: "generate/module/handler.go.hbs", output: "handler.go" },
14
- { template: "generate/module/service_test.go.hbs", output: "service_test.go" },
15
- { template: "generate/module/handler_test.go.hbs", output: "handler_test.go" },
16
- { template: "generate/module/repository_test.go.hbs", output: "repository_test.go" },
3
+ exports.MODULE_FILES_MINIMAL_CQRS = exports.MODULE_FILES_CQRS = exports.MODULE_FILES_MINIMAL = exports.MODULE_FILES = void 0;
4
+ // output paths are relative to the module's own directory.
5
+ //
6
+ // Every generated domain uses the same physical boundary. Surface and
7
+ // application style change the amount of code inside the boundary; they do
8
+ // not change where domain rules, ports, or adapters live.
9
+ const COMMON = [
10
+ { template: "generate/module/hexagonal/domain/entity.go.hbs", output: "domain/entity.go" },
11
+ { template: "generate/module/hexagonal/domain/errors.go.hbs", output: "domain/errors.go" },
12
+ { template: "generate/module/hexagonal/ports/repository.go.hbs", output: "ports/repository.go" },
13
+ {
14
+ template: "generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs",
15
+ output: "adapters/outbound/postgres/model.go",
16
+ },
17
+ {
18
+ template: "generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs",
19
+ output: "adapters/outbound/postgres/repository.go",
20
+ },
21
+ {
22
+ template: "generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs",
23
+ output: "adapters/inbound/http/handler_test.go",
24
+ },
25
+ {
26
+ template: "generate/module/hexagonal/adapters/outbound/postgres/repository_test.go.hbs",
27
+ output: "adapters/outbound/postgres/repository_test.go",
28
+ },
29
+ { template: "generate/module/hexagonal/composition.go.hbs", output: "composition.go" },
17
30
  ];
18
- // CQRS is opt-in so a simple CRUD module does not pay for command/query
19
- // ceremony. The implementation files are still in the feature package, which
20
- // keeps the module easy to adopt incrementally and lets `generate method` patch
21
- // the correct side later.
22
- exports.MODULE_FILES_CQRS = [
23
- { template: "generate/module/model/model.go.hbs", output: "model/model.go" },
24
- { template: "generate/module/dto.go.hbs", output: "dto.go" },
25
- { template: "generate/module/errors.go.hbs", output: "errors.go" },
26
- { template: "generate/module/repository.go.hbs", output: "repository.go" },
27
- { template: "generate/module/commands.go.hbs", output: "commands.go" },
28
- { template: "generate/module/queries.go.hbs", output: "queries.go" },
29
- { template: "generate/module/service.go.hbs", output: "service.go" },
30
- { template: "generate/module/composition.go.hbs", output: "composition.go" },
31
- { template: "generate/module/handler.go.hbs", output: "handler.go" },
32
- { template: "generate/module/service_test.go.hbs", output: "service_test.go" },
33
- { template: "generate/module/handler_test.go.hbs", output: "handler_test.go" },
34
- { template: "generate/module/repository_test.go.hbs", output: "repository_test.go" },
35
- { template: "generate/module/cqrs_test.go.hbs", output: "cqrs_test.go" },
31
+ const SERVICE_MINIMAL = [
32
+ { template: "generate/module/hexagonal/application/dto.minimal.go.hbs", output: "application/dto.go" },
33
+ {
34
+ template: "generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs",
35
+ output: "adapters/inbound/http/dto.go",
36
+ },
37
+ { template: "generate/module/hexagonal/application/service.go.hbs", output: "application/service.go" },
38
+ { template: "generate/module/hexagonal/application/service_test.go.hbs", output: "application/service_test.go" },
39
+ {
40
+ template: "generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs",
41
+ output: "adapters/inbound/http/handler.go",
42
+ },
36
43
  ];
37
- // minimal: same model/errors/repository (generate method's patches assume the
38
- // full data-access surface exists), but no default CRUD in dto/service/handler
39
- // — add endpoints one at a time with `generate method`.
40
- exports.MODULE_FILES_MINIMAL = [
41
- { template: "generate/module/model/model.go.hbs", output: "model/model.go" },
42
- { template: "generate/module/minimal/dto.go.hbs", output: "dto.go" },
43
- { template: "generate/module/errors.go.hbs", output: "errors.go" },
44
- { template: "generate/module/repository.go.hbs", output: "repository.go" },
45
- { template: "generate/module/minimal/service.go.hbs", output: "service.go" },
46
- { template: "generate/module/composition.go.hbs", output: "composition.go" },
47
- { template: "generate/module/minimal/handler.go.hbs", output: "handler.go" },
48
- { template: "generate/module/minimal/service_test.go.hbs", output: "service_test.go" },
49
- { template: "generate/module/minimal/handler_test.go.hbs", output: "handler_test.go" },
50
- { template: "generate/module/repository_test.go.hbs", output: "repository_test.go" },
44
+ const SERVICE_CRUD = [
45
+ { template: "generate/module/hexagonal/application/dto.go.hbs", output: "application/dto.go" },
46
+ {
47
+ template: "generate/module/hexagonal/adapters/inbound/http/dto.go.hbs",
48
+ output: "adapters/inbound/http/dto.go",
49
+ },
50
+ { template: "generate/module/hexagonal/application/service.crud.go.hbs", output: "application/service.go" },
51
+ { template: "generate/module/hexagonal/application/service_test.go.hbs", output: "application/service_test.go" },
52
+ {
53
+ template: "generate/module/hexagonal/adapters/inbound/http/handler.go.hbs",
54
+ output: "adapters/inbound/http/handler.go",
55
+ },
51
56
  ];
52
- exports.MODULE_FILES_MINIMAL_CQRS = [
53
- { template: "generate/module/model/model.go.hbs", output: "model/model.go" },
54
- { template: "generate/module/minimal/dto.go.hbs", output: "dto.go" },
55
- { template: "generate/module/errors.go.hbs", output: "errors.go" },
56
- { template: "generate/module/repository.go.hbs", output: "repository.go" },
57
- { template: "generate/module/minimal/commands.go.hbs", output: "commands.go" },
58
- { template: "generate/module/minimal/queries.go.hbs", output: "queries.go" },
59
- { template: "generate/module/minimal/service.go.hbs", output: "service.go" },
60
- { template: "generate/module/composition.go.hbs", output: "composition.go" },
61
- { template: "generate/module/minimal/handler.go.hbs", output: "handler.go" },
62
- { template: "generate/module/minimal/service_test.go.hbs", output: "service_test.go" },
63
- { template: "generate/module/minimal/handler_test.go.hbs", output: "handler_test.go" },
64
- { template: "generate/module/repository_test.go.hbs", output: "repository_test.go" },
65
- { template: "generate/module/cqrs_test.go.hbs", output: "cqrs_test.go" },
57
+ const CQRS_MINIMAL = [
58
+ { template: "generate/module/hexagonal/application/dto.minimal.go.hbs", output: "application/dto.go" },
59
+ {
60
+ template: "generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs",
61
+ output: "adapters/inbound/http/dto.go",
62
+ },
63
+ { template: "generate/module/hexagonal/application/commands.go.hbs", output: "application/commands.go" },
64
+ { template: "generate/module/hexagonal/application/queries.go.hbs", output: "application/queries.go" },
65
+ {
66
+ template: "generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs",
67
+ output: "adapters/inbound/http/handler.go",
68
+ },
69
+ { template: "generate/module/hexagonal/application/cqrs_test.go.hbs", output: "application/cqrs_test.go" },
66
70
  ];
71
+ const CQRS_CRUD = [
72
+ { template: "generate/module/hexagonal/application/dto.go.hbs", output: "application/dto.go" },
73
+ {
74
+ template: "generate/module/hexagonal/adapters/inbound/http/dto.go.hbs",
75
+ output: "adapters/inbound/http/dto.go",
76
+ },
77
+ { template: "generate/module/hexagonal/application/commands.crud.go.hbs", output: "application/commands.go" },
78
+ { template: "generate/module/hexagonal/application/queries.crud.go.hbs", output: "application/queries.go" },
79
+ {
80
+ template: "generate/module/hexagonal/adapters/inbound/http/handler.go.hbs",
81
+ output: "adapters/inbound/http/handler.go",
82
+ },
83
+ { template: "generate/module/hexagonal/application/cqrs_test.go.hbs", output: "application/cqrs_test.go" },
84
+ ];
85
+ exports.MODULE_FILES = [...COMMON, ...SERVICE_CRUD];
86
+ exports.MODULE_FILES_MINIMAL = [...COMMON, ...SERVICE_MINIMAL];
87
+ exports.MODULE_FILES_CQRS = [...COMMON, ...CQRS_CRUD];
88
+ exports.MODULE_FILES_MINIMAL_CQRS = [...COMMON, ...CQRS_MINIMAL];
@@ -1,19 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RBAC_FILES = void 0;
4
- // output paths are relative to the project root
4
+ // output paths are relative to the project root. RBAC uses the same
5
+ // hexagonal split as every other generated module; there is one role
6
+ // implementation, not a public wrapper around duplicated code.
5
7
  exports.RBAC_FILES = [
6
8
  { template: "add/rbac/internal/shared/middleware/authz.go.hbs", output: "internal/shared/middleware/authz.go" },
7
9
  { template: "add/rbac/internal/shared/middleware/authz_test.go.hbs", output: "internal/shared/middleware/authz_test.go" },
8
- { template: "add/rbac/internal/app/role/model/role.go.hbs", output: "internal/app/role/model/role.go" },
9
- { template: "add/rbac/internal/app/role/model/permission.go.hbs", output: "internal/app/role/model/permission.go" },
10
- { template: "add/rbac/internal/app/role/model/role_permission.go.hbs", output: "internal/app/role/model/role_permission.go" },
11
- { template: "add/rbac/internal/app/role/repository.go.hbs", output: "internal/app/role/repository.go" },
12
- { template: "add/rbac/internal/app/role/service.go.hbs", output: "internal/app/role/service.go" },
13
- { template: "add/rbac/internal/app/role/service_test.go.hbs", output: "internal/app/role/service_test.go" },
14
- { template: "add/rbac/internal/app/role/repository_test.go.hbs", output: "internal/app/role/repository_test.go" },
10
+ { template: "add/rbac/internal/app/role/domain/entity.go.hbs", output: "internal/app/role/domain/entity.go" },
11
+ { template: "add/rbac/internal/app/role/domain/errors.go.hbs", output: "internal/app/role/domain/errors.go" },
12
+ { template: "add/rbac/internal/app/role/ports/repository.go.hbs", output: "internal/app/role/ports/repository.go" },
13
+ { template: "add/rbac/internal/app/role/application/dto.go.hbs", output: "internal/app/role/application/dto.go" },
14
+ { template: "add/rbac/internal/app/role/application/errors.go.hbs", output: "internal/app/role/application/errors.go" },
15
+ { template: "add/rbac/internal/app/role/application/service.go.hbs", output: "internal/app/role/application/service.go" },
16
+ { template: "add/rbac/internal/app/role/application/service_test.go.hbs", output: "internal/app/role/application/service_test.go" },
17
+ { template: "add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs", output: "internal/app/role/adapters/inbound/http/handler.go" },
18
+ { template: "add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs", output: "internal/app/role/adapters/inbound/http/handler_test.go" },
19
+ { template: "add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs", output: "internal/app/role/adapters/outbound/postgres/model.go" },
20
+ { template: "add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs", output: "internal/app/role/adapters/outbound/postgres/repository.go" },
21
+ { template: "add/rbac/internal/app/role/adapters/outbound/postgres/repository_test.go.hbs", output: "internal/app/role/adapters/outbound/postgres/repository_test.go" },
15
22
  { template: "add/rbac/internal/app/role/composition.go.hbs", output: "internal/app/role/composition.go" },
16
- { template: "add/rbac/internal/app/role/handler.go.hbs", output: "internal/app/role/handler.go" },
17
- { template: "add/rbac/internal/app/role/dto.go.hbs", output: "internal/app/role/dto.go" },
18
- { template: "add/rbac/internal/app/role/errors.go.hbs", output: "internal/app/role/errors.go" },
19
23
  ];
@@ -18,7 +18,10 @@ const SHARED = [
18
18
  // its whole dependency tree into go.mod for nothing. Swapping later means
19
19
  // re-running `add worker` with the other backend, or copying the adapter in
20
20
  // by hand — the queue.go contract it implements does not change.
21
- const RIVER = [{ template: "add/worker/internal/platform/queue/river.go.hbs", output: "internal/platform/queue/river.go" }];
21
+ const RIVER = [
22
+ { template: "add/worker/internal/platform/queue/river.go.hbs", output: "internal/platform/queue/river.go" },
23
+ { template: "add/worker/internal/platform/queue/river_test.go.hbs", output: "internal/platform/queue/river_test.go" },
24
+ ];
22
25
  const ASYNQ = [
23
26
  { template: "add/worker/internal/platform/queue/asynq.go.hbs", output: "internal/platform/queue/asynq.go" },
24
27
  // Redis only comes along when it is actually the queue's backing store.
package/dist/types.js CHANGED
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_ARCHITECTURE_CONFIG = void 0;
4
4
  exports.DEFAULT_ARCHITECTURE_CONFIG = {
5
5
  style: "modular-monolith",
6
+ boundary: "hexagonal",
7
+ packageLayout: "split",
6
8
  defaultModuleSurface: "minimal",
7
9
  defaultApplicationStyle: "service",
8
10
  };
@@ -112,7 +112,7 @@ function patchMainGoForAuth(mainGoPath, w) {
112
112
  let content = fs_extra_1.default.readFileSync(mainGoPath, "utf8");
113
113
  const importLine = `"${goModule}/internal/app/user"`;
114
114
  content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, importLine, importLine);
115
- const modelImportLine = `usermodel "${goModule}/internal/app/user/model"`;
115
+ const modelImportLine = `usermodel "${goModule}/internal/app/user/adapters/outbound/postgres"`;
116
116
  content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, modelImportLine, modelImportLine);
117
117
  if (w.worker) {
118
118
  const queueImportLine = `"${goModule}/internal/platform/queue"`;
@@ -238,25 +238,25 @@ function upgradeMailerToQueue(mainGoPath, goModule, queueBackend, compositionGoP
238
238
  }
239
239
  }
240
240
  }
241
- // Keep the fallback for projects scaffolded before feature-local auth
242
- // composition existed. New projects take the branch above.
243
- if (!upgradedComposition && !content.includes(syncMailer))
244
- return false;
245
- const queueImportLine = `"${goModule}/internal/platform/queue"`;
246
- content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, queueImportLine, queueImportLine);
247
- const queueCtor = queueBackend === "river" ? "queue.NewRiverEnqueuer(db)" : "queue.NewAsynqEnqueuer(cfg.RedisURL)";
248
- const queueInitBlock = [`q, err := ${queueCtor}`, "if err != nil {", '\treturn fmt.Errorf("open queue: %w", err)', "}"].join("\n");
249
- content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, PLATFORM_INIT_MARKER, queueInitBlock, "q, err := queue.New");
250
- const cleanupBlock = [
251
- "defer func() {",
252
- '\tif err := q.Close(); err != nil {',
253
- '\t\tlogger.Error("close queue", "error", err)',
254
- "\t}",
255
- "}()",
256
- ].join("\n");
257
- content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, PLATFORM_INIT_MARKER, cleanupBlock, "defer func() {\n\tif err := q.Close()");
258
- if (!upgradedComposition)
259
- content = content.replace(syncMailer, () => "mail.NewAsyncClient(q)");
260
- fs_extra_1.default.writeFileSync(mainGoPath, content);
261
- return true;
241
+ if (upgradedComposition) {
242
+ const queueImportLine = `"${goModule}/internal/platform/queue"`;
243
+ content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, queueImportLine, queueImportLine);
244
+ const queueCtor = queueBackend === "river" ? "queue.NewRiverEnqueuer(db)" : "queue.NewAsynqEnqueuer(cfg.RedisURL)";
245
+ const queueInitBlock = [`q, err := ${queueCtor}`, "if err != nil {", '\treturn fmt.Errorf("open queue: %w", err)', "}"].join("\n");
246
+ content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, PLATFORM_INIT_MARKER, queueInitBlock, "q, err := queue.New");
247
+ const cleanupBlock = [
248
+ "defer func() {",
249
+ '\tif err := q.Close(); err != nil {',
250
+ '\t\tlogger.Error("close queue", "error", err)',
251
+ "\t}",
252
+ "}()",
253
+ ].join("\n");
254
+ content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, PLATFORM_INIT_MARKER, cleanupBlock, "defer func() {\n\tif err := q.Close()");
255
+ fs_extra_1.default.writeFileSync(mainGoPath, content);
256
+ return true;
257
+ }
258
+ if (content.includes(syncMailer)) {
259
+ throw new Error("auth mailer is not owned by internal/app/user/composition.go; regenerate auth with the canonical split layout before adding a worker");
260
+ }
261
+ return false;
262
262
  }