@nakedev/go-scaffold 0.4.0 → 0.4.3

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 (118) hide show
  1. package/README.md +288 -50
  2. package/dist/commands/auth.js +53 -22
  3. package/dist/commands/config.js +50 -0
  4. package/dist/commands/create.js +32 -2
  5. package/dist/commands/generate.js +25 -2
  6. package/dist/commands/method.js +22 -7
  7. package/dist/commands/migration.js +2 -2
  8. package/dist/commands/observability.js +3 -3
  9. package/dist/commands/rbac.js +3 -3
  10. package/dist/commands/undo.js +5 -0
  11. package/dist/commands/worker.js +1 -1
  12. package/dist/index.js +186 -59
  13. package/dist/prompts/auth-wizard.js +40 -6
  14. package/dist/prompts/create-wizard.js +42 -1
  15. package/dist/prompts/generate-wizard.js +89 -9
  16. package/dist/templates/auth-manifest.js +31 -1
  17. package/dist/templates/create-manifest.js +4 -0
  18. package/dist/templates/module-manifest.js +37 -1
  19. package/dist/templates/rbac-manifest.js +1 -0
  20. package/dist/types.js +6 -0
  21. package/dist/utils/auth-patcher.js +115 -24
  22. package/dist/utils/config.js +147 -3
  23. package/dist/utils/main-patcher.js +29 -27
  24. package/dist/utils/marker-patch.js +7 -1
  25. package/dist/utils/method-patcher.js +261 -81
  26. package/dist/utils/module-profile.js +32 -0
  27. package/dist/utils/platform-patcher.js +29 -7
  28. package/dist/utils/rbac-patcher.js +97 -75
  29. package/package.json +7 -2
  30. package/templates/add/auth/cmd/seed/main.go.hbs +13 -3
  31. package/templates/add/auth/docs/login.yaml.hbs +11 -1
  32. package/templates/add/auth/docs/mfa-verify.yaml.hbs +19 -0
  33. package/templates/add/auth/docs/provider-exchange.yaml.hbs +40 -0
  34. package/templates/add/auth/docs/provider-login.yaml.hbs +31 -0
  35. package/templates/add/auth/docs/refresh.yaml.hbs +7 -0
  36. package/templates/add/auth/docs/register.yaml.hbs +7 -0
  37. package/templates/add/auth/docs/reset-password.yaml.hbs +1 -1
  38. package/templates/add/auth/docs/schemas.yaml.hbs +59 -1
  39. package/templates/add/auth/docs/users-me-mfa-confirm.yaml.hbs +19 -0
  40. package/templates/add/auth/docs/users-me-mfa-disable.yaml.hbs +15 -0
  41. package/templates/add/auth/docs/users-me-mfa-setup.yaml.hbs +14 -0
  42. package/templates/add/auth/docs/users-me-mfa.yaml.hbs +12 -0
  43. package/templates/add/auth/internal/app/user/application/oauth.go.hbs +132 -0
  44. package/templates/add/auth/internal/app/user/application/recovery.go.hbs +113 -0
  45. package/templates/add/auth/internal/app/user/browser_policy.go.hbs +98 -0
  46. package/templates/add/auth/internal/app/user/composition.go.hbs +165 -0
  47. package/templates/add/auth/internal/app/user/contracts.go.hbs +88 -0
  48. package/templates/add/auth/internal/app/user/dto.go.hbs +57 -0
  49. package/templates/add/auth/internal/app/user/errors.go.hbs +25 -0
  50. package/templates/add/auth/internal/app/user/external_login.go.hbs +208 -0
  51. package/templates/add/auth/internal/app/user/handler.go.hbs +60 -203
  52. package/templates/add/auth/internal/app/user/handler_local.go.hbs +75 -0
  53. package/templates/add/auth/internal/app/user/handler_mfa.go.hbs +83 -0
  54. package/templates/add/auth/internal/app/user/handler_oauth.go.hbs +70 -0
  55. package/templates/add/auth/internal/app/user/handler_recovery.go.hbs +49 -0
  56. package/templates/add/auth/internal/app/user/handler_test.go.hbs +290 -0
  57. package/templates/add/auth/internal/app/user/handler_user.go.hbs +41 -0
  58. package/templates/add/auth/internal/app/user/jwt.go.hbs +6 -59
  59. package/templates/add/auth/internal/app/user/local_auth.go.hbs +98 -0
  60. package/templates/add/auth/internal/app/user/mfa_service.go.hbs +450 -0
  61. package/templates/add/auth/internal/app/user/mfa_service_test.go.hbs +199 -0
  62. package/templates/add/auth/internal/app/user/mfa_store.go.hbs +127 -0
  63. package/templates/add/auth/internal/app/user/mfa_store_test.go.hbs +174 -0
  64. package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +8 -2
  65. package/templates/add/auth/internal/app/user/model/identity.go.hbs +4 -3
  66. package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +17 -0
  67. package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +20 -0
  68. package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +17 -0
  69. package/templates/add/auth/internal/app/user/model/user.go.hbs +3 -2
  70. package/templates/add/auth/internal/app/user/provider_test.go.hbs +286 -0
  71. package/templates/add/auth/internal/app/user/recovery_service.go.hbs +114 -0
  72. package/templates/add/auth/internal/app/user/repository.go.hbs +2 -0
  73. package/templates/add/auth/internal/app/user/service.go.hbs +82 -478
  74. package/templates/add/auth/internal/app/user/service_test.go.hbs +601 -45
  75. package/templates/add/auth/internal/app/user/session_cookie.go.hbs +33 -0
  76. package/templates/add/auth/internal/app/user/sessions.go.hbs +99 -0
  77. package/templates/add/auth/internal/app/user/tokenstore.go.hbs +42 -14
  78. package/templates/add/auth/internal/app/user/tokenstore_pg.go.hbs +105 -40
  79. package/templates/add/auth/internal/app/user/tokenstore_pg_test.go.hbs +96 -0
  80. package/templates/add/auth/internal/app/user/tokenstore_recovery.go.hbs +58 -0
  81. package/templates/add/auth/internal/app/user/tokenstore_redis.go.hbs +144 -70
  82. package/templates/add/auth/internal/app/user/tokenstore_redis_test.go.hbs +185 -0
  83. package/templates/add/auth/internal/app/user/user_query.go.hbs +65 -0
  84. package/templates/add/auth/internal/platform/authprovider/google/google.go.hbs +389 -0
  85. package/templates/add/auth/internal/platform/authprovider/google/google_test.go.hbs +312 -0
  86. package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +9 -4
  87. package/templates/add/auth/migrations/create_identities.up.sql.hbs +1 -1
  88. package/templates/add/auth/migrations/create_mfa.down.sql.hbs +3 -0
  89. package/templates/add/auth/migrations/create_mfa.up.sql.hbs +29 -0
  90. package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -2
  91. package/templates/add/rbac/internal/app/role/composition.go.hbs +35 -0
  92. package/templates/add/rbac/internal/app/role/service.go.hbs +12 -12
  93. package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +340 -121
  94. package/templates/create/base/.env.example.hbs +0 -1
  95. package/templates/create/base/AGENTS.md.hbs +255 -67
  96. package/templates/create/base/Makefile.hbs +2 -1
  97. package/templates/create/base/README.md.hbs +42 -14
  98. package/templates/create/base/cmd/api/wiring.go.hbs +11 -8
  99. package/templates/create/base/internal/platform/database/database.go.hbs +3 -3
  100. package/templates/create/base/internal/shared/apperror/apperror.go.hbs +15 -2
  101. package/templates/create/base/internal/shared/config/config.go.hbs +0 -8
  102. package/templates/create/base/internal/shared/middleware/cors_test.go.hbs +40 -0
  103. package/templates/create/base/internal/shared/middleware/error.go.hbs +15 -5
  104. package/templates/create/features/docs/architecture.md.hbs +35 -13
  105. package/templates/create/features/docs/patterns.md.hbs +40 -21
  106. package/templates/create/features/docs/techstack.md.hbs +2 -2
  107. package/templates/generate/module/commands.go.hbs +95 -0
  108. package/templates/generate/module/composition.go.hbs +23 -0
  109. package/templates/generate/module/cqrs_test.go.hbs +7 -0
  110. package/templates/generate/module/handler.go.hbs +50 -5
  111. package/templates/generate/module/minimal/commands.go.hbs +34 -0
  112. package/templates/generate/module/minimal/handler.go.hbs +34 -0
  113. package/templates/generate/module/minimal/queries.go.hbs +45 -0
  114. package/templates/generate/module/minimal/service.go.hbs +27 -1
  115. package/templates/generate/module/queries.go.hbs +62 -0
  116. package/templates/generate/module/service.go.hbs +61 -5
  117. package/templates/add/auth/docs/google-callback.yaml.hbs +0 -22
  118. package/templates/add/auth/docs/google-login.yaml.hbs +0 -7
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.configureProject = configureProject;
7
+ exports.showProjectConfig = showProjectConfig;
8
+ exports.validateProjectConfig = validateProjectConfig;
9
+ const picocolors_1 = __importDefault(require("picocolors"));
10
+ const generate_wizard_1 = require("../prompts/generate-wizard");
11
+ const interactive_1 = require("../prompts/interactive");
12
+ const config_1 = require("../utils/config");
13
+ const module_profile_1 = require("../utils/module-profile");
14
+ /**
15
+ * Interactive project-level defaults. Existing modules are not rewritten: the
16
+ * wizard only controls what a future `generate module` receives when the user
17
+ * chooses `--defaults` or accepts the generated-module prompt defaults.
18
+ */
19
+ async function configureProject(projectDir = process.cwd()) {
20
+ const config = (0, config_1.readConfig)(projectDir);
21
+ console.log("\nConfigure future generated modules:\n");
22
+ const profile = await (0, generate_wizard_1.promptModuleProfile)((0, module_profile_1.moduleProfileFor)(config.architecture.defaultModuleSurface, config.architecture.defaultApplicationStyle));
23
+ const { moduleSurface: defaultModuleSurface, applicationStyle: defaultApplicationStyle } = profile === "advanced"
24
+ ? await (0, generate_wizard_1.promptAdvancedModuleArchitecture)(config.architecture.defaultModuleSurface, config.architecture.defaultApplicationStyle)
25
+ : (0, generate_wizard_1.moduleArchitectureForProfile)(profile);
26
+ console.log("\nSummary:");
27
+ console.log(` Default module profile: ${(0, generate_wizard_1.moduleProfileDescription)(defaultModuleSurface, defaultApplicationStyle)}`);
28
+ console.log(` Default module surface: ${defaultModuleSurface}`);
29
+ console.log(` Default application style: ${defaultApplicationStyle}`);
30
+ console.log(" Existing modules: unchanged");
31
+ const proceed = await (0, interactive_1.confirm)({ message: "Save these project defaults?", default: true });
32
+ if (!proceed)
33
+ throw new Error("configuration cancelled");
34
+ (0, config_1.writeConfig)(projectDir, {
35
+ ...config,
36
+ architecture: {
37
+ ...config.architecture,
38
+ defaultModuleSurface,
39
+ defaultApplicationStyle,
40
+ },
41
+ });
42
+ console.log(picocolors_1.default.green("\nupdated go-scaffold.config.json"));
43
+ }
44
+ function showProjectConfig(projectDir = process.cwd()) {
45
+ console.log(JSON.stringify((0, config_1.readConfig)(projectDir), null, 2));
46
+ }
47
+ function validateProjectConfig(projectDir = process.cwd()) {
48
+ const config = (0, config_1.readConfig)(projectDir);
49
+ console.log(picocolors_1.default.green(`valid go-scaffold.config.json (schema ${config.schemaVersion})`));
50
+ }
@@ -13,7 +13,9 @@ const config_1 = require("../utils/config");
13
13
  const naming_1 = require("../utils/naming");
