@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.
- 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 +42 -1
- 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/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 +42 -14
- package/templates/create/base/cmd/api/wiring.go.hbs +11 -8
- 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 +35 -13
- package/templates/create/features/docs/patterns.md.hbs +40 -21
- package/templates/create/features/docs/techstack.md.hbs +2 -2
- 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
|
@@ -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
|
+
}
|
package/dist/commands/create.js
CHANGED
|
@@ -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, {
|
|
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.
|
|
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
|
-
`(
|
|
188
|
+
`(use the development bootstrap locally, or \`migrate -path migrations -database "$DB_DSN" up\` before production)`));
|
|
166
189
|
}
|
package/dist/commands/method.js
CHANGED
|
@@ -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"
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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\`
|
|
33
|
-
`(
|
|
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) {
|
package/dist/commands/rbac.js
CHANGED
|
@@ -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⚠
|
|
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
|
|
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)"));
|
package/dist/commands/undo.js
CHANGED
|
@@ -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`);
|
package/dist/commands/worker.js
CHANGED
|
@@ -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);
|