@nakedev/go-scaffold 0.3.3 → 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.
- package/README.md +288 -50
- package/dist/commands/auth.js +53 -22
- package/dist/commands/config.js +50 -0
- package/dist/commands/create.js +32 -2
- package/dist/commands/generate.js +25 -2
- package/dist/commands/method.js +22 -7
- package/dist/commands/migration.js +2 -2
- package/dist/commands/observability.js +3 -3
- package/dist/commands/rbac.js +3 -3
- package/dist/commands/undo.js +5 -0
- package/dist/commands/worker.js +1 -1
- package/dist/index.js +186 -59
- package/dist/prompts/auth-wizard.js +40 -6
- package/dist/prompts/create-wizard.js +43 -2
- package/dist/prompts/generate-wizard.js +89 -9
- package/dist/templates/auth-manifest.js +31 -1
- package/dist/templates/create-manifest.js +4 -0
- package/dist/templates/module-manifest.js +37 -1
- package/dist/templates/rbac-manifest.js +1 -0
- package/dist/types.js +6 -0
- package/dist/utils/auth-patcher.js +115 -24
- package/dist/utils/config.js +147 -3
- package/dist/utils/main-patcher.js +29 -27
- package/dist/utils/marker-patch.js +7 -1
- package/dist/utils/method-patcher.js +261 -81
- package/dist/utils/module-profile.js +32 -0
- package/dist/utils/observability-patcher.js +2 -2
- package/dist/utils/platform-patcher.js +29 -7
- package/dist/utils/rbac-patcher.js +97 -75
- package/package.json +7 -2
- package/templates/add/auth/cmd/seed/main.go.hbs +13 -3
- package/templates/add/auth/docs/login.yaml.hbs +11 -1
- package/templates/add/auth/docs/mfa-verify.yaml.hbs +19 -0
- package/templates/add/auth/docs/provider-exchange.yaml.hbs +40 -0
- package/templates/add/auth/docs/provider-login.yaml.hbs +31 -0
- package/templates/add/auth/docs/refresh.yaml.hbs +7 -0
- package/templates/add/auth/docs/register.yaml.hbs +7 -0
- package/templates/add/auth/docs/reset-password.yaml.hbs +1 -1
- package/templates/add/auth/docs/schemas.yaml.hbs +59 -1
- package/templates/add/auth/docs/users-me-mfa-confirm.yaml.hbs +19 -0
- package/templates/add/auth/docs/users-me-mfa-disable.yaml.hbs +15 -0
- package/templates/add/auth/docs/users-me-mfa-setup.yaml.hbs +14 -0
- package/templates/add/auth/docs/users-me-mfa.yaml.hbs +12 -0
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +132 -0
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +113 -0
- package/templates/add/auth/internal/app/user/browser_policy.go.hbs +98 -0
- package/templates/add/auth/internal/app/user/composition.go.hbs +165 -0
- package/templates/add/auth/internal/app/user/contracts.go.hbs +88 -0
- package/templates/add/auth/internal/app/user/dto.go.hbs +57 -0
- package/templates/add/auth/internal/app/user/errors.go.hbs +25 -0
- package/templates/add/auth/internal/app/user/external_login.go.hbs +208 -0
- package/templates/add/auth/internal/app/user/handler.go.hbs +60 -203
- package/templates/add/auth/internal/app/user/handler_local.go.hbs +75 -0
- package/templates/add/auth/internal/app/user/handler_mfa.go.hbs +83 -0
- package/templates/add/auth/internal/app/user/handler_oauth.go.hbs +70 -0
- package/templates/add/auth/internal/app/user/handler_recovery.go.hbs +49 -0
- package/templates/add/auth/internal/app/user/handler_test.go.hbs +290 -0
- package/templates/add/auth/internal/app/user/handler_user.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/jwt.go.hbs +6 -59
- package/templates/add/auth/internal/app/user/local_auth.go.hbs +98 -0
- package/templates/add/auth/internal/app/user/mfa_service.go.hbs +450 -0
- package/templates/add/auth/internal/app/user/mfa_service_test.go.hbs +199 -0
- package/templates/add/auth/internal/app/user/mfa_store.go.hbs +127 -0
- package/templates/add/auth/internal/app/user/mfa_store_test.go.hbs +174 -0
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +8 -2
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +4 -3
- package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +17 -0
- package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +20 -0
- package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +17 -0
- package/templates/add/auth/internal/app/user/model/user.go.hbs +3 -2
- package/templates/add/auth/internal/app/user/provider_test.go.hbs +286 -0
- package/templates/add/auth/internal/app/user/recovery_service.go.hbs +114 -0
- package/templates/add/auth/internal/app/user/repository.go.hbs +2 -0
- package/templates/add/auth/internal/app/user/service.go.hbs +82 -478
- package/templates/add/auth/internal/app/user/service_test.go.hbs +601 -45
- package/templates/add/auth/internal/app/user/session_cookie.go.hbs +33 -0
- package/templates/add/auth/internal/app/user/sessions.go.hbs +99 -0
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +42 -14
- package/templates/add/auth/internal/app/user/tokenstore_pg.go.hbs +105 -40
- package/templates/add/auth/internal/app/user/tokenstore_pg_test.go.hbs +96 -0
- package/templates/add/auth/internal/app/user/tokenstore_recovery.go.hbs +58 -0
- package/templates/add/auth/internal/app/user/tokenstore_redis.go.hbs +144 -70
- package/templates/add/auth/internal/app/user/tokenstore_redis_test.go.hbs +185 -0
- package/templates/add/auth/internal/app/user/user_query.go.hbs +65 -0
- package/templates/add/auth/internal/platform/authprovider/google/google.go.hbs +389 -0
- package/templates/add/auth/internal/platform/authprovider/google/google_test.go.hbs +312 -0
- package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +9 -4
- package/templates/add/auth/migrations/create_identities.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_mfa.down.sql.hbs +3 -0
- package/templates/add/auth/migrations/create_mfa.up.sql.hbs +29 -0
- package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -2
- package/templates/add/rbac/internal/app/role/composition.go.hbs +35 -0
- package/templates/add/rbac/internal/app/role/service.go.hbs +12 -12
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +340 -121
- package/templates/create/base/.env.example.hbs +0 -1
- package/templates/create/base/AGENTS.md.hbs +255 -67
- package/templates/create/base/Makefile.hbs +2 -1
- package/templates/create/base/README.md.hbs +45 -17
- package/templates/create/base/cmd/api/wiring.go.hbs +18 -25
- package/templates/create/base/internal/platform/database/database.go.hbs +3 -3
- package/templates/create/base/internal/shared/apperror/apperror.go.hbs +15 -2
- package/templates/create/base/internal/shared/config/config.go.hbs +0 -8
- package/templates/create/base/internal/shared/middleware/cors_test.go.hbs +40 -0
- package/templates/create/base/internal/shared/middleware/error.go.hbs +15 -5
- package/templates/create/features/docs/architecture.md.hbs +38 -16
- package/templates/create/features/docs/patterns.md.hbs +40 -21
- package/templates/create/features/docs/techstack.md.hbs +3 -3
- package/templates/generate/module/commands.go.hbs +95 -0
- package/templates/generate/module/composition.go.hbs +23 -0
- package/templates/generate/module/cqrs_test.go.hbs +7 -0
- package/templates/generate/module/handler.go.hbs +50 -5
- package/templates/generate/module/minimal/commands.go.hbs +34 -0
- package/templates/generate/module/minimal/handler.go.hbs +34 -0
- package/templates/generate/module/minimal/queries.go.hbs +45 -0
- package/templates/generate/module/minimal/service.go.hbs +27 -1
- package/templates/generate/module/queries.go.hbs +62 -0
- package/templates/generate/module/service.go.hbs +61 -5
- package/templates/add/auth/docs/google-callback.yaml.hbs +0 -22
- package/templates/add/auth/docs/google-login.yaml.hbs +0 -7
|
@@ -6,12 +6,20 @@ exports.promptMethodType = promptMethodType;
|
|
|
6
6
|
exports.promptGetMode = promptGetMode;
|
|
7
7
|
exports.promptMigrationName = promptMigrationName;
|
|
8
8
|
exports.promptLookupField = promptLookupField;
|
|
9
|
+
exports.promptModuleSurface = promptModuleSurface;
|
|
10
|
+
exports.promptApplicationStyle = promptApplicationStyle;
|
|
11
|
+
exports.promptModuleProfile = promptModuleProfile;
|
|
12
|
+
exports.promptAdvancedModuleArchitecture = promptAdvancedModuleArchitecture;
|
|
13
|
+
exports.moduleArchitectureForProfile = moduleArchitectureForProfile;
|
|
14
|
+
exports.moduleProfileDescription = moduleProfileDescription;
|
|
9
15
|
exports.promptModuleShape = promptModuleShape;
|
|
16
|
+
exports.promptModuleCqrs = promptModuleCqrs;
|
|
10
17
|
exports.promptModuleAuth = promptModuleAuth;
|
|
11
18
|
exports.promptModulePermission = promptModulePermission;
|
|
12
19
|
exports.promptExistingModule = promptExistingModule;
|
|
13
20
|
const interactive_1 = require("./interactive");
|
|
14
21
|
const naming_1 = require("../utils/naming");
|
|
22
|
+
const module_profile_1 = require("../utils/module-profile");
|
|
15
23
|
// wraps an assert-style validator into inquirer's true|string contract so a
|
|
16
24
|
// reserved word re-prompts inline instead of aborting the whole command.
|
|
17
25
|
function notKeyword(value, role) {
|
|
@@ -78,29 +86,101 @@ async function promptLookupField() {
|
|
|
78
86
|
});
|
|
79
87
|
return field.trim();
|
|
80
88
|
}
|
|
81
|
-
// The
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
async function promptModuleShape() {
|
|
89
|
+
// The `generate module` choices live here next to the other generate prompts so
|
|
90
|
+
// the subcommand, bare menu, and project config wizard ask them consistently —
|
|
91
|
+
// a choice only reachable by knowing a flag name is not a choice for someone
|
|
92
|
+
// driving the CLI interactively.
|
|
93
|
+
async function promptModuleSurface(defaultValue = "minimal") {
|
|
87
94
|
return (0, interactive_1.select)({
|
|
88
95
|
message: "What should the module contain?",
|
|
89
|
-
default:
|
|
96
|
+
default: defaultValue,
|
|
90
97
|
choices: [
|
|
91
98
|
{
|
|
92
99
|
name: "Minimal — model + wiring only",
|
|
93
|
-
value:
|
|
100
|
+
value: "minimal",
|
|
94
101
|
description: "the safe default; add endpoints one at a time with `generate method`",
|
|
95
102
|
},
|
|
96
103
|
{
|
|
97
104
|
name: "CRUD skeleton — list/get/create/update/delete",
|
|
98
|
-
value:
|
|
105
|
+
value: "crud",
|
|
99
106
|
description: "all five endpoints wired up; DTO fields and business rules are left as TODO",
|
|
100
107
|
},
|
|
101
108
|
],
|
|
102
109
|
});
|
|
103
110
|
}
|
|
111
|
+
async function promptApplicationStyle(defaultValue = "service") {
|
|
112
|
+
return (0, interactive_1.select)({
|
|
113
|
+
message: "How should the application boundary be organised?",
|
|
114
|
+
default: defaultValue,
|
|
115
|
+
choices: [
|
|
116
|
+
{
|
|
117
|
+
name: "Single service",
|
|
118
|
+
value: "service",
|
|
119
|
+
description: "the simpler path; use this unless reads and writes have different business needs",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "CQRS command/query handlers",
|
|
123
|
+
value: "cqrs",
|
|
124
|
+
description: "separate state-changing commands from read-only queries; storage remains shared by default",
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Ask for the useful decision first. The underlying surface/application
|
|
131
|
+
* choices remain available through Advanced and through the CLI flags, but a
|
|
132
|
+
* new user should not have to understand two architecture axes before making
|
|
133
|
+
* a sensible choice.
|
|
134
|
+
*/
|
|
135
|
+
async function promptModuleProfile(defaultValue = "lean") {
|
|
136
|
+
return (0, interactive_1.select)({
|
|
137
|
+
message: "Choose a module profile:",
|
|
138
|
+
default: defaultValue,
|
|
139
|
+
choices: [
|
|
140
|
+
{
|
|
141
|
+
name: "Lean — minimal + single service",
|
|
142
|
+
value: "lean",
|
|
143
|
+
description: "model, repository, service and handler wiring; add only the endpoints this domain needs",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "CRUD — CRUD surface + single service",
|
|
147
|
+
value: "crud",
|
|
148
|
+
description: "list/get/create/update/delete skeletons; fields and business rules remain TODOs",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "CQRS — minimal surface + command/query handlers",
|
|
152
|
+
value: "cqrs",
|
|
153
|
+
description: "separate state-changing commands from read-only queries; no broker or second database is added",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "Advanced — choose surface and application boundary",
|
|
157
|
+
value: "advanced",
|
|
158
|
+
description: "for the uncommon CRUD + CQRS combination or a deliberately customised module",
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async function promptAdvancedModuleArchitecture(defaultSurface = "minimal", defaultApplicationStyle = "service") {
|
|
164
|
+
return {
|
|
165
|
+
moduleSurface: await promptModuleSurface(defaultSurface),
|
|
166
|
+
applicationStyle: await promptApplicationStyle(defaultApplicationStyle),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function moduleArchitectureForProfile(profile) {
|
|
170
|
+
return (0, module_profile_1.architectureForModuleProfile)(profile);
|
|
171
|
+
}
|
|
172
|
+
function moduleProfileDescription(moduleSurface, applicationStyle) {
|
|
173
|
+
return (0, module_profile_1.describeModuleProfile)((0, module_profile_1.moduleProfileFor)(moduleSurface, applicationStyle));
|
|
174
|
+
}
|
|
175
|
+
// Backward-compatible boolean helpers for the existing generate-module flow.
|
|
176
|
+
// The richer enum prompts are also used by the project config wizard so one
|
|
177
|
+
// set of choices describes both the default and the per-module override.
|
|
178
|
+
async function promptModuleShape(defaultValue = false) {
|
|
179
|
+
return (await promptModuleSurface(defaultValue ? "crud" : "minimal")) === "crud";
|
|
180
|
+
}
|
|
181
|
+
async function promptModuleCqrs(defaultValue = false) {
|
|
182
|
+
return (await promptApplicationStyle(defaultValue ? "cqrs" : "service")) === "cqrs";
|
|
183
|
+
}
|
|
104
184
|
async function promptModuleAuth() {
|
|
105
185
|
return (0, interactive_1.confirm)({
|
|
106
186
|
message: "Require a valid access token for this module's routes?",
|
|
@@ -8,27 +8,57 @@ const SHARED = [
|
|
|
8
8
|
{ template: "add/auth/internal/app/user/model/user.go.hbs", output: "internal/app/user/model/user.go" },
|
|
9
9
|
{ template: "add/auth/internal/app/user/model/identity.go.hbs", output: "internal/app/user/model/identity.go" },
|
|
10
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" },
|
|
16
|
+
{ 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" },
|
|
18
|
+
{ 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" },
|
|
11
22
|
{ template: "add/auth/internal/app/user/dto.go.hbs", output: "internal/app/user/dto.go" },
|
|
12
23
|
{ template: "add/auth/internal/app/user/errors.go.hbs", output: "internal/app/user/errors.go" },
|
|
13
24
|
{ template: "add/auth/internal/app/user/jwt.go.hbs", output: "internal/app/user/jwt.go" },
|
|
14
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" },
|
|
15
30
|
{ template: "add/auth/internal/app/user/repository.go.hbs", output: "internal/app/user/repository.go" },
|
|
16
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" },
|
|
17
37
|
{ template: "add/auth/internal/app/user/service_test.go.hbs", output: "internal/app/user/service_test.go" },
|
|
18
38
|
{ template: "add/auth/internal/app/user/repository_test.go.hbs", output: "internal/app/user/repository_test.go" },
|
|
19
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
|
+
{ template: "add/auth/internal/platform/authprovider/google/google.go.hbs", output: "internal/platform/authprovider/google/google.go" },
|
|
48
|
+
{ template: "add/auth/internal/platform/authprovider/google/google_test.go.hbs", output: "internal/platform/authprovider/google/google_test.go" },
|
|
20
49
|
{ template: "add/auth/cmd/seed/main.go.hbs", output: "cmd/seed/main.go" },
|
|
21
50
|
];
|
|
22
51
|
// Only the chosen store's implementation is written. Shipping both would drag
|
|
23
52
|
// go-redis into every project's go.mod for a file it never constructs — the
|
|
24
53
|
// same reason `add worker` writes one queue adapter, not two.
|
|
25
54
|
const POSTGRES = [
|
|
26
|
-
{ template: "add/auth/internal/app/user/model/authtoken.go.hbs", output: "internal/app/user/model/authtoken.go" },
|
|
27
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" },
|
|
28
57
|
{ template: "add/auth/internal/shared/middleware/ratelimit_memory.go.hbs", output: "internal/shared/middleware/ratelimit_memory.go" },
|
|
29
58
|
];
|
|
30
59
|
const REDIS = [
|
|
31
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" },
|
|
32
62
|
{ template: "add/auth/internal/shared/middleware/ratelimit_redis.go.hbs", output: "internal/shared/middleware/ratelimit_redis.go" },
|
|
33
63
|
];
|
|
34
64
|
function authFiles(store) {
|
|
@@ -57,6 +57,10 @@ exports.CREATE_MANIFEST = [
|
|
|
57
57
|
template: "create/base/internal/shared/middleware/cors.go.hbs",
|
|
58
58
|
output: "internal/shared/middleware/cors.go",
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
template: "create/base/internal/shared/middleware/cors_test.go.hbs",
|
|
62
|
+
output: "internal/shared/middleware/cors_test.go",
|
|
63
|
+
},
|
|
60
64
|
{
|
|
61
65
|
template: "create/base/internal/shared/middleware/error.go.hbs",
|
|
62
66
|
output: "internal/shared/middleware/error.go",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MODULE_FILES_MINIMAL = exports.MODULE_FILES = void 0;
|
|
3
|
+
exports.MODULE_FILES_MINIMAL_CQRS = exports.MODULE_FILES_MINIMAL = exports.MODULE_FILES_CQRS = exports.MODULE_FILES = void 0;
|
|
4
4
|
// output paths are relative to the module's own directory
|
|
5
5
|
// (internal/app/<pkg> or internal/app/v<version>/<pkg>)
|
|
6
6
|
exports.MODULE_FILES = [
|
|
@@ -9,11 +9,31 @@ exports.MODULE_FILES = [
|
|
|
9
9
|
{ template: "generate/module/errors.go.hbs", output: "errors.go" },
|
|
10
10
|
{ template: "generate/module/repository.go.hbs", output: "repository.go" },
|
|
11
11
|
{ template: "generate/module/service.go.hbs", output: "service.go" },
|
|
12
|
+
{ template: "generate/module/composition.go.hbs", output: "composition.go" },
|
|
12
13
|
{ template: "generate/module/handler.go.hbs", output: "handler.go" },
|
|
13
14
|
{ template: "generate/module/service_test.go.hbs", output: "service_test.go" },
|
|
14
15
|
{ template: "generate/module/handler_test.go.hbs", output: "handler_test.go" },
|
|
15
16
|
{ template: "generate/module/repository_test.go.hbs", output: "repository_test.go" },
|
|
16
17
|
];
|
|
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" },
|
|
36
|
+
];
|
|
17
37
|
// minimal: same model/errors/repository (generate method's patches assume the
|
|
18
38
|
// full data-access surface exists), but no default CRUD in dto/service/handler
|
|
19
39
|
// — add endpoints one at a time with `generate method`.
|
|
@@ -23,8 +43,24 @@ exports.MODULE_FILES_MINIMAL = [
|
|
|
23
43
|
{ template: "generate/module/errors.go.hbs", output: "errors.go" },
|
|
24
44
|
{ template: "generate/module/repository.go.hbs", output: "repository.go" },
|
|
25
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" },
|
|
51
|
+
];
|
|
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" },
|
|
26
61
|
{ template: "generate/module/minimal/handler.go.hbs", output: "handler.go" },
|
|
27
62
|
{ template: "generate/module/minimal/service_test.go.hbs", output: "service_test.go" },
|
|
28
63
|
{ template: "generate/module/minimal/handler_test.go.hbs", output: "handler_test.go" },
|
|
29
64
|
{ template: "generate/module/repository_test.go.hbs", output: "repository_test.go" },
|
|
65
|
+
{ template: "generate/module/cqrs_test.go.hbs", output: "cqrs_test.go" },
|
|
30
66
|
];
|
|
@@ -12,6 +12,7 @@ exports.RBAC_FILES = [
|
|
|
12
12
|
{ template: "add/rbac/internal/app/role/service.go.hbs", output: "internal/app/role/service.go" },
|
|
13
13
|
{ template: "add/rbac/internal/app/role/service_test.go.hbs", output: "internal/app/role/service_test.go" },
|
|
14
14
|
{ template: "add/rbac/internal/app/role/repository_test.go.hbs", output: "internal/app/role/repository_test.go" },
|
|
15
|
+
{ template: "add/rbac/internal/app/role/composition.go.hbs", output: "internal/app/role/composition.go" },
|
|
15
16
|
{ template: "add/rbac/internal/app/role/handler.go.hbs", output: "internal/app/role/handler.go" },
|
|
16
17
|
{ template: "add/rbac/internal/app/role/dto.go.hbs", output: "internal/app/role/dto.go" },
|
|
17
18
|
{ template: "add/rbac/internal/app/role/errors.go.hbs", output: "internal/app/role/errors.go" },
|
package/dist/types.js
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_ARCHITECTURE_CONFIG = void 0;
|
|
4
|
+
exports.DEFAULT_ARCHITECTURE_CONFIG = {
|
|
5
|
+
style: "modular-monolith",
|
|
6
|
+
defaultModuleSurface: "minimal",
|
|
7
|
+
defaultApplicationStyle: "service",
|
|
8
|
+
};
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.patchConfigForAuth = patchConfigForAuth;
|
|
7
7
|
exports.authWiringLines = authWiringLines;
|
|
8
|
+
exports.authHandlerLineFor = authHandlerLineFor;
|
|
8
9
|
exports.patchMainGoForAuth = patchMainGoForAuth;
|
|
9
10
|
exports.upgradeMailerToQueue = upgradeMailerToQueue;
|
|
10
11
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
@@ -17,8 +18,7 @@ const PLATFORM_INIT_MARKER = "// go-scaffold:platform-init";
|
|
|
17
18
|
const SCHEMA_MARKER = "// go-scaffold:schemas";
|
|
18
19
|
const MODEL_MARKER = "// go-scaffold:models";
|
|
19
20
|
const ROUTE_MARKER = "// go-scaffold:routes";
|
|
20
|
-
|
|
21
|
-
// patchConfigForAuth adds JWT/cookie/password-reset/Google OAuth fields to
|
|
21
|
+
// patchConfigForAuth adds JWT/cookie/password-reset/browser OAuth fields to
|
|
22
22
|
// Config, the same marker-based text insertion patchConfigForWorker uses
|
|
23
23
|
// (config.go.hbs is only rendered once, at `create` — everything after that
|
|
24
24
|
// is a real file a human may have already edited).
|
|
@@ -28,8 +28,11 @@ function patchConfigForAuth(configGoPath) {
|
|
|
28
28
|
"JWTSecret string",
|
|
29
29
|
"JWTAccessTTL time.Duration",
|
|
30
30
|
"JWTRefreshTTL time.Duration",
|
|
31
|
+
"JWTRefreshMaxTTL time.Duration",
|
|
32
|
+
"OAuthStateTTL time.Duration",
|
|
31
33
|
"CookieSecure bool",
|
|
32
34
|
"CookieSameSite string",
|
|
35
|
+
"AuthBrowserTopology string",
|
|
33
36
|
"",
|
|
34
37
|
"PasswordResetTTL time.Duration",
|
|
35
38
|
"PasswordResetURL string",
|
|
@@ -39,15 +42,25 @@ function patchConfigForAuth(configGoPath) {
|
|
|
39
42
|
"",
|
|
40
43
|
"GoogleClientID string",
|
|
41
44
|
"GoogleClientSecret string",
|
|
42
|
-
"
|
|
45
|
+
"GoogleOAuthRedirectURI string",
|
|
46
|
+
"",
|
|
47
|
+
"AuthMFAEnabled bool",
|
|
48
|
+
"MFAIssuer string",
|
|
49
|
+
"MFAEncryptionKey string",
|
|
50
|
+
"MFAChallengeTTL time.Duration",
|
|
51
|
+
"MFATOTPWindow int",
|
|
52
|
+
"MFARecoveryCodeCount int",
|
|
43
53
|
].join("\n");
|
|
44
54
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, CONFIG_FIELDS_MARKER, fieldsBlock, "JWTSecret");
|
|
45
55
|
const loadBlock = [
|
|
46
56
|
'JWTSecret: env("JWT_SECRET", "dev-secret-change-me"),',
|
|
47
57
|
'JWTAccessTTL: time.Duration(envInt("JWT_ACCESS_TTL_MIN", 15)) * time.Minute,',
|
|
48
58
|
'JWTRefreshTTL: time.Duration(envInt("JWT_REFRESH_TTL_MIN", 43200)) * time.Minute,',
|
|
59
|
+
'JWTRefreshMaxTTL: time.Duration(envInt("JWT_REFRESH_MAX_TTL_MIN", 43200)) * time.Minute,',
|
|
60
|
+
'OAuthStateTTL: time.Duration(envInt("OAUTH_STATE_TTL_MIN", 10)) * time.Minute,',
|
|
49
61
|
'CookieSecure: env("COOKIE_SECURE", "false") == "true",',
|
|
50
62
|
'CookieSameSite: env("COOKIE_SAMESITE", "strict"),',
|
|
63
|
+
'AuthBrowserTopology: env("AUTH_BROWSER_TOPOLOGY", "same-site"),',
|
|
51
64
|
"",
|
|
52
65
|
'PasswordResetTTL: time.Duration(envInt("PASSWORD_RESET_TTL_MIN", 30)) * time.Minute,',
|
|
53
66
|
'PasswordResetURL: env("PASSWORD_RESET_URL", "http://localhost:3000/reset-password"),',
|
|
@@ -57,7 +70,14 @@ function patchConfigForAuth(configGoPath) {
|
|
|
57
70
|
"",
|
|
58
71
|
'GoogleClientID: env("GOOGLE_CLIENT_ID", ""),',
|
|
59
72
|
'GoogleClientSecret: env("GOOGLE_CLIENT_SECRET", ""),',
|
|
60
|
-
'
|
|
73
|
+
'GoogleOAuthRedirectURI: env("GOOGLE_OAUTH_REDIRECT_URI", ""),',
|
|
74
|
+
"",
|
|
75
|
+
'AuthMFAEnabled: env("AUTH_MFA_ENABLED", "false") == "true",',
|
|
76
|
+
'MFAIssuer: env("MFA_ISSUER", "go-scaffold"),',
|
|
77
|
+
'MFAEncryptionKey: env("MFA_ENCRYPTION_KEY", ""),',
|
|
78
|
+
'MFAChallengeTTL: time.Duration(envInt("MFA_CHALLENGE_TTL_MIN", 5)) * time.Minute,',
|
|
79
|
+
'MFATOTPWindow: envInt("MFA_TOTP_WINDOW", 1),',
|
|
80
|
+
'MFARecoveryCodeCount: envInt("MFA_RECOVERY_CODE_COUNT", 10),',
|
|
61
81
|
].join("\n");
|
|
62
82
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, CONFIG_LOAD_MARKER, loadBlock, 'env("JWT_SECRET"');
|
|
63
83
|
fs_extra_1.default.writeFileSync(configGoPath, content);
|
|
@@ -68,13 +88,25 @@ function patchConfigForAuth(configGoPath) {
|
|
|
68
88
|
function authWiringLines(w) {
|
|
69
89
|
const postgres = w.store === "postgres";
|
|
70
90
|
return {
|
|
71
|
-
tokenStore: postgres ? "user.NewPgTokenStore(db)" : "user.NewRedisTokenStore(rdb)",
|
|
91
|
+
tokenStore: postgres ? "user.NewPgTokenStore(db)" : "user.NewRedisTokenStore(rdb, db)",
|
|
72
92
|
limiter: postgres ? "middleware.NewMemoryLimiter()" : "middleware.NewRedisLimiter(rdb)",
|
|
73
93
|
// with a queue the mail is enqueued and the request returns immediately;
|
|
74
94
|
// without one it goes out inline, which is the cost of not running a worker
|
|
75
95
|
mailer: w.worker ? "mail.NewAsyncClient(q)" : "mail.NewSyncClient(mail.Open(cfg))",
|
|
76
96
|
};
|
|
77
97
|
}
|
|
98
|
+
// authHandlerLineFor is the one root-level auth route shape. The feature owns
|
|
99
|
+
// construction; wiring.go only hands it shared infrastructure and, when RBAC
|
|
100
|
+
// is present, the role feature's public capabilities.
|
|
101
|
+
function authHandlerLineFor(w, roleDependencies = ["nil", "nil"]) {
|
|
102
|
+
const args = ["db", "cfg"];
|
|
103
|
+
if (w.store === "redis")
|
|
104
|
+
args.push("rdb");
|
|
105
|
+
if (w.worker)
|
|
106
|
+
args.push("q");
|
|
107
|
+
args.push(...roleDependencies);
|
|
108
|
+
return `user.NewHandlerFromDB(${args.join(", ")}).Register(api)`;
|
|
109
|
+
}
|
|
78
110
|
function patchMainGoForAuth(mainGoPath, w) {
|
|
79
111
|
const { goModule, queueBackend, store } = w;
|
|
80
112
|
let content = fs_extra_1.default.readFileSync(mainGoPath, "utf8");
|
|
@@ -86,8 +118,6 @@ function patchMainGoForAuth(mainGoPath, w) {
|
|
|
86
118
|
const queueImportLine = `"${goModule}/internal/platform/queue"`;
|
|
87
119
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, queueImportLine, queueImportLine);
|
|
88
120
|
}
|
|
89
|
-
const mailImportLine = `"${goModule}/internal/platform/mail"`;
|
|
90
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, mailImportLine, mailImportLine);
|
|
91
121
|
const checkBlock = [
|
|
92
122
|
'if cfg.IsProd() && cfg.JWTSecret == "dev-secret-change-me" {',
|
|
93
123
|
'\treturn errors.New("JWT_SECRET is still the dev default — set a real secret before deploying with APP_ENV=production")',
|
|
@@ -105,6 +135,21 @@ function patchMainGoForAuth(mainGoPath, w) {
|
|
|
105
135
|
"}",
|
|
106
136
|
].join("\n");
|
|
107
137
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, CONFIG_CHECKS_MARKER, smtpCheckBlock, "SMTP_HOST is unset");
|
|
138
|
+
const mfaCheckBlock = [
|
|
139
|
+
"if cfg.AuthMFAEnabled {",
|
|
140
|
+
"\tif err := user.ValidateMFASettings(user.MFASettings{",
|
|
141
|
+
"\t\tEnabled: cfg.AuthMFAEnabled,",
|
|
142
|
+
"\t\tIssuer: cfg.MFAIssuer,",
|
|
143
|
+
"\t\tEncryptionKey: cfg.MFAEncryptionKey,",
|
|
144
|
+
"\t\tChallengeTTL: cfg.MFAChallengeTTL,",
|
|
145
|
+
"\t\tTOTPWindow: cfg.MFATOTPWindow,",
|
|
146
|
+
"\t\tRecoveryCodeCount: cfg.MFARecoveryCodeCount,",
|
|
147
|
+
"\t}); err != nil {",
|
|
148
|
+
"\t\treturn fmt.Errorf(\"invalid MFA configuration: %w\", err)",
|
|
149
|
+
"\t}",
|
|
150
|
+
"}",
|
|
151
|
+
].join("\n");
|
|
152
|
+
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, CONFIG_CHECKS_MARKER, mfaCheckBlock, "invalid MFA configuration");
|
|
108
153
|
// The enqueuer is built from whatever backend `add worker` chose — the
|
|
109
154
|
// constructor differs, everything downstream of it (mail.NewAsyncClient)
|
|
110
155
|
// only sees the queue.Enqueuer interface and doesn't change.
|
|
@@ -120,24 +165,27 @@ function patchMainGoForAuth(mainGoPath, w) {
|
|
|
120
165
|
].join("\n");
|
|
121
166
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, SCHEMA_MARKER, schemaBlock, "CREATE SCHEMA IF NOT EXISTS user_svc");
|
|
122
167
|
const migrateLines = ["&usermodel.User{},", "&usermodel.Identity{},", "&usermodel.LoginThrottle{},"];
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
168
|
+
// Recovery tokens are always durable in Postgres, even when refresh tokens
|
|
169
|
+
// live in Redis, so reset/verification can share the user transaction.
|
|
170
|
+
migrateLines.push("&usermodel.AuthToken{},");
|
|
171
|
+
migrateLines.push("&usermodel.MFAEnrollment{},", "&usermodel.MFAChallenge{},", "&usermodel.MFARecoveryCode{},");
|
|
126
172
|
for (const line of migrateLines) {
|
|
127
173
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, MODEL_MARKER, line, line);
|
|
128
174
|
}
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
const { tokenStore, limiter, mailer } = authWiringLines(w);
|
|
133
|
-
const svcLine = `userSvc := user.NewService(user.NewRepository(db), ${tokenStore}, ${mailer}, cfg)`;
|
|
134
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, ROUTE_MARKER, svcLine, "userSvc :=");
|
|
135
|
-
const routeLine = `user.NewHandler(userSvc, cfg.JWTSecret, cfg.JWTRefreshTTL, cfg.CookieSecure, cfg.CookieSameSite, ${limiter}).Register(api)`;
|
|
175
|
+
// Keep auth construction inside the feature package. The root only chooses
|
|
176
|
+
// shared infrastructure and registers the resulting handler.
|
|
177
|
+
const routeLine = authHandlerLineFor(w);
|
|
136
178
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, ROUTE_MARKER, routeLine, routeLine);
|
|
137
179
|
content = content.replace(/\n\t_ = api \/\/ dropped once `generate module` registers the first route\n/, "\n");
|
|
138
180
|
if (w.worker) {
|
|
139
|
-
const
|
|
140
|
-
|
|
181
|
+
const cleanupBlock = [
|
|
182
|
+
"defer func() {",
|
|
183
|
+
'\tif err := q.Close(); err != nil {',
|
|
184
|
+
'\t\tlogger.Error("close queue", "error", err)',
|
|
185
|
+
"\t}",
|
|
186
|
+
"}()",
|
|
187
|
+
].join("\n");
|
|
188
|
+
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, PLATFORM_INIT_MARKER, cleanupBlock, "defer func() {\n\tif err := q.Close()");
|
|
141
189
|
}
|
|
142
190
|
fs_extra_1.default.writeFileSync(mainGoPath, content);
|
|
143
191
|
}
|
|
@@ -153,19 +201,62 @@ function patchMainGoForAuth(mainGoPath, w) {
|
|
|
153
201
|
// at all — both simply don't contain the line it looks for. Returns whether
|
|
154
202
|
// it actually upgraded something, so the caller's printed summary can say
|
|
155
203
|
// which happened instead of always assuming "no auth yet".
|
|
156
|
-
function upgradeMailerToQueue(mainGoPath, goModule, queueBackend) {
|
|
204
|
+
function upgradeMailerToQueue(mainGoPath, goModule, queueBackend, compositionGoPath) {
|
|
157
205
|
let content = fs_extra_1.default.readFileSync(mainGoPath, "utf8");
|
|
158
206
|
const syncMailer = "mail.NewSyncClient(mail.Open(cfg))";
|
|
159
|
-
|
|
207
|
+
let upgradedComposition = false;
|
|
208
|
+
// Auth now keeps mailer construction in internal/app/user. A worker added
|
|
209
|
+
// later must upgrade that feature-local composition and thread q into the
|
|
210
|
+
// existing root registration, rather than moving construction back to the
|
|
211
|
+
// composition root.
|
|
212
|
+
if (compositionGoPath && fs_extra_1.default.existsSync(compositionGoPath)) {
|
|
213
|
+
let composition = fs_extra_1.default.readFileSync(compositionGoPath, "utf8");
|
|
214
|
+
if (composition.includes(syncMailer)) {
|
|
215
|
+
composition = (0, marker_patch_1.ensureImport)(composition, `${goModule}/internal/platform/queue`);
|
|
216
|
+
if (!composition.includes("q queue.Enqueuer")) {
|
|
217
|
+
const configParam = "\tcfg config.Config,\n";
|
|
218
|
+
const redisParam = "\trdb *redis.Client,\n";
|
|
219
|
+
const queueAnchor = composition.includes(redisParam) ? redisParam : configParam;
|
|
220
|
+
if (!composition.includes(queueAnchor)) {
|
|
221
|
+
throw new Error(`${compositionGoPath} is missing the cfg parameter needed to upgrade auth mail to a queue`);
|
|
222
|
+
}
|
|
223
|
+
composition = composition.replace(queueAnchor, `${queueAnchor}\tq queue.Enqueuer,\n`);
|
|
224
|
+
}
|
|
225
|
+
composition = composition.replace(syncMailer, "mail.NewAsyncClient(q)");
|
|
226
|
+
fs_extra_1.default.writeFileSync(compositionGoPath, composition);
|
|
227
|
+
upgradedComposition = true;
|
|
228
|
+
const routeMatch = content.match(/user\.NewHandlerFromDB\(db, cfg, ([^)]*)\)\.Register\(api\)/);
|
|
229
|
+
if (!routeMatch) {
|
|
230
|
+
throw new Error("cmd/api/wiring.go is missing auth's feature-local NewHandlerFromDB route while adding the worker");
|
|
231
|
+
}
|
|
232
|
+
const args = routeMatch[1].split(",").map((arg) => arg.trim());
|
|
233
|
+
if (!args.includes("q")) {
|
|
234
|
+
if (args.length < 2)
|
|
235
|
+
throw new Error("auth's NewHandlerFromDB route has no role/authz dependency slots");
|
|
236
|
+
args.splice(args.length - 2, 0, "q");
|
|
237
|
+
content = content.replace(routeMatch[0], `user.NewHandlerFromDB(db, cfg, ${args.join(", ")}).Register(api)`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
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))
|
|
160
244
|
return false;
|
|
161
245
|
const queueImportLine = `"${goModule}/internal/platform/queue"`;
|
|
162
246
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, queueImportLine, queueImportLine);
|
|
163
247
|
const queueCtor = queueBackend === "river" ? "queue.NewRiverEnqueuer(db)" : "queue.NewAsynqEnqueuer(cfg.RedisURL)";
|
|
164
248
|
const queueInitBlock = [`q, err := ${queueCtor}`, "if err != nil {", '\treturn fmt.Errorf("open queue: %w", err)', "}"].join("\n");
|
|
165
249
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, PLATFORM_INIT_MARKER, queueInitBlock, "q, err := queue.New");
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
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)");
|
|
169
260
|
fs_extra_1.default.writeFileSync(mainGoPath, content);
|
|
170
261
|
return true;
|
|
171
262
|
}
|