14
14
  const create_wizard_1 = require("../prompts/create-wizard");
15
15
  const version_1 = require("../utils/version");
16
+ const types_1 = require("../types");
16
17
  const observability_1 = require("./observability");
18
+ const module_profile_1 = require("../utils/module-profile");
17
19
  async function createProject(rawName, opts) {
18
20
  const trimmed = (rawName ?? (await (0, create_wizard_1.promptProjectName)())).trim();
19
21
  if (!trimmed)
@@ -27,6 +29,13 @@ async function createProject(rawName, opts) {
27
29
  }
28
30
  let features;
29
31
  let apiPrefix;
32
+ let architecture;
33
+ const moduleProfile = (0, config_1.parseModuleProfile)(opts.moduleProfile, "--module-profile");
34
+ const moduleSurface = (0, config_1.parseModuleSurface)(opts.moduleSurface);
35
+ const applicationStyle = (0, config_1.parseApplicationStyle)(opts.applicationStyle);
36
+ if (moduleProfile && (moduleSurface || applicationStyle)) {
37
+ throw new Error("--module-profile cannot be combined with --module-surface or --application-style — choose one configuration style");
38
+ }
30
39
  if (opts.defaults) {
31
40
  features = { docker: opts.docker ?? true, openapiDocs: opts.openapiDocs ?? true, observability: opts.observability ?? false };
32
41
  // "" to match what the wizard's Enter now gives. --defaults means "don't
@@ -37,16 +46,27 @@ async function createProject(rawName, opts) {
37
46
  const check = (0, naming_1.validateApiPrefix)(apiPrefix);
38
47
  if (check !== true)
39
48
  throw new Error(check);
49
+ const profileArchitecture = moduleProfile ? (0, module_profile_1.architectureForModuleProfile)(moduleProfile) : undefined;
50
+ architecture = {
51
+ ...types_1.DEFAULT_ARCHITECTURE_CONFIG,
52
+ ...(profileArchitecture ? { defaultModuleSurface: profileArchitecture.moduleSurface } : {}),
53
+ ...(profileArchitecture ? { defaultApplicationStyle: profileArchitecture.applicationStyle } : {}),
54
+ ...(!profileArchitecture && moduleSurface ? { defaultModuleSurface: moduleSurface } : {}),
55
+ ...(!profileArchitecture && applicationStyle ? { defaultApplicationStyle: applicationStyle } : {}),
56
+ };
40
57
  }
41
58
  else {
42
59
  // commander gives `--no-x` options a default of true, and there is no
43
60
  // `--docker`/`--openapi-docs` to pass, so false here can only mean the
44
61
  // caller opted out explicitly. undefined leaves the question to the wizard.
45
- ({ features, apiPrefix } = await (0, create_wizard_1.runCreateWizard)({
62
+ ({ features, apiPrefix, architecture } = await (0, create_wizard_1.runCreateWizard)({
46
63
  docker: opts.docker === false ? false : undefined,
47
64
  openapiDocs: opts.openapiDocs === false ? false : undefined,
48
65
  observability: opts.observability === true ? true : undefined,
49
66
  apiPrefix: opts.apiPrefix,
67
+ moduleProfile,
68
+ moduleSurface,
69
+ applicationStyle,
50
70
  }));
51
71
  }
52
72
  const context = {
@@ -54,12 +74,22 @@ async function createProject(rawName, opts) {
54
74
  goModule,
55
75
  dbName: (0, naming_1.toDbName)(projectName),
56
76
  apiPrefix,
77
+ ...architecture,
57
78
  ...features,
58
79
  };
59
80
  await fs_extra_1.default.ensureDir(projectDir);
60
81
  await (0, template_renderer_1.applyTemplateEntries)(projectDir, create_manifest_1.CREATE_MANIFEST, context);
61
82
  (0, template_renderer_1.gofmtTree)(projectDir);
62
- (0, config_1.writeConfig)(projectDir, { projectName, goModule, apiPrefix, features, scaffoldVersion: (0, version_1.cliVersion)() });
83
+ (0, config_1.writeConfig)(projectDir, {
84
+ schemaVersion: 1,
85
+ projectName,
86
+ goModule,
87
+ apiPrefix,
88
+ features,
89
+ architecture,
90
+ modules: {},
91
+ scaffoldVersion: (0, version_1.cliVersion)(),
92
+ });
63
93
  // Composed the same way a user would do it by hand — `create` always
64
94
  // renders the plain base, then this layers the same patches `add
65
95
  // observability` would apply on an existing project, so the two paths
@@ -59,8 +59,17 @@ async function generateModule(rawName, opts, projectDir = process.cwd()) {
59
59
  modulePath,
60
60
  auth: opts.auth,
61
61
  permission: opts.permission,
62
+ cqrs: opts.cqrs,
63
+ moduleSurface: opts.full ? "crud" : "minimal",
64
+ applicationStyle: opts.cqrs ? "cqrs" : "service",
62
65
  };
63
- const moduleFiles = opts.full ? module_manifest_1.MODULE_FILES : module_manifest_1.MODULE_FILES_MINIMAL;
66
+ const moduleFiles = opts.cqrs
67
+ ? opts.full
68
+ ? module_manifest_1.MODULE_FILES_CQRS
69
+ : module_manifest_1.MODULE_FILES_MINIMAL_CQRS
70
+ : opts.full
71
+ ? module_manifest_1.MODULE_FILES
72
+ : module_manifest_1.MODULE_FILES_MINIMAL;
64
73
  const moduleEntries = moduleFiles.map((f) => ({
65
74
  template: f.template,
66
75
  output: path_1.default.join("internal", "app", modulePath, f.output),
@@ -132,8 +141,22 @@ async function generateModule(rawName, opts, projectDir = process.cwd()) {
132
141
  }
133
142
  (0, template_renderer_1.gofmtTree)(projectDir);
134
143
  (0, gocheck_1.assertNoDrift)(projectDir, checkBefore, config);
144
+ (0, config_1.writeConfig)(projectDir, {
145
+ ...config,
146
+ modules: {
147
+ ...config.modules,
148
+ [modulePath]: {
149
+ surface: opts.full ? "crud" : "minimal",
150
+ applicationStyle: opts.cqrs ? "cqrs" : "service",
151
+ },
152
+ },
153
+ });
135
154
  const routePath = config.apiPrefix ? `/${config.apiPrefix}/${naming.plural}` : `/${naming.plural}`;
136
155
  console.log(picocolors_1.default.green(`\ngenerated internal/app/${modulePath}/`));
156
+ if (opts.cqrs) {
157
+ console.log("application boundary: CQRS command/query handlers in commands.go and queries.go");
158
+ }
159
+ console.log(`recorded module defaults: ${opts.full ? "crud" : "minimal"} + ${opts.cqrs ? "cqrs" : "service"}`);
137
160
  if (opts.full) {
138
161
  console.log(`registered route ${routePath} in cmd/api/wiring.go`);
139
162
  }
@@ -162,5 +185,5 @@ async function generateModule(rawName, opts, projectDir = process.cwd()) {
162
185
  if (docsMessage)
163
186
  console.log(docsMessage);
164
187
  console.log(picocolors_1.default.dim(`\nnext: add real fields to model.go/dto.go, run \`go build ./...\`, then apply the migration ` +
165
- `(AUTO_MIGRATE=true handles it in dev, or \`migrate -path migrations -database "$DB_DSN" up\`)`));
188
+ `(use the development bootstrap locally, or \`migrate -path migrations -database "$DB_DSN" up\` before production)`));
166
189
  }
@@ -51,13 +51,13 @@ function methodOpenapiDocument(naming, method, type, getMode, field) {
51
51
  if (type === "post") {
52
52
  operation.push(" requestBody:", " required: true", " content:", " application/json:", " schema:", " type: object", " description: TODO define request fields");
53
53
  }
54
- const status = type === "delete" ? "204" : type === "post" ? "201" : "200";
55
- operation.push(" responses:", ` \"${status}\":`, ` description: ${type === "delete" ? "completed or already absent" : "TODO define response"}`);
56
- if (type !== "delete") {
54
+ const status = type === "delete" ? "204" : type === "post" ? "201" : type === "put" || type === "patch" ? "501" : "200";
55
+ operation.push(" responses:", ` \"${status}\":`, ` description: ${type === "delete" ? "completed or already absent" : type === "put" || type === "patch" ? "not implemented" : "TODO define response"}`);
56
+ if (type !== "delete" && type !== "put" && type !== "patch") {
57
57
  operation.push(" content:", " application/json:", " schema:", " type: object", " description: TODO replace with the method response schema");
58
58
  }
59
59
  operation.push(" \"400\": { $ref: '../../common/responses.yaml#/ValidationError' }");
60
- if (type === "get" || type === "put" || type === "patch") {
60
+ if (type === "get") {
61
61
  operation.push(" \"404\": { $ref: '../../common/responses.yaml#/NotFoundError' }");
62
62
  }
63
63
  return [...pathParameters, ...operation, ""].join("\n");
@@ -70,16 +70,30 @@ async function generateMethod(moduleNameArg, methodNameArg, opts, projectDir = p
70
70
  const paths = {
71
71
  dtoPath: path_1.default.join(moduleDir, "dto.go"),
72
72
  repositoryPath: path_1.default.join(moduleDir, "repository.go"),
73
+ commandPath: path_1.default.join(moduleDir, "commands.go"),
74
+ queryPath: path_1.default.join(moduleDir, "queries.go"),
73
75
  servicePath: path_1.default.join(moduleDir, "service.go"),
74
76
  handlerPath: path_1.default.join(moduleDir, "handler.go"),
75
77
  serviceTestPath: path_1.default.join(moduleDir, "service_test.go"),
76
78
  };
77
- for (const p of Object.values(paths)) {
79
+ const requiredPaths = [
80
+ paths.dtoPath,
81
+ paths.repositoryPath,
82
+ paths.servicePath,
83
+ paths.handlerPath,
84
+ paths.serviceTestPath,
85
+ ];
86
+ for (const p of requiredPaths) {
78
87
  if (!fs_extra_1.default.existsSync(p)) {
79
88
  throw new Error(`module "${naming.pkg}" not found at ${moduleDir} (missing ${path_1.default.basename(p)}) — ` +
80
89
  `run \`go-scaffold generate module ${naming.pkg}\` first`);
81
90
  }
82
91
  }
92
+ const cqrsPaths = [paths.commandPath, paths.queryPath].filter((candidate) => candidate !== undefined && fs_extra_1.default.existsSync(candidate));
93
+ if (cqrsPaths.length === 1) {
94
+ throw new Error(`module "${naming.pkg}" has an incomplete CQRS boundary at ${moduleDir} — ` +
95
+ `commands.go and queries.go must be present together`);
96
+ }
83
97
  const type = opts.type ?? (await (0, generate_wizard_1.promptMethodType)());
84
98
  if (opts.getMode && type !== "get") {
85
99
  throw new Error("--get-mode can only be used with --type get");
@@ -98,7 +112,7 @@ async function generateMethod(moduleNameArg, methodNameArg, opts, projectDir = p
98
112
  // patchMethod writes dto.go, then handler.go, then reads service.go, so a
99
113
  // missing service marker used to leave two files patched and the method
100
114
  // permanently un-retryable (assertNotDuplicate then sees it as existing).
101
- if (!(0, method_patcher_1.markersPresent)(paths.handlerPath, paths.servicePath)) {
115
+ if (!(0, method_patcher_1.markersPresentForPaths)(paths)) {
102
116
  throw new Error(`internal/app/${naming.pkg} is missing the marker comments \`generate method\` patches at.\n` +
103
117
  `handler.go and service.go must both still carry their \`// go-scaffold:*\` markers —\n` +
104
118
  `restore them, or add this method by hand.`);
@@ -151,5 +165,6 @@ async function generateMethod(moduleNameArg, methodNameArg, opts, projectDir = p
151
165
  if (docsRelativePath) {
152
166
  console.log(picocolors_1.default.green(`docs: docs/${docsRelativePath} (wired into docs/openapi.yaml)`));
153
167
  }
154
- console.log(picocolors_1.default.dim(`\nnext: fill in the TODO in service.go, then \`go build ./...\` / \`go test ./...\``));
168
+ const implementationFile = cqrsPaths.length === 2 ? (type === "get" ? "queries.go" : "commands.go") : "service.go";
169
+ console.log(picocolors_1.default.dim(`\nnext: fill in the TODO in ${implementationFile}, then \`go build ./...\` / \`go test ./...\``));
155
170
  }
@@ -29,6 +29,6 @@ async function generateMigration(rawName, projectDir = process.cwd()) {
29
29
  fs_extra_1.default.writeFileSync(upPath, `-- TODO: write the up migration for ${name}\n`);
30
30
  fs_extra_1.default.writeFileSync(downPath, `-- TODO: write the down migration for ${name} (reverses the up migration)\n`);
31
31
  console.log(picocolors_1.default.green(`\ngenerated migrations/${version}_${name}.{up,down}.sql`));
32
- console.log(picocolors_1.default.dim(`\nnext: write the SQL, then \`make migrate-up\` (dev) or apply it as a deploy step ` +
33
- `(AUTO_MIGRATE=true also picks up model changes automatically in dev — this file matters most for prod)`));
32
+ console.log(picocolors_1.default.dim(`\nnext: write the SQL, then apply it with \`make migrate-up\` before production ` +
33
+ `(APP_ENV=development only enables the convenience bootstrap)`));
34
34
  }
@@ -95,9 +95,9 @@ function refreshArchitectDocs(projectDir, config) {
95
95
  { template: "create/features/docs/architecture.md.hbs", output: path_1.default.join("docs", "architect", "architecture.md") },
96
96
  { template: "create/features/docs/techstack.md.hbs", output: path_1.default.join("docs", "architect", "techstack.md") },
97
97
  ];
98
- // only what the two templates actually reference — projectName, apiPrefix
99
- // and the feature flags
100
- const base = { projectName: config.projectName, apiPrefix: config.apiPrefix, ...config.features };
98
+ // only what the two templates actually reference — projectName, apiPrefix,
99
+ // architecture defaults and the feature flags
100
+ const base = { projectName: config.projectName, apiPrefix: config.apiPrefix, ...config.architecture, ...config.features };
101
101
  const root = (0, template_renderer_1.getTemplatesRoot)();
102
102
  const skipped = [];
103
103
  for (const doc of docs) {
@@ -61,7 +61,7 @@ async function addRbac(projectDir = process.cwd()) {
61
61
  (0, rbac_patcher_1.patchUserModelForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "model", "user.go"));
62
62
  (0, rbac_patcher_1.patchMiddlewareAuthForRbac)(path_1.default.join(projectDir, "internal", "shared", "middleware", "auth.go"));
63
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"));
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
65
  (0, rbac_patcher_1.patchUserServiceTestForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "service_test.go"));
66
66
  (0, rbac_patcher_1.patchUserDTOForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "dto.go"));
67
67
  (0, rbac_patcher_1.patchUserHandlerForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "handler.go"), config.goModule);
@@ -97,9 +97,9 @@ async function addRbac(projectDir = process.cwd()) {
97
97
  (0, config_1.writeConfig)(projectDir, { ...config, features: { ...config.features, rbac: true } });
98
98
  console.log(picocolors_1.default.green("\nadded internal/app/role/ and internal/shared/middleware/authz.go"));
99
99
  console.log("registered GET /users, GET /users/:id, PATCH /users/:id/set-role, /roles, and /permissions in cmd/api/wiring.go" + docsMessage);
100
- console.log(picocolors_1.default.yellow("\n⚠ AUTO_MIGRATE=true does NOT seed the role/permission data — it only creates the\n" +
100
+ console.log(picocolors_1.default.yellow("\n⚠ The development table bootstrap does NOT seed role/permission data — it only creates the\n" +
101
101
  " tables from the Go structs. The \"staff\"/\"admin\" roles and their permissions live\n" +
102
- " in the migration's SQL (INSERT statements), which AutoMigrate never runs. Without\n" +
102
+ " in the migration's SQL (INSERT statements), which table creation never runs. Without\n" +
103
103
  " applying it for real, `make seed` fails with \"unknown role code\" and nobody can be\n" +
104
104
  " granted anything. Apply it before relying on RBAC, even in dev:\n" +
105
105
  " migrate -path migrations -database \"$DB_DSN\" up (or: make migrate-up)"));
@@ -123,6 +123,11 @@ async function undoModule(rawName, opts, projectDir = process.cwd()) {
123
123
  didWhat: `undid module "${naming.pkg}"`,
124
124
  recover: `internal/app/${modulePath}/ is gone; re-run \`go-scaffold generate module ${naming.pkg}\` to put it\nback, then reconcile cmd/api/wiring.go by hand.`,
125
125
  });
126
+ if (config.modules[modulePath]) {
127
+ const modules = { ...config.modules };
128
+ delete modules[modulePath];
129
+ (0, config_1.writeConfig)(projectDir, { ...config, modules });
130
+ }
126
131
  console.log(picocolors_1.default.green(`\nundid module "${naming.pkg}"`));
127
132
  console.log(` deleted internal/app/${modulePath}/`);
128
133
  console.log(` un-wired cmd/api/wiring.go`);
@@ -41,7 +41,7 @@ async function addWorker(backend, projectDir = process.cwd()) {
41
41
  : ["github.com/hibiken/asynq v0.26.0", "github.com/redis/go-redis/v9 v9.22.0"]);
42
42
  // auth added before the worker wired a synchronous mailer — now that there
43
43
  // is a queue, move it onto it
44
- const mailerUpgraded = (0, auth_patcher_1.upgradeMailerToQueue)(path_1.default.join(projectDir, "cmd", "api", "wiring.go"), config.goModule, backend);
44
+ const mailerUpgraded = (0, auth_patcher_1.upgradeMailerToQueue)(path_1.default.join(projectDir, "cmd", "api", "wiring.go"), config.goModule, backend, path_1.default.join(projectDir, "internal", "app", "user", "composition.go"));
45
45
  patchEnvExample(path_1.default.join(projectDir, ".env.example"), { redis: !riverQueue });
46
46
  patchMakefile(path_1.default.join(projectDir, "Makefile"), { river: riverQueue });
47
47
  (0, template_renderer_1.gofmtTree)(projectDir);