@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
package/dist/utils/config.js
CHANGED
|
@@ -3,18 +3,45 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CONFIG_SCHEMA_VERSION = void 0;
|
|
6
7
|
exports.configPath = configPath;
|
|
8
|
+
exports.parseModuleSurface = parseModuleSurface;
|
|
9
|
+
exports.parseApplicationStyle = parseApplicationStyle;
|
|
10
|
+
exports.parseModuleProfile = parseModuleProfile;
|
|
7
11
|
exports.writeConfig = writeConfig;
|
|
8
12
|
exports.readConfig = readConfig;
|
|
9
13
|
exports.isProjectDir = isProjectDir;
|
|
10
14
|
const path_1 = __importDefault(require("path"));
|
|
11
15
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
16
|
+
const types_1 = require("../types");
|
|
12
17
|
const CONFIG_FILE = "go-scaffold.config.json";
|
|
18
|
+
exports.CONFIG_SCHEMA_VERSION = 1;
|
|
13
19
|
function configPath(projectDir) {
|
|
14
20
|
return path_1.default.join(projectDir, CONFIG_FILE);
|
|
15
21
|
}
|
|
22
|
+
function parseModuleSurface(value) {
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
const normalized = value.trim().toLowerCase();
|
|
26
|
+
assertOneOf(normalized, "--module-surface", ["minimal", "crud"]);
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
function parseApplicationStyle(value) {
|
|
30
|
+
if (value === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
const normalized = value.trim().toLowerCase();
|
|
33
|
+
assertOneOf(normalized, "--application-style", ["service", "cqrs"]);
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
function parseModuleProfile(value, flag = "--profile") {
|
|
37
|
+
if (value === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
const normalized = value.trim().toLowerCase();
|
|
40
|
+
assertOneOf(normalized, flag, ["lean", "crud", "cqrs"]);
|
|
41
|
+
return normalized;
|
|
42
|
+
}
|
|
16
43
|
function writeConfig(projectDir, config) {
|
|
17
|
-
fs_extra_1.default.writeJsonSync(configPath(projectDir), config, { spaces: 2 });
|
|
44
|
+
fs_extra_1.default.writeJsonSync(configPath(projectDir), normalizeProjectConfig(config, projectDir), { spaces: 2 });
|
|
18
45
|
}
|
|
19
46
|
// readConfig falls back to detecting from go.mod when the config file is
|
|
20
47
|
// missing (e.g. a project scaffolded before this file existed).
|
|
@@ -35,13 +62,127 @@ function readConfig(projectDir) {
|
|
|
35
62
|
if (!fs_extra_1.default.existsSync(file))
|
|
36
63
|
return detectConfig(projectDir);
|
|
37
64
|
const config = fs_extra_1.default.readJsonSync(file);
|
|
65
|
+
if (!isRecord(config)) {
|
|
66
|
+
throw new Error(`${CONFIG_FILE} must contain a JSON object`);
|
|
67
|
+
}
|
|
68
|
+
const detected = detectFeatures(projectDir);
|
|
69
|
+
const features = { ...(isRecord(config.features) ? config.features : {}) };
|
|
70
|
+
for (const key of Object.keys(detected)) {
|
|
71
|
+
if (features[key] === undefined)
|
|
72
|
+
features[key] = detected[key];
|
|
73
|
+
}
|
|
74
|
+
return normalizeProjectConfig({ ...config, features: features }, projectDir);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Validate and fill defaults for the project manifest. Older projects have
|
|
78
|
+
* no architecture/modules keys yet; normalising them here lets every command
|
|
79
|
+
* consume one stable shape while keeping the old config file compatible.
|
|
80
|
+
*/
|
|
81
|
+
function normalizeProjectConfig(raw, projectDir) {
|
|
82
|
+
const projectName = requiredString(raw.projectName, "projectName");
|
|
83
|
+
const goModule = requiredString(raw.goModule, "goModule");
|
|
84
|
+
const apiPrefix = requiredString(raw.apiPrefix ?? "", "apiPrefix");
|
|
85
|
+
const schemaVersion = raw.schemaVersion ?? exports.CONFIG_SCHEMA_VERSION;
|
|
86
|
+
if (!Number.isInteger(schemaVersion) || schemaVersion < 1) {
|
|
87
|
+
throw new Error(`${CONFIG_FILE} has invalid schemaVersion "${String(schemaVersion)}" — expected a positive integer`);
|
|
88
|
+
}
|
|
89
|
+
if (schemaVersion > exports.CONFIG_SCHEMA_VERSION) {
|
|
90
|
+
throw new Error(`${CONFIG_FILE} uses schemaVersion ${schemaVersion}, but this CLI supports up to ${exports.CONFIG_SCHEMA_VERSION} — upgrade go-scaffold first`);
|
|
91
|
+
}
|
|
38
92
|
const detected = detectFeatures(projectDir);
|
|
39
|
-
const features = { ...
|
|
93
|
+
const features = { ...(isRecord(raw.features) ? raw.features : {}) };
|
|
40
94
|
for (const key of Object.keys(detected)) {
|
|
41
95
|
if (features[key] === undefined)
|
|
42
96
|
features[key] = detected[key];
|
|
43
97
|
}
|
|
44
|
-
|
|
98
|
+
validateFeatures(features);
|
|
99
|
+
const architecture = normalizeArchitecture(raw.architecture);
|
|
100
|
+
const modules = normalizeModules(raw.modules);
|
|
101
|
+
return {
|
|
102
|
+
schemaVersion,
|
|
103
|
+
projectName,
|
|
104
|
+
goModule,
|
|
105
|
+
apiPrefix,
|
|
106
|
+
features: features,
|
|
107
|
+
architecture,
|
|
108
|
+
modules,
|
|
109
|
+
...(raw.scaffoldVersion ? { scaffoldVersion: raw.scaffoldVersion } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function normalizeArchitecture(raw) {
|
|
113
|
+
const value = raw === undefined ? {} : raw;
|
|
114
|
+
if (!isRecord(value)) {
|
|
115
|
+
throw new Error(`${CONFIG_FILE}.architecture must be a JSON object`);
|
|
116
|
+
}
|
|
117
|
+
const architecture = {
|
|
118
|
+
...types_1.DEFAULT_ARCHITECTURE_CONFIG,
|
|
119
|
+
...value,
|
|
120
|
+
};
|
|
121
|
+
assertOneOf(architecture.style, "architecture.style", ["modular-monolith"]);
|
|
122
|
+
assertOneOf(architecture.defaultModuleSurface, "architecture.defaultModuleSurface", ["minimal", "crud"]);
|
|
123
|
+
assertOneOf(architecture.defaultApplicationStyle, "architecture.defaultApplicationStyle", ["service", "cqrs"]);
|
|
124
|
+
return {
|
|
125
|
+
style: architecture.style,
|
|
126
|
+
defaultModuleSurface: architecture.defaultModuleSurface,
|
|
127
|
+
defaultApplicationStyle: architecture.defaultApplicationStyle,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function normalizeModules(raw) {
|
|
131
|
+
if (raw === undefined)
|
|
132
|
+
return {};
|
|
133
|
+
if (!isRecord(raw))
|
|
134
|
+
throw new Error(`${CONFIG_FILE}.modules must be a JSON object`);
|
|
135
|
+
const modules = {};
|
|
136
|
+
for (const [name, entry] of Object.entries(raw)) {
|
|
137
|
+
if (!name || name.includes("/") || name.includes("\\")) {
|
|
138
|
+
throw new Error(`${CONFIG_FILE}.modules has invalid module key "${name}" — use the module's Go package name`);
|
|
139
|
+
}
|
|
140
|
+
if (!isRecord(entry))
|
|
141
|
+
throw new Error(`${CONFIG_FILE}.modules.${name} must be a JSON object`);
|
|
142
|
+
assertOneOf(entry.surface, `modules.${name}.surface`, ["minimal", "crud"]);
|
|
143
|
+
assertOneOf(entry.applicationStyle, `modules.${name}.applicationStyle`, ["service", "cqrs"]);
|
|
144
|
+
modules[name] = {
|
|
145
|
+
surface: entry.surface,
|
|
146
|
+
applicationStyle: entry.applicationStyle,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return modules;
|
|
150
|
+
}
|
|
151
|
+
function validateFeatures(features) {
|
|
152
|
+
if (typeof features.docker !== "boolean")
|
|
153
|
+
throw new Error(`${CONFIG_FILE}.features.docker must be true or false`);
|
|
154
|
+
if (typeof features.openapiDocs !== "boolean")
|
|
155
|
+
throw new Error(`${CONFIG_FILE}.features.openapiDocs must be true or false`);
|
|
156
|
+
if (features.worker !== undefined && typeof features.worker !== "boolean") {
|
|
157
|
+
throw new Error(`${CONFIG_FILE}.features.worker must be true or false`);
|
|
158
|
+
}
|
|
159
|
+
if (features.queue !== undefined)
|
|
160
|
+
assertOneOf(features.queue, "features.queue", ["river", "asynq"]);
|
|
161
|
+
if (features.auth !== undefined && typeof features.auth !== "boolean") {
|
|
162
|
+
throw new Error(`${CONFIG_FILE}.features.auth must be true or false`);
|
|
163
|
+
}
|
|
164
|
+
if (features.authStore !== undefined)
|
|
165
|
+
assertOneOf(features.authStore, "features.authStore", ["postgres", "redis"]);
|
|
166
|
+
if (features.rbac !== undefined && typeof features.rbac !== "boolean") {
|
|
167
|
+
throw new Error(`${CONFIG_FILE}.features.rbac must be true or false`);
|
|
168
|
+
}
|
|
169
|
+
if (features.observability !== undefined && typeof features.observability !== "boolean") {
|
|
170
|
+
throw new Error(`${CONFIG_FILE}.features.observability must be true or false`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function requiredString(value, field) {
|
|
174
|
+
if (typeof value !== "string")
|
|
175
|
+
throw new Error(`${CONFIG_FILE}.${field} must be a string`);
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
function assertOneOf(value, field, allowed) {
|
|
179
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
180
|
+
const label = field.startsWith("--") ? field : `${CONFIG_FILE}.${field}`;
|
|
181
|
+
throw new Error(`${label} must be one of: ${allowed.join(", ")} (got "${String(value)}")`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function isRecord(value) {
|
|
185
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
186
|
}
|
|
46
187
|
// detectFeatures answers "what is actually installed here" from the tree
|
|
47
188
|
// alone. Every `add` command leaves a directory or a file behind that nothing
|
|
@@ -107,10 +248,13 @@ function detectConfig(projectDir) {
|
|
|
107
248
|
// already exists"), with no way out. Worse, the first `add` to succeed then
|
|
108
249
|
// wrote a config that recorded the undetected features as absent.
|
|
109
250
|
return {
|
|
251
|
+
schemaVersion: exports.CONFIG_SCHEMA_VERSION,
|
|
110
252
|
projectName: path_1.default.basename(projectDir),
|
|
111
253
|
goModule,
|
|
112
254
|
apiPrefix,
|
|
113
255
|
features: detectFeatures(projectDir),
|
|
256
|
+
architecture: { ...types_1.DEFAULT_ARCHITECTURE_CONFIG },
|
|
257
|
+
modules: {},
|
|
114
258
|
};
|
|
115
259
|
}
|
|
116
260
|
// isProjectDir answers "would readConfig succeed here?" without throwing, so
|
|
@@ -18,26 +18,28 @@ const UNUSED_API_LINE = "_ = api // dropped once `generate module` registers the
|
|
|
18
18
|
// the exact lines patchMainGo inserts for a module — one source of truth so
|
|
19
19
|
// unpatchMainGo removes precisely what patch added.
|
|
20
20
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// from it, which means editing this block by hand — and an inline expression
|
|
25
|
-
// has nowhere to hold the result. With a named variable the edit is "add an
|
|
26
|
-
// argument to the end of line one", and unpatchMainGo can still find the line
|
|
27
|
-
// afterwards because it matches on the `<pkg>Svc :=` prefix, not the whole
|
|
28
|
-
// text.
|
|
21
|
+
// Generated features own their repository/service/handler composition. The
|
|
22
|
+
// API binary supplies infrastructure and security dependencies, then only
|
|
23
|
+
// registers the feature's handler.
|
|
29
24
|
function mainGoLines(patch) {
|
|
30
25
|
const modelAlias = `${patch.pkg}model`; // every domain's model subpackage is named "model"
|
|
31
|
-
const
|
|
32
|
-
const
|
|
26
|
+
const handlerArgs = ["db"];
|
|
27
|
+
const legacyHandlerArgs = [`${patch.pkg}Svc`];
|
|
33
28
|
if (patch.auth)
|
|
34
29
|
handlerArgs.push("cfg.JWTSecret");
|
|
30
|
+
// RBAC's Authz is owned by role/composition.go. wiring.go registers a
|
|
31
|
+
// permission-gated feature with that public capability; it no longer keeps a
|
|
32
|
+
// standalone root variable named `authz`.
|
|
35
33
|
if (patch.permission)
|
|
36
|
-
handlerArgs.push("
|
|
34
|
+
handlerArgs.push("roleComposition.Authz");
|
|
35
|
+
if (patch.auth)
|
|
36
|
+
legacyHandlerArgs.push("cfg.JWTSecret");
|
|
37
|
+
if (patch.permission)
|
|
38
|
+
legacyHandlerArgs.push("roleComposition.Authz");
|
|
37
39
|
return {
|
|
38
40
|
importLine: `"${patch.goModule}/internal/app/${patch.modulePath}"`,
|
|
39
41
|
modelImportLine: `${modelAlias} "${patch.goModule}/internal/app/${patch.modulePath}/model"`,
|
|
40
|
-
//
|
|
42
|
+
// Development schema bootstrap creates tables but not the schema they live in — see
|
|
41
43
|
// the comment on go-scaffold:schemas in main.go.hbs. One Exec per schema,
|
|
42
44
|
// guarded by its own sentinel so two modules sharing a schema name only
|
|
43
45
|
// ever produce one line (not expected today, but cheap to keep safe).
|
|
@@ -48,15 +50,17 @@ function mainGoLines(patch) {
|
|
|
48
50
|
].join("\n"),
|
|
49
51
|
schemaSentinel: `CREATE SCHEMA IF NOT EXISTS ${patch.schemaName}`,
|
|
50
52
|
migrateLine: `&${modelAlias}.${patch.pascalName}{},`,
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
53
|
+
// kept only so undo can clean projects generated before feature-local
|
|
54
|
+
// composition was introduced.
|
|
55
|
+
legacyServicePrefix: `${patch.pkg}Svc :=`,
|
|
56
|
+
legacyRouteLine: `${patch.pkg}.NewHandler(${legacyHandlerArgs.join(", ")}).Register(api)`,
|
|
54
57
|
// `api` is the one route group declared by main.go.hbs, prefixed with
|
|
55
58
|
// whatever apiPrefix the project chose at create time (e.g. /v1, /api,
|
|
56
59
|
// or none) — every module registers on it, there is no per-module choice.
|
|
57
|
-
// `
|
|
58
|
-
// is only ever set once that
|
|
59
|
-
|
|
60
|
+
// `roleComposition.Authz` only exists in main.go once `add rbac` has run —
|
|
61
|
+
// patch.permission is only ever set once that has been verified by the
|
|
62
|
+
// caller.
|
|
63
|
+
routeLine: `${patch.pkg}.NewHandlerFromDB(${handlerArgs.join(", ")}).Register(api)`,
|
|
60
64
|
};
|
|
61
65
|
}
|
|
62
66
|
// assertMainGoPatchable is the pre-flight for it: every marker patchMainGo
|
|
@@ -79,13 +83,13 @@ function assertMainGoPatchable(mainGoPath) {
|
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
// patchMainGo wires a newly generated module into cmd/api/wiring.go: its
|
|
82
|
-
// import, its model in the
|
|
86
|
+
// import, its model in the development bootstrap, and its route registration —
|
|
83
87
|
// via marker comments rather than a Go AST rewrite (ponytail: text insertion
|
|
84
88
|
// at a fixed marker is enough here; reach for go/ast if main.go ever needs
|
|
85
89
|
// edits markers can't express).
|
|
86
90
|
function patchMainGo(mainGoPath, patch) {
|
|
87
91
|
let content = fs_extra_1.default.readFileSync(mainGoPath, "utf8");
|
|
88
|
-
const { importLine, modelImportLine, schemaLines, schemaSentinel, migrateLine,
|
|
92
|
+
const { importLine, modelImportLine, schemaLines, schemaSentinel, migrateLine, routeLine } = mainGoLines(patch);
|
|
89
93
|
// each guarded by its own sentinel so re-running after only the module
|
|
90
94
|
// folder was deleted (main.go still wired) is a no-op, not a dup that
|
|
91
95
|
// panics gin at startup.
|
|
@@ -93,9 +97,6 @@ function patchMainGo(mainGoPath, patch) {
|
|
|
93
97
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, modelImportLine, modelImportLine);
|
|
94
98
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, SCHEMA_MARKER, schemaLines, schemaSentinel);
|
|
95
99
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, MODEL_MARKER, migrateLine, migrateLine);
|
|
96
|
-
// sentinel is the prefix, not the whole line: a re-run must not add a
|
|
97
|
-
// second service line just because the first one gained a dependency.
|
|
98
|
-
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, ROUTE_MARKER, serviceLine, servicePrefix);
|
|
99
100
|
content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, ROUTE_MARKER, routeLine, routeLine);
|
|
100
101
|
content = (0, marker_patch_1.removeLines)(content, [UNUSED_API_LINE]);
|
|
101
102
|
fs_extra_1.default.writeFileSync(mainGoPath, content);
|
|
@@ -105,10 +106,11 @@ function patchMainGo(mainGoPath, patch) {
|
|
|
105
106
|
// main.go still compiles (api would otherwise be declared-and-unused).
|
|
106
107
|
function unpatchMainGo(mainGoPath, patch) {
|
|
107
108
|
let content = fs_extra_1.default.readFileSync(mainGoPath, "utf8");
|
|
108
|
-
const { importLine, modelImportLine, schemaLines, migrateLine,
|
|
109
|
-
content = (0, marker_patch_1.removeLines)(content, [importLine, modelImportLine, migrateLine, routeLine]);
|
|
110
|
-
// by prefix: the service line
|
|
111
|
-
|
|
109
|
+
const { importLine, modelImportLine, schemaLines, migrateLine, legacyServicePrefix, legacyRouteLine, routeLine } = mainGoLines(patch);
|
|
110
|
+
content = (0, marker_patch_1.removeLines)(content, [importLine, modelImportLine, migrateLine, routeLine, legacyRouteLine]);
|
|
111
|
+
// by prefix: remove the named service line from projects generated before
|
|
112
|
+
// composition became feature-local.
|
|
113
|
+
content = (0, marker_patch_1.removeLinesByPrefix)(content, [legacyServicePrefix]);
|
|
112
114
|
// by contiguous block, not removeLines: every module's schema block is
|
|
113
115
|
// identical except the schema name, so a line-by-line removal would also
|
|
114
116
|
// strip another module's matching lines.
|
|
@@ -31,7 +31,13 @@ function ensureImport(content, importPath) {
|
|
|
31
31
|
// the search argument is a plain string, not a regex — collapsing any "$$"
|
|
32
32
|
// that happens to appear in importLine. A function's return value is spliced
|
|
33
33
|
// in literally, so this holds regardless of what importPath contains.
|
|
34
|
-
|
|
34
|
+
if (content.includes("import (\n")) {
|
|
35
|
+
return content.replace(/import \(\n/, () => `import (\n\t${importLine}\n`);
|
|
36
|
+
}
|
|
37
|
+
// A minimal CQRS service has no imports until the first method is added.
|
|
38
|
+
// Create a complete block after the package clause so later generated
|
|
39
|
+
// methods can use the same idempotent path.
|
|
40
|
+
return content.replace(/^(package [^\n]+\n)/, (_match, packageLine) => `${packageLine}\nimport (\n\t${importLine}\n)\n`);
|
|
35
41
|
}
|
|
36
42
|
// insertBeforeMarkerOnce: like insertBeforeMarker but a no-op if `sentinel`
|
|
37
43
|
// already appears in the file. Makes module wiring idempotent — re-running
|