@nakedev/go-scaffold 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +598 -306
- package/dist/commands/auth.js +65 -23
- package/dist/commands/check.js +281 -0
- package/dist/commands/config.js +50 -0
- package/dist/commands/create.js +33 -2
- package/dist/commands/generate.js +29 -3
- package/dist/commands/method.js +74 -63
- package/dist/commands/migration.js +2 -2
- package/dist/commands/observability.js +4 -53
- package/dist/commands/rbac.js +21 -10
- package/dist/commands/undo.js +11 -3
- package/dist/commands/worker.js +15 -5
- package/dist/index.js +198 -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 +50 -19
- package/dist/templates/create-manifest.js +8 -0
- package/dist/templates/module-manifest.js +84 -26
- package/dist/templates/rbac-manifest.js +16 -11
- package/dist/templates/worker-manifest.js +4 -1
- package/dist/types.js +8 -0
- package/dist/utils/auth-patcher.js +124 -33
- package/dist/utils/config.js +167 -4
- package/dist/utils/docs-patcher.js +68 -0
- package/dist/utils/hexagonal-method-patcher.js +334 -0
- package/dist/utils/main-patcher.js +32 -30
- package/dist/utils/marker-patch.js +7 -1
- package/dist/utils/module-location.js +17 -11
- package/dist/utils/module-profile.js +32 -0
- package/dist/utils/platform-patcher.js +56 -7
- package/dist/utils/rbac-patcher.js +89 -210
- package/package.json +7 -2
- package/templates/add/auth/cmd/seed/main.go.hbs +15 -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/adapters/inbound/http/browser_policy.go.hbs +98 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler.go.hbs +228 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_local.go.hbs +76 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_mfa.go.hbs +83 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_oauth.go.hbs +70 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_recovery.go.hbs +49 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_test.go.hbs +311 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_user.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/session_cookie.go.hbs +35 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs +35 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs +20 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/mfa_store.go.hbs +129 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/mfa_store_test.go.hbs +174 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +84 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +211 -0
- package/templates/add/auth/internal/app/user/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +18 -19
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg.go.hbs +213 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg_test.go.hbs +103 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_recovery.go.hbs +84 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/redis/tokenstore.go.hbs +228 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/redis/tokenstore_test.go.hbs +196 -0
- package/templates/add/auth/internal/app/user/application/contracts.go.hbs +52 -0
- package/templates/add/auth/internal/app/user/application/dto.go.hbs +75 -0
- package/templates/add/auth/internal/app/user/application/errors.go.hbs +62 -0
- package/templates/add/auth/internal/app/user/application/external_login.go.hbs +198 -0
- package/templates/add/auth/internal/app/user/application/jwt.go.hbs +58 -0
- package/templates/add/auth/internal/app/user/application/local_auth.go.hbs +96 -0
- package/templates/add/auth/internal/app/user/application/mfa_service.go.hbs +449 -0
- package/templates/add/auth/internal/app/user/application/mfa_service_test.go.hbs +200 -0
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +132 -0
- package/templates/add/auth/internal/app/user/application/provider_test.go.hbs +285 -0
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +82 -0
- package/templates/add/auth/internal/app/user/application/recovery_service.go.hbs +112 -0
- package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
- package/templates/add/auth/internal/app/user/application/service_test.go.hbs +891 -0
- package/templates/add/auth/internal/app/user/application/sessions.go.hbs +99 -0
- package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
- package/templates/add/auth/internal/app/user/application/user_query.go.hbs +65 -0
- package/templates/add/auth/internal/app/user/composition.go.hbs +168 -0
- package/templates/add/auth/internal/app/user/domain/entity.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/domain/errors.go.hbs +32 -0
- package/templates/add/auth/internal/app/user/ports/password.go.hbs +9 -0
- package/templates/add/auth/internal/app/user/ports/repository.go.hbs +90 -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 +10 -5
- package/templates/add/auth/migrations/create_identities.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_login_throttle.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 +4 -3
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs +142 -0
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs +48 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs +127 -0
- package/templates/add/rbac/internal/app/role/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/add/rbac/internal/app/role/application/dto.go.hbs +47 -0
- package/templates/add/rbac/internal/app/role/application/errors.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/application/service.go.hbs +157 -0
- package/templates/add/rbac/internal/app/role/{service_test.go.hbs → application/service_test.go.hbs} +26 -19
- package/templates/add/rbac/internal/app/role/composition.go.hbs +48 -0
- package/templates/add/rbac/internal/app/role/domain/entity.go.hbs +23 -0
- package/templates/add/rbac/internal/app/role/domain/errors.go.hbs +26 -0
- package/templates/add/rbac/internal/app/role/ports/repository.go.hbs +25 -0
- package/templates/add/rbac/migrations/add_roles.down.sql.hbs +3 -11
- package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -6
- package/templates/add/worker/internal/platform/queue/river_test.go.hbs +84 -0
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +358 -121
- package/templates/create/base/.env.example.hbs +0 -1
- package/templates/create/base/.golangci.yml.hbs +2 -2
- package/templates/create/base/AGENTS.md.hbs +279 -67
- package/templates/create/base/Makefile.hbs +2 -1
- package/templates/create/base/README.md.hbs +115 -32
- package/templates/create/base/cmd/api/wiring.go.hbs +13 -9
- package/templates/create/base/internal/composition/doc.go.hbs +7 -0
- 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 +92 -32
- package/templates/create/features/docs/patterns.md.hbs +137 -91
- package/templates/create/features/docs/techstack.md.hbs +18 -3
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.go.hbs +45 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs +28 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.go.hbs +182 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs +83 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_crud_test.go.hbs +18 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs +30 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs +37 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs +95 -0
- package/templates/generate/module/{repository_test.go.hbs → hexagonal/adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/generate/module/hexagonal/application/commands.crud.go.hbs +54 -0
- package/templates/generate/module/hexagonal/application/commands.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/cqrs_test.go.hbs +66 -0
- package/templates/generate/module/hexagonal/application/dto.go.hbs +35 -0
- package/templates/generate/module/hexagonal/application/dto.minimal.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/queries.crud.go.hbs +33 -0
- package/templates/generate/module/hexagonal/application/queries.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/service.crud.go.hbs +73 -0
- package/templates/generate/module/hexagonal/application/service.go.hbs +29 -0
- package/templates/generate/module/hexagonal/application/service_test.go.hbs +62 -0
- package/templates/generate/module/hexagonal/composition.go.hbs +27 -0
- package/templates/generate/module/hexagonal/domain/entity.go.hbs +20 -0
- package/templates/generate/module/hexagonal/domain/errors.go.hbs +11 -0
- package/templates/generate/module/hexagonal/ports/repository.go.hbs +38 -0
- package/templates/generate/module/migration.up.sql.hbs +1 -1
- package/dist/utils/method-patcher.js +0 -357
- package/templates/add/auth/docs/google-callback.yaml.hbs +0 -22
- package/templates/add/auth/docs/google-login.yaml.hbs +0 -7
- package/templates/add/auth/internal/app/user/dto.go.hbs +0 -77
- package/templates/add/auth/internal/app/user/errors.go.hbs +0 -43
- package/templates/add/auth/internal/app/user/handler.go.hbs +0 -276
- package/templates/add/auth/internal/app/user/jwt.go.hbs +0 -108
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -39
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -31
- package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
- package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -30
- package/templates/add/auth/internal/app/user/repository.go.hbs +0 -137
- package/templates/add/auth/internal/app/user/service.go.hbs +0 -531
- package/templates/add/auth/internal/app/user/service_test.go.hbs +0 -316
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -30
- package/templates/add/auth/internal/app/user/tokenstore_pg.go.hbs +0 -144
- package/templates/add/auth/internal/app/user/tokenstore_redis.go.hbs +0 -147
- package/templates/add/rbac/internal/app/role/dto.go.hbs +0 -45
- package/templates/add/rbac/internal/app/role/errors.go.hbs +0 -39
- package/templates/add/rbac/internal/app/role/handler.go.hbs +0 -104
- package/templates/add/rbac/internal/app/role/model/permission.go.hbs +0 -12
- package/templates/add/rbac/internal/app/role/model/role.go.hbs +0 -22
- package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +0 -11
- package/templates/add/rbac/internal/app/role/repository.go.hbs +0 -97
- package/templates/add/rbac/internal/app/role/service.go.hbs +0 -217
- package/templates/generate/module/dto.go.hbs +0 -36
- package/templates/generate/module/errors.go.hbs +0 -33
- package/templates/generate/module/handler.go.hbs +0 -134
- package/templates/generate/module/handler_test.go.hbs +0 -174
- package/templates/generate/module/minimal/dto.go.hbs +0 -28
- package/templates/generate/module/minimal/handler.go.hbs +0 -48
- package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
- package/templates/generate/module/minimal/service.go.hbs +0 -45
- package/templates/generate/module/minimal/service_test.go.hbs +0 -77
- package/templates/generate/module/model/model.go.hbs +0 -36
- package/templates/generate/module/repository.go.hbs +0 -103
- package/templates/generate/module/service.go.hbs +0 -108
- package/templates/generate/module/service_test.go.hbs +0 -161
package/dist/index.js
CHANGED
|
@@ -20,7 +20,10 @@ const observability_1 = require("./commands/observability");
|
|
|
20
20
|
const worker_wizard_1 = require("./prompts/worker-wizard");
|
|
21
21
|
const auth_wizard_1 = require("./prompts/auth-wizard");
|
|
22
22
|
const generate_wizard_1 = require("./prompts/generate-wizard");
|
|
23
|
-
const config_1 = require("./
|
|
23
|
+
const config_1 = require("./commands/config");
|
|
24
|
+
const check_1 = require("./commands/check");
|
|
25
|
+
const config_2 = require("./utils/config");
|
|
26
|
+
const module_profile_1 = require("./utils/module-profile");
|
|
24
27
|
// fail is every command's catch: one place so the two non-obvious cases stay
|
|
25
28
|
// consistent. @inquirer/prompts throws ExitPromptError on Ctrl-C, and its raw
|
|
26
29
|
// message ("User force closed the prompt with 0 null") tells a user nothing.
|
|
@@ -41,17 +44,31 @@ const program = new commander_1.Command();
|
|
|
41
44
|
program
|
|
42
45
|
.name("go-scaffold")
|
|
43
46
|
.description("Scaffold Gin + GORM + Postgres Go backend projects with a consistent domain-module standard\n\n" +
|
|
44
|
-
"Run `go-scaffold` with no arguments to pick what to do from a menu.
|
|
47
|
+
"Run `go-scaffold` with no arguments to pick what to do from a menu. Interactive commands ask for values you omit; read-only commands (`check`, `config show`, `config validate`) print or check state without prompts.")
|
|
45
48
|
.version((0, version_1.cliVersion)());
|
|
49
|
+
program
|
|
50
|
+
.command("check")
|
|
51
|
+
.description("validate the hexagonal split layout, process composition boundary, layer dependencies, and service/CQRS contract")
|
|
52
|
+
.action(() => {
|
|
53
|
+
try {
|
|
54
|
+
(0, check_1.checkProject)();
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
fail(err);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
46
60
|
program
|
|
47
61
|
.command("create [name]")
|
|
48
62
|
.alias("c")
|
|
49
63
|
.description("scaffold a new project (bare skeleton — add domains with `generate module`)")
|
|
50
|
-
.option("--defaults", "skip
|
|
51
|
-
.option("--no-docker", "
|
|
52
|
-
.option("--no-openapi-docs", "
|
|
53
|
-
.option("--observability", "
|
|
54
|
-
.option("--api-prefix <prefix>", '
|
|
64
|
+
.option("--defaults", "skip settings prompts; use Docker/OpenAPI on, no prefix, and Lean module defaults (still asks for name if omitted)")
|
|
65
|
+
.option("--no-docker", "do not create docker-compose.yml or include a local Postgres service")
|
|
66
|
+
.option("--no-openapi-docs", "do not create docs/openapi.yaml or per-module OpenAPI files")
|
|
67
|
+
.option("--observability", "include Prometheus /metrics and OpenTelemetry tracing; off unless this flag is passed")
|
|
68
|
+
.option("--api-prefix <prefix>", 'group every API route under a prefix such as "v1" or "api/v1"; omit for no prefix')
|
|
69
|
+
.option("--module-profile <profile>", "default profile for future modules: lean, crud, or cqrs; replaces the two profile questions")
|
|
70
|
+
.option("--module-surface <surface>", "legacy axis flag for future modules: minimal or crud; wizard asks when omitted")
|
|
71
|
+
.option("--application-style <style>", "legacy axis flag for future modules: service or cqrs; wizard asks when omitted")
|
|
55
72
|
.action(async (name, opts) => {
|
|
56
73
|
try {
|
|
57
74
|
await (0, create_1.createProject)(name, {
|
|
@@ -60,37 +77,96 @@ program
|
|
|
60
77
|
openapiDocs: opts.openapiDocs,
|
|
61
78
|
observability: opts.observability,
|
|
62
79
|
apiPrefix: opts.apiPrefix,
|
|
80
|
+
moduleProfile: opts.moduleProfile,
|
|
81
|
+
moduleSurface: opts.moduleSurface,
|
|
82
|
+
applicationStyle: opts.applicationStyle,
|
|
63
83
|
});
|
|
64
84
|
}
|
|
65
85
|
catch (err) {
|
|
66
86
|
fail(err);
|
|
67
87
|
}
|
|
68
88
|
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// defaults
|
|
89
|
+
function assertModuleWizardInputs(name, opts, config) {
|
|
90
|
+
if (process.stdin.isTTY)
|
|
91
|
+
return;
|
|
92
|
+
const missing = [];
|
|
93
|
+
if (name === undefined)
|
|
94
|
+
missing.push("<name>");
|
|
95
|
+
// --defaults answers every optional wizard question. The name is still
|
|
96
|
+
// required because there is no safe module name to invent.
|
|
97
|
+
if (!opts.defaults) {
|
|
98
|
+
if (opts.profile === undefined && opts.full === undefined)
|
|
99
|
+
missing.push("--profile or --full/--no-full");
|
|
100
|
+
if (opts.profile === undefined && opts.cqrs === undefined)
|
|
101
|
+
missing.push("--profile or --cqrs");
|
|
102
|
+
if (config.features.auth && opts.auth === undefined) {
|
|
103
|
+
missing.push("--auth (or --defaults for no auth)");
|
|
104
|
+
}
|
|
105
|
+
if (opts.auth && config.features.rbac && opts.permission === undefined) {
|
|
106
|
+
missing.push("--permission <code> (or --defaults for no permission)");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (missing.length > 0) {
|
|
110
|
+
throw new Error(`no interactive terminal to prompt on — \`generate module\` is missing: ${missing.join(", ")}. ` +
|
|
111
|
+
"Pass the missing choices as flags, or add --defaults.");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
76
114
|
async function runModuleWizard(name, opts) {
|
|
77
|
-
let { full, auth, permission } = opts;
|
|
115
|
+
let { full, cqrs, auth, permission } = opts;
|
|
116
|
+
// Read before checking the prompt contract so the error can name the
|
|
117
|
+
// feature-dependent auth/RBAC choices, and so a non-project still fails
|
|
118
|
+
// with the useful project error rather than a list of flags.
|
|
119
|
+
const config = (0, config_2.readConfig)(process.cwd());
|
|
120
|
+
assertModuleWizardInputs(name, opts, config);
|
|
121
|
+
if (opts.profile) {
|
|
122
|
+
const architecture = (0, module_profile_1.architectureForModuleProfile)(opts.profile);
|
|
123
|
+
full = architecture.moduleSurface === "crud";
|
|
124
|
+
cqrs = architecture.applicationStyle === "cqrs";
|
|
125
|
+
}
|
|
78
126
|
if (!opts.defaults) {
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// would present a choice whose only outcome is generateModule's error.
|
|
83
|
-
const config = (0, config_1.readConfig)(process.cwd());
|
|
127
|
+
// auth/permission questions are only asked when the project actually has
|
|
128
|
+
// the features they depend on — offering them otherwise would present a
|
|
129
|
+
// choice whose only outcome is generateModule's error.
|
|
84
130
|
if (name === undefined)
|
|
85
131
|
name = await (0, generate_wizard_1.promptModuleName)();
|
|
86
|
-
if (full === undefined)
|
|
87
|
-
|
|
132
|
+
if (opts.profile === undefined && full === undefined && cqrs === undefined) {
|
|
133
|
+
const profile = await (0, generate_wizard_1.promptModuleProfile)((0, module_profile_1.moduleProfileFor)(config.architecture.defaultModuleSurface, config.architecture.defaultApplicationStyle));
|
|
134
|
+
if (profile === "advanced") {
|
|
135
|
+
const architecture = await (0, generate_wizard_1.promptAdvancedModuleArchitecture)(config.architecture.defaultModuleSurface, config.architecture.defaultApplicationStyle);
|
|
136
|
+
full = architecture.moduleSurface === "crud";
|
|
137
|
+
cqrs = architecture.applicationStyle === "cqrs";
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const architecture = (0, module_profile_1.architectureForModuleProfile)(profile);
|
|
141
|
+
full = architecture.moduleSurface === "crud";
|
|
142
|
+
cqrs = architecture.applicationStyle === "cqrs";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// A legacy axis flag is still an explicit answer. Ask only for the
|
|
147
|
+
// other axis so `--full` never gets silently changed by the profile
|
|
148
|
+
// selector.
|
|
149
|
+
if (full === undefined) {
|
|
150
|
+
full = (await (0, generate_wizard_1.promptModuleSurface)(config.architecture.defaultModuleSurface)) === "crud";
|
|
151
|
+
}
|
|
152
|
+
if (cqrs === undefined) {
|
|
153
|
+
cqrs = (await (0, generate_wizard_1.promptApplicationStyle)(config.architecture.defaultApplicationStyle)) === "cqrs";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
88
156
|
if (auth === undefined && config.features.auth)
|
|
89
157
|
auth = await (0, generate_wizard_1.promptModuleAuth)();
|
|
90
158
|
if (auth && permission === undefined && config.features.rbac)
|
|
91
159
|
permission = await (0, generate_wizard_1.promptModulePermission)();
|
|
92
160
|
}
|
|
93
|
-
|
|
161
|
+
else {
|
|
162
|
+
// --defaults means "use this project's recorded defaults". Fresh projects
|
|
163
|
+
// still resolve to the historical minimal/service behaviour, while a
|
|
164
|
+
// project config wizard can intentionally choose another default for new
|
|
165
|
+
// modules without making CI scripts interactive.
|
|
166
|
+
full ??= config.architecture.defaultModuleSurface === "crud";
|
|
167
|
+
cqrs ??= config.architecture.defaultApplicationStyle === "cqrs";
|
|
168
|
+
}
|
|
169
|
+
await (0, generate_1.generateModule)(name, { full: full ?? false, cqrs: cqrs ?? false, auth, permission });
|
|
94
170
|
}
|
|
95
171
|
// runGenerateWizard is `generate`/`g` run bare — asks which target, then
|
|
96
172
|
// delegates (each subcommand still prompts for anything else it's missing,
|
|
@@ -101,7 +177,7 @@ async function runGenerateWizard() {
|
|
|
101
177
|
const target = await (0, interactive_1.select)({
|
|
102
178
|
message: "What do you want to generate?",
|
|
103
179
|
choices: [
|
|
104
|
-
{ name: "Module (
|
|
180
|
+
{ name: "Module (choose Lean / CRUD / CQRS profile)", value: "module" },
|
|
105
181
|
{ name: "Method (add one endpoint to an existing module)", value: "method" },
|
|
106
182
|
{ name: "Migration (reserve a timestamped up/down SQL file pair)", value: "migration" },
|
|
107
183
|
],
|
|
@@ -119,7 +195,7 @@ async function runGenerateWizard() {
|
|
|
119
195
|
const generate = program
|
|
120
196
|
.command("generate")
|
|
121
197
|
.alias("g")
|
|
122
|
-
.description("add to an existing
|
|
198
|
+
.description("add a module, endpoint, or migration to an existing project; bare `generate` opens a target wizard")
|
|
123
199
|
.action(async () => {
|
|
124
200
|
try {
|
|
125
201
|
await runGenerateWizard();
|
|
@@ -131,21 +207,29 @@ const generate = program
|
|
|
131
207
|
generate
|
|
132
208
|
.command("module [name]")
|
|
133
209
|
.alias("m")
|
|
134
|
-
.description("scaffold a
|
|
135
|
-
.option("--
|
|
210
|
+
.description("scaffold a domain module; the wizard asks for a Lean, CRUD, CQRS, or Advanced profile")
|
|
211
|
+
.option("--profile <profile>", "choose the architecture preset: lean (minimal + service), crud (CRUD + service), or cqrs (minimal + CQRS); auth may still prompt")
|
|
212
|
+
.option("--full", "legacy alias for the CRUD profile: generate list/get/create/update/delete skeletons; fields and rules remain TODO")
|
|
213
|
+
.option("--cqrs", "legacy axis flag: split state-changing commands from read-only queries; combine with --full for CRUD + CQRS")
|
|
136
214
|
// kept working for muscle memory, hidden from help: minimal is already the
|
|
137
215
|
// default, so advertising a flag that asks for it is pure noise.
|
|
138
216
|
.addOption(new commander_1.Option("--no-full", "deprecated compatibility alias; minimal is already the default").hideHelp())
|
|
139
|
-
.option("--auth", "require a valid access token for this module's routes (needs `add auth
|
|
140
|
-
.option("--permission <code>", "also require this permission via authz.Require (needs `add rbac
|
|
141
|
-
.option("--defaults", "skip
|
|
217
|
+
.option("--auth", "require a valid access token for this module's routes (needs `add auth`; omitted means public)")
|
|
218
|
+
.option("--permission <code>", "also require this permission via authz.Require (needs `add rbac` and --auth; e.g. users:manage)")
|
|
219
|
+
.option("--defaults", "skip every wizard question; use recorded project defaults and keep the module public (for CI/scripting)")
|
|
142
220
|
.action(async (name, opts) => {
|
|
143
221
|
try {
|
|
222
|
+
const profile = (0, config_2.parseModuleProfile)(opts.profile);
|
|
223
|
+
if (profile && (opts.full !== undefined || opts.cqrs !== undefined)) {
|
|
224
|
+
throw new Error("--profile cannot be combined with --full, --no-full, or --cqrs — choose one configuration style");
|
|
225
|
+
}
|
|
144
226
|
// anything not passed as a flag gets asked for — declaring both --full
|
|
145
227
|
// and --no-full leaves opts.full undefined when neither is given, which
|
|
146
228
|
// is exactly the "not answered yet" signal runModuleWizard needs.
|
|
147
229
|
await runModuleWizard(name, {
|
|
230
|
+
profile,
|
|
148
231
|
full: opts.full,
|
|
232
|
+
cqrs: opts.cqrs,
|
|
149
233
|
auth: opts.auth,
|
|
150
234
|
permission: opts.permission,
|
|
151
235
|
defaults: opts.defaults,
|
|
@@ -155,13 +239,46 @@ generate
|
|
|
155
239
|
fail(err);
|
|
156
240
|
}
|
|
157
241
|
});
|
|
242
|
+
const configCommand = program
|
|
243
|
+
.command("config")
|
|
244
|
+
.description("configure future module defaults interactively, or inspect/validate the project config")
|
|
245
|
+
.action(async () => {
|
|
246
|
+
try {
|
|
247
|
+
await (0, config_1.configureProject)();
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
fail(err);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
configCommand
|
|
254
|
+
.command("show")
|
|
255
|
+
.description("print the resolved project config, including installed features and module choices; no wizard")
|
|
256
|
+
.action(() => {
|
|
257
|
+
try {
|
|
258
|
+
(0, config_1.showProjectConfig)();
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
fail(err);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
configCommand
|
|
265
|
+
.command("validate")
|
|
266
|
+
.description("validate project config and detected scaffold state without changing files; no wizard")
|
|
267
|
+
.action(() => {
|
|
268
|
+
try {
|
|
269
|
+
(0, config_1.validateProjectConfig)();
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
fail(err);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
158
275
|
generate
|
|
159
276
|
.command("method [module] [name]")
|
|
160
277
|
.alias("me")
|
|
161
|
-
.description("add one endpoint to an existing module
|
|
162
|
-
.option("--type <type>", "get
|
|
163
|
-
.option("--get-mode <mode>", "for
|
|
164
|
-
.option("--field <name>", "
|
|
278
|
+
.description("add one endpoint to an existing module; omitted module, name, and endpoint details are asked by the wizard")
|
|
279
|
+
.option("--type <type>", "HTTP verb: get, post, put, patch, or delete; omitted means the wizard asks")
|
|
280
|
+
.option("--get-mode <mode>", "GET only: all for a list endpoint or one for a lookup by another field")
|
|
281
|
+
.option("--field <name>", "GET one only: lookup column such as email/status/slug; id is already the built-in lookup")
|
|
165
282
|
.action(async (moduleName, methodName, opts) => {
|
|
166
283
|
try {
|
|
167
284
|
const type = opts.type;
|
|
@@ -185,7 +302,7 @@ generate
|
|
|
185
302
|
generate
|
|
186
303
|
.command("migration [name]")
|
|
187
304
|
.alias("mig")
|
|
188
|
-
.description("reserve a timestamped
|
|
305
|
+
.description("reserve a timestamped up/down SQL pair; omitted name opens a migration-name wizard and the SQL remains your responsibility")
|
|
189
306
|
.action(async (name) => {
|
|
190
307
|
try {
|
|
191
308
|
await (0, migration_1.generateMigration)(name);
|
|
@@ -224,7 +341,7 @@ async function runAddWizard() {
|
|
|
224
341
|
// read once, up front: every add command reads it anyway, the summary below
|
|
225
342
|
// needs to know what's already installed (e.g. whether auth's mail goes out
|
|
226
343
|
// inline or through an existing queue), and so does the menu itself.
|
|
227
|
-
const config = (0,
|
|
344
|
+
const config = (0, config_2.readConfig)(process.cwd());
|
|
228
345
|
// Each add is once-only, and rbac needs auth first. Both facts are already
|
|
229
346
|
// known here, so say them in the menu rather than letting someone walk three
|
|
230
347
|
// steps and a confirmation to reach "already been added".
|
|
@@ -265,7 +382,7 @@ async function runAddWizard() {
|
|
|
265
382
|
// all), so the menu has to ask it the same way the worker menu asks for
|
|
266
383
|
// its queue backend — a choice only reachable by knowing the flag name
|
|
267
384
|
// isn't a choice for anyone driving this from the menu.
|
|
268
|
-
await runAddAuth(await (0, auth_wizard_1.promptAuthStore)(), {});
|
|
385
|
+
await runAddAuth(await (0, auth_wizard_1.promptAuthStore)(), await (0, auth_wizard_1.promptBrowserTopology)(), {});
|
|
269
386
|
}
|
|
270
387
|
else if (target === "rbac") {
|
|
271
388
|
await runAddRbac({});
|
|
@@ -283,21 +400,33 @@ async function runAddWorker(backend, opts) {
|
|
|
283
400
|
], opts);
|
|
284
401
|
await (0, worker_1.addWorker)(backend);
|
|
285
402
|
}
|
|
286
|
-
async function runAddAuth(store, opts) {
|
|
287
|
-
const config = (0,
|
|
403
|
+
async function runAddAuth(store, browserTopology, opts) {
|
|
404
|
+
const config = (0, config_2.readConfig)(process.cwd());
|
|
288
405
|
await confirmAdd([
|
|
289
406
|
"add internal/app/user/, internal/shared/middleware/auth.go, and cmd/seed",
|
|
407
|
+
`browser OAuth: frontend-owned callback with server-side provider redirect URI (${browserTopology})`,
|
|
290
408
|
store === "postgres"
|
|
291
|
-
? "
|
|
292
|
-
: picocolors_1.default.yellow("tokens + rate-limit counters: Redis — requires
|
|
409
|
+
? "refresh + recovery tokens: Postgres (user_svc.auth_tokens), rate-limit counters in-process — no extra service"
|
|
410
|
+
: picocolors_1.default.yellow("refresh tokens + rate-limit counters: Redis; recovery tokens: Postgres — requires Redis"),
|
|
293
411
|
config.features.worker
|
|
294
412
|
? "verification/reset mail: queued through the worker already installed"
|
|
295
413
|
: picocolors_1.default.yellow("verification/reset mail: sent inline over SMTP (no worker yet) — /auth/register and /auth/forgot-password block until it's sent"),
|
|
296
414
|
], opts);
|
|
297
|
-
await (0, auth_1.addAuth)(store);
|
|
415
|
+
await (0, auth_1.addAuth)(store, process.cwd(), browserTopology);
|
|
416
|
+
}
|
|
417
|
+
// Explicit browser flags are intentionally resolved before the confirmation
|
|
418
|
+
// summary. `--defaults` is the stable non-TTY escape hatch; `--yes` with no
|
|
419
|
+
// browser flags also uses the local default so existing CI invocations such as
|
|
420
|
+
// `add auth --store postgres --yes` do not unexpectedly start prompting.
|
|
421
|
+
async function resolveBrowserTopology(opts) {
|
|
422
|
+
if (opts.browserTopology !== undefined)
|
|
423
|
+
return (0, auth_wizard_1.validateBrowserTopology)(opts.browserTopology);
|
|
424
|
+
if (opts.defaults || opts.yes)
|
|
425
|
+
return auth_wizard_1.DEFAULT_BROWSER_TOPOLOGY;
|
|
426
|
+
return (0, auth_wizard_1.promptBrowserTopology)();
|
|
298
427
|
}
|
|
299
428
|
async function runAddRbac(opts) {
|
|
300
|
-
const config = (0,
|
|
429
|
+
const config = (0, config_2.readConfig)(process.cwd());
|
|
301
430
|
if (!config.features.auth) {
|
|
302
431
|
throw new Error("`add rbac` requires `add auth` first — there's no Role claim to check permissions against otherwise");
|
|
303
432
|
}
|
|
@@ -305,7 +434,7 @@ async function runAddRbac(opts) {
|
|
|
305
434
|
await (0, rbac_1.addRbac)();
|
|
306
435
|
}
|
|
307
436
|
async function runAddObservability(opts) {
|
|
308
|
-
const config = (0,
|
|
437
|
+
const config = (0, config_2.readConfig)(process.cwd());
|
|
309
438
|
await confirmAdd([
|
|
310
439
|
"add Prometheus /metrics + OpenTelemetry tracing",
|
|
311
440
|
"patch cmd/api/wiring.go and internal/platform/database to wire it in — cmd/api only",
|
|
@@ -317,7 +446,7 @@ async function runAddObservability(opts) {
|
|
|
317
446
|
}
|
|
318
447
|
const add = program
|
|
319
448
|
.command("add")
|
|
320
|
-
.description("add opt-in infrastructure to an existing
|
|
449
|
+
.description("add opt-in infrastructure to an existing project; bare `add` opens a worker/auth/RBAC/observability wizard")
|
|
321
450
|
.action(async () => {
|
|
322
451
|
try {
|
|
323
452
|
await runAddWizard();
|
|
@@ -330,8 +459,8 @@ add
|
|
|
330
459
|
.command("worker")
|
|
331
460
|
.description("add a background job queue, SMTP mail, and cmd/worker (opt-in — most projects don't need this on day one)")
|
|
332
461
|
.option("--queue <backend>", "where jobs are stored: postgres (River, default) or redis (Asynq)")
|
|
333
|
-
.option("--defaults", "skip
|
|
334
|
-
.option("-y, --yes", "skip the confirmation summary")
|
|
462
|
+
.option("--defaults", "skip queue and confirmation prompts; use Postgres/River")
|
|
463
|
+
.option("-y, --yes", "skip only the confirmation summary; an omitted queue still opens the queue wizard")
|
|
335
464
|
.action(async (opts) => {
|
|
336
465
|
try {
|
|
337
466
|
await runAddWorker(await resolveQueueBackend(opts), { yes: opts.yes || opts.defaults });
|
|
@@ -367,8 +496,9 @@ add
|
|
|
367
496
|
.command("auth")
|
|
368
497
|
.description("add email/password auth: JWT access tokens, refresh token rotation, register/login/refresh/logout/me (no prerequisites — without `add worker` the verification/reset mail is sent inline)")
|
|
369
498
|
.option("--store <store>", 'where tokens and rate-limit counters live: "postgres" (default, no extra service) or "redis" (exact across replicas)')
|
|
370
|
-
.option("--
|
|
371
|
-
.option("
|
|
499
|
+
.option("--browser-topology <topology>", "browser deployment topology for cookie/CORS policy: same-origin, same-site (different origin), or cross-site (requires HTTPS deployment)")
|
|
500
|
+
.option("--defaults", "skip store, browser-topology, and confirmation prompts; use Postgres plus local same-site defaults")
|
|
501
|
+
.option("-y, --yes", "skip confirmation; omitted store/topology use local Postgres and same-site defaults")
|
|
372
502
|
.action(async (opts) => {
|
|
373
503
|
try {
|
|
374
504
|
// Same shape as `add worker`: an explicit flag wins, --defaults takes
|
|
@@ -388,7 +518,8 @@ add
|
|
|
388
518
|
else {
|
|
389
519
|
store = await (0, auth_wizard_1.promptAuthStore)();
|
|
390
520
|
}
|
|
391
|
-
|
|
521
|
+
const browserTopology = await resolveBrowserTopology(opts);
|
|
522
|
+
await runAddAuth(store, browserTopology, { yes: opts.yes || opts.defaults });
|
|
392
523
|
}
|
|
393
524
|
catch (err) {
|
|
394
525
|
fail(err);
|
|
@@ -396,8 +527,8 @@ add
|
|
|
396
527
|
});
|
|
397
528
|
add
|
|
398
529
|
.command("rbac")
|
|
399
|
-
.description("add role-based access control: roles/permissions admin API, cached Authz middleware, PATCH /users/:id/set-role (requires `add auth
|
|
400
|
-
.option("-y, --yes", "skip the confirmation summary")
|
|
530
|
+
.description("add role-based access control: roles/permissions admin API, cached Authz middleware, and PATCH /users/:id/set-role (requires `add auth`; direct command only needs confirmation)")
|
|
531
|
+
.option("-y, --yes", "skip the confirmation summary; the bare `add` wizard can select this target")
|
|
401
532
|
.action(async (opts) => {
|
|
402
533
|
try {
|
|
403
534
|
await runAddRbac({ yes: opts.yes });
|
|
@@ -408,8 +539,8 @@ add
|
|
|
408
539
|
});
|
|
409
540
|
add
|
|
410
541
|
.command("observability")
|
|
411
|
-
.description("add Prometheus /metrics + OpenTelemetry tracing for Gin + GORM (also available at
|
|
412
|
-
.option("-y, --yes", "skip the confirmation summary")
|
|
542
|
+
.description("add Prometheus /metrics + OpenTelemetry tracing for Gin + GORM (also available at create time via --observability; direct command only needs confirmation)")
|
|
543
|
+
.option("-y, --yes", "skip the confirmation summary; the bare `add` wizard can select this target")
|
|
413
544
|
.action(async (opts) => {
|
|
414
545
|
try {
|
|
415
546
|
await runAddObservability({ yes: opts.yes });
|
|
@@ -420,7 +551,7 @@ add
|
|
|
420
551
|
});
|
|
421
552
|
const undo = program
|
|
422
553
|
.command("undo")
|
|
423
|
-
.description("undo a
|
|
554
|
+
.description("undo a generated module and its wiring; bare `undo` opens a module selector and confirmation")
|
|
424
555
|
.action(async () => {
|
|
425
556
|
// bare `undo` — module is the only target, so prompt for the name
|
|
426
557
|
try {
|
|
@@ -433,8 +564,8 @@ const undo = program
|
|
|
433
564
|
undo
|
|
434
565
|
.command("module [name]")
|
|
435
566
|
.alias("m")
|
|
436
|
-
.description("delete a generated module and
|
|
437
|
-
.option("-y, --yes", "skip the confirmation prompt")
|
|
567
|
+
.description("delete a generated module, its owned migrations/docs, and the wiring it added; omitted name opens a selector")
|
|
568
|
+
.option("-y, --yes", "skip the destructive confirmation prompt (use only after reviewing the target)")
|
|
438
569
|
.action(async (name, opts) => {
|
|
439
570
|
try {
|
|
440
571
|
await (0, undo_1.undoModule)(name, { yes: opts.yes });
|
|
@@ -449,10 +580,14 @@ undo
|
|
|
449
580
|
// ran on every database created from then on. Kept as an alias rather than
|
|
450
581
|
// deleted outright: a muscle-memory `rm m` shouldn't be an unrecognised-command
|
|
451
582
|
// error, and the semantics only got safer.
|
|
452
|
-
const remove = program
|
|
583
|
+
const remove = program
|
|
584
|
+
.command("remove", { hidden: true })
|
|
585
|
+
.alias("rm")
|
|
586
|
+
.description("deprecated hidden alias for undo; kept for backwards compatibility");
|
|
453
587
|
remove
|
|
454
588
|
.command("module [name]")
|
|
455
589
|
.alias("m")
|
|
590
|
+
.description("deprecated hidden alias for `undo module`; kept for backwards compatibility")
|
|
456
591
|
.option("-y, --yes", "skip the confirmation prompt")
|
|
457
592
|
.action(async (name, opts) => {
|
|
458
593
|
console.error(picocolors_1.default.yellow("`remove module` is now `undo module` — running that instead."));
|
|
@@ -478,13 +613,14 @@ async function runTopMenu() {
|
|
|
478
613
|
// Every entry but "create" needs a project. Offering them outside one buys
|
|
479
614
|
// two selects and then the same error — so say it once, up front, and only
|
|
480
615
|
// offer what can actually run here.
|
|
481
|
-
const inProject = (0,
|
|
616
|
+
const inProject = (0, config_2.isProjectDir)(process.cwd());
|
|
482
617
|
const target = inProject
|
|
483
618
|
? await (0, interactive_1.select)({
|
|
484
619
|
message: "What do you want to do?",
|
|
485
620
|
choices: [
|
|
486
621
|
{ name: "Create a new project", value: "create" },
|
|
487
622
|
{ name: "Generate (module/method/migration in an existing project)", value: "generate" },
|
|
623
|
+
{ name: "Configure project generation defaults", value: "config" },
|
|
488
624
|
{ name: "Add (auth/worker/rbac/observability in an existing project)", value: "add" },
|
|
489
625
|
{ name: "Undo a generated module", value: "undo" },
|
|
490
626
|
],
|
|
@@ -496,6 +632,9 @@ async function runTopMenu() {
|
|
|
496
632
|
else if (target === "generate") {
|
|
497
633
|
await runGenerateWizard();
|
|
498
634
|
}
|
|
635
|
+
else if (target === "config") {
|
|
636
|
+
await (0, config_1.configureProject)();
|
|
637
|
+
}
|
|
499
638
|
else if (target === "add") {
|
|
500
639
|
await runAddWizard();
|
|
501
640
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_BROWSER_TOPOLOGY = void 0;
|
|
3
4
|
exports.promptAuthStore = promptAuthStore;
|
|
5
|
+
exports.validateBrowserTopology = validateBrowserTopology;
|
|
6
|
+
exports.promptBrowserTopology = promptBrowserTopology;
|
|
4
7
|
const interactive_1 = require("./interactive");
|
|
5
|
-
// The one decision `add auth` cannot make for you: where refresh tokens
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// and whether the auth_tokens table exists.
|
|
8
|
+
// The one decision `add auth` cannot make for you: where refresh tokens and
|
|
9
|
+
// rate-limit counters live. Recovery tokens always use the durable Postgres
|
|
10
|
+
// table so consumption can share a transaction with the user update.
|
|
9
11
|
//
|
|
10
12
|
// Mirrors promptQueueBackend: the choice exists as `--store` for scripting,
|
|
11
13
|
// but nobody should have to know the flag name to discover the option.
|
|
@@ -17,13 +19,45 @@ async function promptAuthStore() {
|
|
|
17
19
|
{
|
|
18
20
|
name: "Postgres (user_svc.auth_tokens)",
|
|
19
21
|
value: "postgres",
|
|
20
|
-
description: "no extra service to run;
|
|
22
|
+
description: "no extra service to run; refresh and recovery tokens use your database, and the rate limiter counts in-process",
|
|
21
23
|
},
|
|
22
24
|
{
|
|
23
25
|
name: "Redis",
|
|
24
26
|
value: "redis",
|
|
25
|
-
description: "TTL
|
|
27
|
+
description: "refresh TTL and rate-limit counters are exact across replicas; recovery stays transactional in Postgres; needs Redis",
|
|
26
28
|
},
|
|
27
29
|
],
|
|
28
30
|
});
|
|
29
31
|
}
|
|
32
|
+
exports.DEFAULT_BROWSER_TOPOLOGY = "same-site";
|
|
33
|
+
const TOPOLOGIES = [
|
|
34
|
+
{
|
|
35
|
+
name: "Same-origin",
|
|
36
|
+
value: "same-origin",
|
|
37
|
+
description: "frontend and API share scheme, host, and port; simplest cookie boundary",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "Same-site, different-origin",
|
|
41
|
+
value: "same-site",
|
|
42
|
+
description: "for example localhost:3000 + localhost:8080 or app.example.com + api.example.com",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "Cross-site",
|
|
46
|
+
value: "cross-site",
|
|
47
|
+
description: "different registrable sites; requires SameSite=None, Secure cookies, HTTPS, and exact CORS",
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
function validateBrowserTopology(raw) {
|
|
51
|
+
const value = raw.trim().toLowerCase();
|
|
52
|
+
if (!TOPOLOGIES.some((topology) => topology.value === value)) {
|
|
53
|
+
throw new Error(`Browser topology must be one of: ${TOPOLOGIES.map((topology) => topology.value).join(", ")} (got "${raw}")`);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
async function promptBrowserTopology() {
|
|
58
|
+
return (0, interactive_1.select)({
|
|
59
|
+
message: "How are the browser frontend and API deployed?",
|
|
60
|
+
default: exports.DEFAULT_BROWSER_TOPOLOGY,
|
|
61
|
+
choices: TOPOLOGIES,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -4,6 +4,9 @@ exports.promptProjectName = promptProjectName;
|
|
|
4
4
|
exports.runCreateWizard = runCreateWizard;
|
|
5
5
|
const interactive_1 = require("./interactive");
|
|
6
6
|
const naming_1 = require("../utils/naming");
|
|
7
|
+
const module_profile_1 = require("../utils/module-profile");
|
|
8
|
+
const types_1 = require("../types");
|
|
9
|
+
const generate_wizard_1 = require("./generate-wizard");
|
|
7
10
|
async function promptProjectName() {
|
|
8
11
|
const name = await (0, interactive_1.input)({
|
|
9
12
|
message: "Project name:",
|
|
@@ -49,6 +52,31 @@ async function runCreateWizard(preset = {}) {
|
|
|
49
52
|
if (prefixCheck !== true)
|
|
50
53
|
throw new Error(prefixCheck);
|
|
51
54
|
const apiPrefix = (0, naming_1.normalizeApiPrefix)(apiPrefixRaw);
|
|
55
|
+
let moduleSurface;
|
|
56
|
+
let applicationStyle;
|
|
57
|
+
if (preset.moduleProfile) {
|
|
58
|
+
({ moduleSurface, applicationStyle } = (0, generate_wizard_1.moduleArchitectureForProfile)(preset.moduleProfile));
|
|
59
|
+
}
|
|
60
|
+
else if (preset.moduleSurface !== undefined || preset.applicationStyle !== undefined) {
|
|
61
|
+
// Keep the two old axis flags useful for partial scripted invocations. If
|
|
62
|
+
// only one was supplied, ask only for the other one instead of silently
|
|
63
|
+
// overriding the explicit flag with a profile choice.
|
|
64
|
+
moduleSurface =
|
|
65
|
+
preset.moduleSurface ??
|
|
66
|
+
(await (0, generate_wizard_1.promptModuleSurface)(types_1.DEFAULT_ARCHITECTURE_CONFIG.defaultModuleSurface));
|
|
67
|
+
applicationStyle =
|
|
68
|
+
preset.applicationStyle ??
|
|
69
|
+
(await (0, generate_wizard_1.promptApplicationStyle)(types_1.DEFAULT_ARCHITECTURE_CONFIG.defaultApplicationStyle));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const profile = await (0, generate_wizard_1.promptModuleProfile)((0, module_profile_1.moduleProfileFor)(types_1.DEFAULT_ARCHITECTURE_CONFIG.defaultModuleSurface, types_1.DEFAULT_ARCHITECTURE_CONFIG.defaultApplicationStyle));
|
|
73
|
+
if (profile === "advanced") {
|
|
74
|
+
({ moduleSurface, applicationStyle } = await (0, generate_wizard_1.promptAdvancedModuleArchitecture)());
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
({ moduleSurface, applicationStyle } = (0, generate_wizard_1.moduleArchitectureForProfile)(profile));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
52
80
|
// Everything is listed whether it was asked or passed, so a flag never
|
|
53
81
|
// reaches the project without the caller seeing the value it produced.
|
|
54
82
|
const fromFlag = (passed) => (passed !== undefined ? " (from flag)" : "");
|
|
@@ -57,9 +85,22 @@ async function runCreateWizard(preset = {}) {
|
|
|
57
85
|
console.log(` OpenAPI docs: ${openapiDocs ? "yes" : "no"}${fromFlag(preset.openapiDocs)}`);
|
|
58
86
|
console.log(` Metrics + tracing: ${observability ? "yes" : "no"}${fromFlag(preset.observability)}`);
|
|
59
87
|
console.log(` Route prefix: ${apiPrefix ? `/${apiPrefix}` : "(none)"}${fromFlag(preset.apiPrefix)}`);
|
|
88
|
+
const profile = (0, generate_wizard_1.moduleProfileDescription)(moduleSurface, applicationStyle);
|
|
89
|
+
const profileSource = preset.moduleProfile !== undefined ? " (from --module-profile)" : "";
|
|
90
|
+
console.log(` Default module profile: ${profile}${profileSource}`);
|
|
91
|
+
console.log(` Default module surface: ${moduleSurface}${fromFlag(preset.moduleSurface)}`);
|
|
92
|
+
console.log(` Default application style: ${applicationStyle}${fromFlag(preset.applicationStyle)}`);
|
|
60
93
|
const proceed = await (0, interactive_1.confirm)({ message: "\nCreate project with these settings?", default: true });
|
|
61
94
|
if (!proceed) {
|
|
62
95
|
throw new Error("project creation cancelled");
|
|
63
96
|
}
|
|
64
|
-
return {
|
|
97
|
+
return {
|
|
98
|
+
features: { docker, openapiDocs, observability },
|
|
99
|
+
apiPrefix,
|
|
100
|
+
architecture: {
|
|
101
|
+
...types_1.DEFAULT_ARCHITECTURE_CONFIG,
|
|
102
|
+
defaultModuleSurface: moduleSurface,
|
|
103
|
+
defaultApplicationStyle: applicationStyle,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
65
106
|
}
|