@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/commands/method.js
CHANGED
|
@@ -10,13 +10,13 @@ const picocolors_1 = __importDefault(require("picocolors"));
|
|
|
10
10
|
const config_1 = require("../utils/config");
|
|
11
11
|
const naming_1 = require("../utils/naming");
|
|
12
12
|
const module_location_1 = require("../utils/module-location");
|
|
13
|
-
const method_patcher_1 = require("../utils/method-patcher");
|
|
14
13
|
const gocheck_1 = require("../utils/gocheck");
|
|
15
14
|
const template_renderer_1 = require("../utils/template-renderer");
|
|
16
15
|
const migrations_1 = require("../utils/migrations");
|
|
17
16
|
const openapi_patcher_1 = require("../utils/openapi-patcher");
|
|
18
17
|
const generate_wizard_1 = require("../prompts/generate-wizard");
|
|
19
|
-
|
|
18
|
+
const hexagonal_method_patcher_1 = require("../utils/hexagonal-method-patcher");
|
|
19
|
+
// URL path registered in the inbound adapter, excluding the project-wide API
|
|
20
20
|
// prefix so it can be passed directly to patchOpenapiIndexRaw.
|
|
21
21
|
function methodRoutePath(naming, method, type, getMode, field) {
|
|
22
22
|
const base = `/${naming.plural}`;
|
|
@@ -51,105 +51,116 @@ 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");
|
|
64
64
|
}
|
|
65
|
-
async function
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
async function generateHexagonalMethod(config, naming, methodNameArg, opts, projectDir) {
|
|
66
|
+
const moduleDir = path_1.default.join(projectDir, "internal", "app", naming.pkg);
|
|
67
|
+
const moduleConfig = config.modules[naming.pkg];
|
|
68
|
+
if (!moduleConfig) {
|
|
69
|
+
throw new Error("module " + naming.pkg + " has no split-layout metadata in go-scaffold.config.json; run go-scaffold check first");
|
|
70
|
+
}
|
|
71
|
+
const cqrs = moduleConfig.applicationStyle === "cqrs";
|
|
72
|
+
const authModule = naming.pkg === "user" && fs_extra_1.default.existsSync(path_1.default.join(moduleDir, "application", "contracts.go"));
|
|
70
73
|
const paths = {
|
|
71
|
-
dtoPath: path_1.default.join(moduleDir, "dto.go"),
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
dtoPath: path_1.default.join(moduleDir, "application", "dto.go"),
|
|
75
|
+
requestDTOPath: path_1.default.join(moduleDir, "adapters", "inbound", "http", "dto.go"),
|
|
76
|
+
portsPath: path_1.default.join(moduleDir, "ports", "repository.go"),
|
|
77
|
+
repositoryAdapterPath: path_1.default.join(moduleDir, "adapters", "outbound", "postgres", "repository.go"),
|
|
78
|
+
servicePath: cqrs ? undefined : path_1.default.join(moduleDir, "application", "service.go"),
|
|
79
|
+
commandPath: cqrs ? path_1.default.join(moduleDir, "application", "commands.go") : undefined,
|
|
80
|
+
queryPath: cqrs ? path_1.default.join(moduleDir, "application", "queries.go") : undefined,
|
|
81
|
+
handlerPath: path_1.default.join(moduleDir, "adapters", "inbound", "http", "handler.go"),
|
|
82
|
+
serviceTestPath: path_1.default.join(moduleDir, "application", moduleConfig.applicationStyle === "cqrs" ? "cqrs_test.go" : "service_test.go"),
|
|
83
|
+
...(authModule
|
|
84
|
+
? {
|
|
85
|
+
repositoryModelType: "User",
|
|
86
|
+
repositoryToDomain: "toDomainUser",
|
|
87
|
+
repositoryErrorMapper: "persistenceError",
|
|
88
|
+
repositoryStubReceiver: "f *fakeRepo",
|
|
89
|
+
handlerErrorMapper: "toHTTPError",
|
|
90
|
+
}
|
|
91
|
+
: {}),
|
|
76
92
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
const required = [
|
|
94
|
+
paths.dtoPath,
|
|
95
|
+
paths.requestDTOPath,
|
|
96
|
+
paths.portsPath,
|
|
97
|
+
paths.repositoryAdapterPath,
|
|
98
|
+
paths.handlerPath,
|
|
99
|
+
paths.serviceTestPath,
|
|
100
|
+
...(cqrs ? [paths.commandPath, paths.queryPath] : [paths.servicePath]),
|
|
101
|
+
];
|
|
102
|
+
const missing = required.filter((file) => !fs_extra_1.default.existsSync(file));
|
|
103
|
+
if (missing.length) {
|
|
104
|
+
throw new Error("module " +
|
|
105
|
+
naming.pkg +
|
|
106
|
+
" is not a complete hexagonal split module (missing " +
|
|
107
|
+
missing.map((file) => path_1.default.relative(projectDir, file)).join(", ") +
|
|
108
|
+
"); run go-scaffold check");
|
|
82
109
|
}
|
|
83
110
|
const type = opts.type ?? (await (0, generate_wizard_1.promptMethodType)());
|
|
84
|
-
if (opts.getMode && type !== "get")
|
|
111
|
+
if (opts.getMode && type !== "get")
|
|
85
112
|
throw new Error("--get-mode can only be used with --type get");
|
|
86
|
-
|
|
87
|
-
if (opts.field && !(type === "get" && opts.getMode === "one")) {
|
|
113
|
+
if (opts.field && !(type === "get" && opts.getMode === "one"))
|
|
88
114
|
throw new Error("--field can only be used with --type get --get-mode one");
|
|
89
|
-
}
|
|
90
115
|
const getMode = type === "get" ? opts.getMode ?? (await (0, generate_wizard_1.promptGetMode)()) : undefined;
|
|
91
116
|
const field = type === "get" && getMode === "one" ? opts.field ?? (await (0, generate_wizard_1.promptLookupField)()) : undefined;
|
|
92
|
-
// field becomes a Go param name (`func (...)(ctx, <field> string)`) and a
|
|
93
|
-
// column name in the generated `WHERE <field> = ?`
|
|
94
117
|
if (field)
|
|
95
118
|
(0, naming_1.assertGoIdentifier)((0, naming_1.toCamelCase)(field), "lookup field");
|
|
96
119
|
const method = (0, naming_1.resolveMethodNaming)(methodNameArg ?? (await (0, generate_wizard_1.promptMethodName)()));
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// missing service marker used to leave two files patched and the method
|
|
100
|
-
// permanently un-retryable (assertNotDuplicate then sees it as existing).
|
|
101
|
-
if (!(0, method_patcher_1.markersPresent)(paths.handlerPath, paths.servicePath)) {
|
|
102
|
-
throw new Error(`internal/app/${naming.pkg} is missing the marker comments \`generate method\` patches at.\n` +
|
|
103
|
-
`handler.go and service.go must both still carry their \`// go-scaffold:*\` markers —\n` +
|
|
104
|
-
`restore them, or add this method by hand.`);
|
|
120
|
+
if (!(0, hexagonal_method_patcher_1.hexagonalMarkersPresent)(paths)) {
|
|
121
|
+
throw new Error("internal/app/" + naming.pkg + " is missing a split-layout generate-method marker; restore the go-scaffold markers or edit this endpoint by hand");
|
|
105
122
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// gets the blame on a docs-enabled project and the real cause (pick another
|
|
109
|
-
// method name) is the one thing the message doesn't say.
|
|
110
|
-
(0, method_patcher_1.assertMethodAbsent)(paths, method);
|
|
111
|
-
const docsRelativePath = config.features.openapiDocs
|
|
112
|
-
? `${naming.plural}/methods/${method.pathSegment}.yaml`
|
|
113
|
-
: undefined;
|
|
123
|
+
(0, hexagonal_method_patcher_1.assertHexagonalMethodAbsent)(paths, method);
|
|
124
|
+
const docsRelativePath = config.features.openapiDocs ? naming.plural + "/methods/" + method.pathSegment + ".yaml" : undefined;
|
|
114
125
|
if (docsRelativePath && fs_extra_1.default.existsSync(path_1.default.join(projectDir, "docs", docsRelativePath))) {
|
|
115
|
-
throw new Error(
|
|
126
|
+
throw new Error("OpenAPI method document already exists: docs/" + docsRelativePath);
|
|
116
127
|
}
|
|
117
128
|
const checkBefore = (0, gocheck_1.typeChecks)(projectDir);
|
|
118
|
-
(0,
|
|
129
|
+
(0, hexagonal_method_patcher_1.patchHexagonalMethod)(paths, naming, method, { type, getMode, field }, config.goModule);
|
|
119
130
|
(0, template_renderer_1.gofmtTree)(projectDir);
|
|
120
131
|
(0, gocheck_1.assertNoDrift)(projectDir, checkBefore, config);
|
|
121
|
-
// A --field lookup queries a column the table doesn't have yet. GORM builds
|
|
122
|
-
// that SQL at runtime, so nothing before the first real request notices:
|
|
123
|
-
// the build passes, vet passes, the generated tests pass. Ship the column
|
|
124
|
-
// and its index with the code that needs them.
|
|
125
132
|
let fieldMigration = "";
|
|
126
133
|
if (field) {
|
|
127
134
|
const migrationsDir = path_1.default.join(projectDir, "migrations");
|
|
128
135
|
fieldMigration = (0, migrations_1.newMigrationVersion)(migrationsDir);
|
|
129
136
|
const fieldColumn = (0, naming_1.toDbName)(field);
|
|
130
137
|
await (0, template_renderer_1.applyTemplateEntries)(projectDir, [
|
|
131
|
-
{ template: "generate/module/field-column.up.sql.hbs", output: path_1.default.join("migrations",
|
|
132
|
-
{ template: "generate/module/field-column.down.sql.hbs", output: path_1.default.join("migrations",
|
|
133
|
-
], { ...naming,
|
|
138
|
+
{ template: "generate/module/field-column.up.sql.hbs", output: path_1.default.join("migrations", fieldMigration + "_add_" + naming.tableName + "_" + fieldColumn + ".up.sql") },
|
|
139
|
+
{ template: "generate/module/field-column.down.sql.hbs", output: path_1.default.join("migrations", fieldMigration + "_add_" + naming.tableName + "_" + fieldColumn + ".down.sql") },
|
|
140
|
+
], { ...naming, methodName: method.name, fieldColumn });
|
|
134
141
|
}
|
|
135
142
|
if (fieldMigration) {
|
|
136
|
-
console.log(
|
|
137
|
-
`(adds the column the lookup queries, plus an index on it — check the type before applying)`);
|
|
143
|
+
console.log("migration: migrations/" + fieldMigration + "_add_" + naming.tableName + "_" + (0, naming_1.toDbName)(field) + ".{up,down}.sql (adds the column the lookup queries, plus an index on it; check the type before applying)");
|
|
138
144
|
}
|
|
139
145
|
if (docsRelativePath) {
|
|
140
146
|
const docsPath = path_1.default.join(projectDir, "docs", docsRelativePath);
|
|
141
147
|
fs_extra_1.default.outputFileSync(docsPath, methodOpenapiDocument(naming, method, type, getMode, field));
|
|
142
148
|
(0, openapi_patcher_1.patchOpenapiIndexRaw)(path_1.default.join(projectDir, "docs", "openapi.yaml"), config.apiPrefix, [
|
|
143
|
-
{
|
|
144
|
-
urlPath: methodRoutePath(naming, method, type, getMode, field),
|
|
145
|
-
file: `./${docsRelativePath}`,
|
|
146
|
-
},
|
|
149
|
+
{ urlPath: methodRoutePath(naming, method, type, getMode, field), file: "./" + docsRelativePath },
|
|
147
150
|
]);
|
|
148
151
|
}
|
|
149
|
-
console.log(picocolors_1.default.green(
|
|
150
|
-
console.log(
|
|
151
|
-
if (docsRelativePath)
|
|
152
|
-
console.log(picocolors_1.default.green(
|
|
152
|
+
console.log(picocolors_1.default.green("\nadded \"" + method.name + "\" to internal/app/" + naming.pkg + "/"));
|
|
153
|
+
console.log("route: " + routeHint(naming, method, type, config.apiPrefix, getMode, field));
|
|
154
|
+
if (docsRelativePath)
|
|
155
|
+
console.log(picocolors_1.default.green("docs: docs/" + docsRelativePath + " (wired into docs/openapi.yaml)"));
|
|
156
|
+
const implementationFile = cqrs ? "application/" + (type === "get" ? "queries.go" : "commands.go") : "application/service.go";
|
|
157
|
+
console.log(picocolors_1.default.dim("\nnext: fill in the TODO in " + implementationFile + ", then go build ./... / go test ./..."));
|
|
158
|
+
}
|
|
159
|
+
async function generateMethod(moduleNameArg, methodNameArg, opts, projectDir = process.cwd()) {
|
|
160
|
+
const config = (0, config_1.readConfig)(projectDir);
|
|
161
|
+
const naming = (0, module_location_1.resolveProjectModuleNaming)(projectDir, moduleNameArg ?? (await (0, generate_wizard_1.promptExistingModule)((0, module_location_1.existingModulePackages)(projectDir), "add a method to")));
|
|
162
|
+
if (config.architecture.packageLayout !== "split") {
|
|
163
|
+
throw new Error("this scaffold only supports the canonical hexagonal split layout");
|
|
153
164
|
}
|
|
154
|
-
|
|
165
|
+
await generateHexagonalMethod(config, naming, methodNameArg, opts, projectDir);
|
|
155
166
|
}
|
|
@@ -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
|
}
|
|
@@ -13,6 +13,7 @@ const observability_manifest_1 = require("../templates/observability-manifest");
|
|
|
13
13
|
const observability_patcher_1 = require("../utils/observability-patcher");
|
|
14
14
|
const gocheck_1 = require("../utils/gocheck");
|
|
15
15
|
const gomod_patcher_1 = require("../utils/gomod-patcher");
|
|
16
|
+
const docs_patcher_1 = require("../utils/docs-patcher");
|
|
16
17
|
// addObservability scaffolds Prometheus metrics (GET /metrics) and
|
|
17
18
|
// OpenTelemetry tracing for Gin + GORM, wiring both into the files `create`
|
|
18
19
|
// already wrote. Opt-in and separate from `create`: most projects don't want
|
|
@@ -52,7 +53,7 @@ async function addObservability(projectDir = process.cwd(), opts = {}) {
|
|
|
52
53
|
// docs saying `enabled` while the config it never reached still said false —
|
|
53
54
|
// a disagreement no later command could converge, since the "already added"
|
|
54
55
|
// guard then blocks a re-run.
|
|
55
|
-
const staleDocs =
|
|
56
|
+
const staleDocs = (0, docs_patcher_1.refreshProjectDocs)(projectDir, config, { observability: true });
|
|
56
57
|
(0, config_1.writeConfig)(projectDir, { ...config, features: { ...config.features, observability: true } });
|
|
57
58
|
if (opts.silent)
|
|
58
59
|
return;
|
|
@@ -66,57 +67,7 @@ async function addObservability(projectDir = process.cwd(), opts = {}) {
|
|
|
66
67
|
if (config.features.worker) {
|
|
67
68
|
console.log(picocolors_1.default.yellow("cmd/worker is not instrumented — it initialises no tracer provider, so its spans are dropped and it exposes no /metrics"));
|
|
68
69
|
}
|
|
69
|
-
if (staleDocs.length)
|
|
70
|
-
|
|
71
|
-
// match against today's template, and techstack.md embeds pinned
|
|
72
|
-
// dependency versions — so every release that bumps one makes every
|
|
73
|
-
// project scaffolded before it look edited. Which is exactly the
|
|
74
|
-
// population this function exists for.
|
|
75
|
-
console.log(picocolors_1.default.yellow(`\nnote: couldn't safely rewrite ${staleDocs.join(" and ")} — ` +
|
|
76
|
-
`${staleDocs.length > 1 ? "they don't" : "it doesn't"} match what this go-scaffold\n` +
|
|
77
|
-
` would have generated, so ${staleDocs.length > 1 ? "they were" : "it was"} left alone rather than overwriting your edits.\n` +
|
|
78
|
-
` ${staleDocs.length > 1 ? "They still describe" : "It still describes"} this project as having no metrics or tracing; update by hand.`));
|
|
79
|
-
}
|
|
70
|
+
if (staleDocs.length)
|
|
71
|
+
console.log(picocolors_1.default.yellow((0, docs_patcher_1.docsRefreshWarning)(staleDocs, "add observability")));
|
|
80
72
|
console.log(picocolors_1.default.dim("\nnext: go mod tidy, then set OTEL_EXPORTER_OTLP_ENDPOINT to export traces (empty = tracing no-ops, /metrics works either way)"));
|
|
81
73
|
}
|
|
82
|
-
// The architecture docs gate their observability sections on a `create`-time
|
|
83
|
-
// flag, so adding the feature afterwards used to leave techstack.md saying
|
|
84
|
-
// `disabled` and architecture.md missing the section entirely — while the
|
|
85
|
-
// README promises `create --observability` and this command produce the same
|
|
86
|
-
// project.
|
|
87
|
-
//
|
|
88
|
-
// Re-rendering from the templates rather than string-patching keeps the prose
|
|
89
|
-
// in one place (the .hbs), but would also silently discard a user's edits. So
|
|
90
|
-
// it first renders what the file *should* look like today, with observability
|
|
91
|
-
// still off: only a byte-for-byte match proves nobody has touched it. Returns
|
|
92
|
-
// the docs it declined to overwrite, for the caller to warn about.
|
|
93
|
-
function refreshArchitectDocs(projectDir, config) {
|
|
94
|
-
const docs = [
|
|
95
|
-
{ template: "create/features/docs/architecture.md.hbs", output: path_1.default.join("docs", "architect", "architecture.md") },
|
|
96
|
-
{ template: "create/features/docs/techstack.md.hbs", output: path_1.default.join("docs", "architect", "techstack.md") },
|
|
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 };
|
|
101
|
-
const root = (0, template_renderer_1.getTemplatesRoot)();
|
|
102
|
-
const skipped = [];
|
|
103
|
-
for (const doc of docs) {
|
|
104
|
-
const outputPath = path_1.default.join(projectDir, doc.output);
|
|
105
|
-
if (!fs_extra_1.default.existsSync(outputPath))
|
|
106
|
-
continue;
|
|
107
|
-
const source = fs_extra_1.default.readFileSync(path_1.default.join(root, doc.template), "utf8");
|
|
108
|
-
// compare with line endings normalised — a Windows checkout with
|
|
109
|
-
// core.autocrlf=true would otherwise never match, and the feature would
|
|
110
|
-
// silently never fire there
|
|
111
|
-
const onDisk = lf(fs_extra_1.default.readFileSync(outputPath, "utf8"));
|
|
112
|
-
if (onDisk !== lf((0, template_renderer_1.renderString)(source, { ...base, observability: false }))) {
|
|
113
|
-
skipped.push(doc.output);
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
fs_extra_1.default.writeFileSync(outputPath, (0, template_renderer_1.renderString)(source, { ...base, observability: true }));
|
|
117
|
-
}
|
|
118
|
-
return skipped;
|
|
119
|
-
}
|
|
120
|
-
function lf(text) {
|
|
121
|
-
return text.replace(/\r\n/g, "\n");
|
|
122
|
-
}
|
package/dist/commands/rbac.js
CHANGED
|
@@ -15,6 +15,7 @@ const rbac_patcher_1 = require("../utils/rbac-patcher");
|
|
|
15
15
|
const openapi_patcher_1 = require("../utils/openapi-patcher");
|
|
16
16
|
const golangci_patcher_1 = require("../utils/golangci-patcher");
|
|
17
17
|
const gocheck_1 = require("../utils/gocheck");
|
|
18
|
+
const docs_patcher_1 = require("../utils/docs-patcher");
|
|
18
19
|
// URL (relative to the api prefix) -> docs file (relative to docs/) for every
|
|
19
20
|
// route `add rbac` registers or adds onto the user handler.
|
|
20
21
|
const RBAC_OPENAPI_PATHS = [
|
|
@@ -58,14 +59,14 @@ async function addRbac(projectDir = process.cwd()) {
|
|
|
58
59
|
{ template: "add/rbac/migrations/add_roles.down.sql.hbs", output: path_1.default.join("migrations", `${version}_add_roles.down.sql`) },
|
|
59
60
|
], {});
|
|
60
61
|
(0, rbac_patcher_1.patchConfigForRbac)(path_1.default.join(projectDir, "internal", "shared", "config", "config.go"));
|
|
61
|
-
(0, rbac_patcher_1.patchUserModelForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "
|
|
62
|
+
(0, rbac_patcher_1.patchUserModelForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "adapters", "outbound", "postgres", "model.go"));
|
|
62
63
|
(0, rbac_patcher_1.patchMiddlewareAuthForRbac)(path_1.default.join(projectDir, "internal", "shared", "middleware", "auth.go"));
|
|
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"));
|
|
65
|
-
(0, rbac_patcher_1.patchUserServiceTestForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "service_test.go"));
|
|
66
|
-
(0, rbac_patcher_1.patchUserDTOForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "dto.go"));
|
|
67
|
-
(0, rbac_patcher_1.patchUserHandlerForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "handler.go")
|
|
68
|
-
(0, rbac_patcher_1.patchUserErrorsForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "errors.go"));
|
|
64
|
+
(0, rbac_patcher_1.patchUserJWTForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "application", "jwt.go"));
|
|
65
|
+
(0, rbac_patcher_1.patchUserServiceForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "application", "service.go"), path_1.default.join(projectDir, "internal", "app", "user", "application", "sessions.go"));
|
|
66
|
+
(0, rbac_patcher_1.patchUserServiceTestForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "application", "service_test.go"));
|
|
67
|
+
(0, rbac_patcher_1.patchUserDTOForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "adapters", "inbound", "http", "dto.go"));
|
|
68
|
+
(0, rbac_patcher_1.patchUserHandlerForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "adapters", "inbound", "http", "handler.go"));
|
|
69
|
+
(0, rbac_patcher_1.patchUserErrorsForRbac)(path_1.default.join(projectDir, "internal", "app", "user", "application", "errors.go"));
|
|
69
70
|
(0, golangci_patcher_1.patchGolangciForModule)(path_1.default.join(projectDir, ".golangci.yml"), config.goModule, "role");
|
|
70
71
|
// projects scaffolded before --store existed are all Redis-backed
|
|
71
72
|
(0, rbac_patcher_1.patchMainGoForRbac)(path_1.default.join(projectDir, "cmd", "api", "wiring.go"), config.goModule, config.features.authStore ?? "redis", config.features.worker ?? false);
|
|
@@ -94,12 +95,22 @@ async function addRbac(projectDir = process.cwd()) {
|
|
|
94
95
|
didWhat: "added RBAC",
|
|
95
96
|
recover: "internal/app/role/ and the rbac patches were left in place — reconcile cmd/api/wiring.go by hand.",
|
|
96
97
|
});
|
|
97
|
-
(0,
|
|
98
|
+
const staleDocs = (0, docs_patcher_1.refreshProjectDocs)(projectDir, config, { rbac: true });
|
|
99
|
+
(0, config_1.writeConfig)(projectDir, {
|
|
100
|
+
...config,
|
|
101
|
+
features: { ...config.features, rbac: true },
|
|
102
|
+
modules: {
|
|
103
|
+
...config.modules,
|
|
104
|
+
role: { surface: "crud", applicationStyle: "service", boundary: "hexagonal", packageLayout: "split" },
|
|
105
|
+
},
|
|
106
|
+
});
|
|
98
107
|
console.log(picocolors_1.default.green("\nadded internal/app/role/ and internal/shared/middleware/authz.go"));
|
|
99
108
|
console.log("registered GET /users, GET /users/:id, PATCH /users/:id/set-role, /roles, and /permissions in cmd/api/wiring.go" + docsMessage);
|
|
100
|
-
|
|
109
|
+
if (staleDocs.length)
|
|
110
|
+
console.log(picocolors_1.default.yellow((0, docs_patcher_1.docsRefreshWarning)(staleDocs, "add rbac")));
|
|
111
|
+
console.log(picocolors_1.default.yellow("\n⚠ The development table bootstrap does NOT seed role/permission data — it only creates the\n" +
|
|
101
112
|
" tables from the Go structs. The \"staff\"/\"admin\" roles and their permissions live\n" +
|
|
102
|
-
" in the migration's SQL (INSERT statements), which
|
|
113
|
+
" in the migration's SQL (INSERT statements), which table creation never runs. Without\n" +
|
|
103
114
|
" applying it for real, `make seed` fails with \"unknown role code\" and nobody can be\n" +
|
|
104
115
|
" granted anything. Apply it before relying on RBAC, even in dev:\n" +
|
|
105
116
|
" migrate -path migrations -database \"$DB_DSN\" up (or: make migrate-up)"));
|
package/dist/commands/undo.js
CHANGED
|
@@ -69,13 +69,16 @@ async function undoModule(rawName, opts, projectDir = process.cwd()) {
|
|
|
69
69
|
if (!ok)
|
|
70
70
|
throw new Error("undo cancelled");
|
|
71
71
|
}
|
|
72
|
-
// detect --auth/--permission from the generated handler
|
|
72
|
+
// detect --auth/--permission from the generated handler itself (not
|
|
73
73
|
// stored anywhere else) — unpatchMainGo needs the exact same flags used at
|
|
74
74
|
// generate-time to reconstruct the identical line it's removing.
|
|
75
|
-
const
|
|
75
|
+
const handlerCandidates = [
|
|
76
|
+
path_1.default.join(moduleDir, "adapters", "inbound", "http", "handler.go"),
|
|
77
|
+
];
|
|
78
|
+
const handlerGoPath = handlerCandidates.find((candidate) => fs_extra_1.default.existsSync(candidate));
|
|
76
79
|
let auth;
|
|
77
80
|
let permission;
|
|
78
|
-
if (
|
|
81
|
+
if (handlerGoPath) {
|
|
79
82
|
const handlerContent = fs_extra_1.default.readFileSync(handlerGoPath, "utf8");
|
|
80
83
|
auth = /jwtSecret\s+string/.test(handlerContent);
|
|
81
84
|
permission = handlerContent.match(/h\.authz\.Require\("([^"]+)"\)/)?.[1];
|
|
@@ -123,6 +126,11 @@ async function undoModule(rawName, opts, projectDir = process.cwd()) {
|
|
|
123
126
|
didWhat: `undid module "${naming.pkg}"`,
|
|
124
127
|
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
128
|
});
|
|
129
|
+
if (config.modules[modulePath]) {
|
|
130
|
+
const modules = { ...config.modules };
|
|
131
|
+
delete modules[modulePath];
|
|
132
|
+
(0, config_1.writeConfig)(projectDir, { ...config, modules });
|
|
133
|
+
}
|
|
126
134
|
console.log(picocolors_1.default.green(`\nundid module "${naming.pkg}"`));
|
|
127
135
|
console.log(` deleted internal/app/${modulePath}/`);
|
|
128
136
|
console.log(` un-wired cmd/api/wiring.go`);
|
package/dist/commands/worker.js
CHANGED
|
@@ -14,6 +14,7 @@ const platform_patcher_1 = require("../utils/platform-patcher");
|
|
|
14
14
|
const gocheck_1 = require("../utils/gocheck");
|
|
15
15
|
const gomod_patcher_1 = require("../utils/gomod-patcher");
|
|
16
16
|
const auth_patcher_1 = require("../utils/auth-patcher");
|
|
17
|
+
const docs_patcher_1 = require("../utils/docs-patcher");
|
|
17
18
|
// addWorker scaffolds async job processing: the backend-neutral queue
|
|
18
19
|
// contract (platform/queue), one adapter for the chosen backing store, SMTP
|
|
19
20
|
// mail (platform/mail, with an email:send job as the one thing a fresh
|
|
@@ -35,19 +36,23 @@ async function addWorker(backend, projectDir = process.cwd()) {
|
|
|
35
36
|
(0, platform_patcher_1.patchComposeForRedis)(path_1.default.join(projectDir, "docker-compose.yml"));
|
|
36
37
|
(0, platform_patcher_1.patchCiForRedis)(path_1.default.join(projectDir, ".github", "workflows", "ci.yml"));
|
|
37
38
|
}
|
|
39
|
+
else {
|
|
40
|
+
(0, platform_patcher_1.patchCiForRiver)(path_1.default.join(projectDir, ".github", "workflows", "ci.yml"));
|
|
41
|
+
}
|
|
38
42
|
// pinned to what this scaffold was written against — see gomod-patcher
|
|
39
43
|
(0, gomod_patcher_1.patchGoModRequires)(path_1.default.join(projectDir, "go.mod"), riverQueue
|
|
40
44
|
? ["github.com/riverqueue/river v0.43.0", "github.com/riverqueue/river/riverdriver/riverdatabasesql v0.43.0"]
|
|
41
45
|
: ["github.com/hibiken/asynq v0.26.0", "github.com/redis/go-redis/v9 v9.22.0"]);
|
|
42
46
|
// auth added before the worker wired a synchronous mailer — now that there
|
|
43
47
|
// 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);
|
|
48
|
+
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
49
|
patchEnvExample(path_1.default.join(projectDir, ".env.example"), { redis: !riverQueue });
|
|
46
50
|
patchMakefile(path_1.default.join(projectDir, "Makefile"), { river: riverQueue });
|
|
47
51
|
(0, template_renderer_1.gofmtTree)(projectDir);
|
|
48
52
|
// parse-only: river/asynq aren't in go.mod until the user runs `go mod
|
|
49
53
|
// tidy`, so `go vet` can't be the gate here.
|
|
50
54
|
(0, gocheck_1.assertStillParses)(projectDir, parsedBefore, `added worker (${backend})`);
|
|
55
|
+
const staleDocs = (0, docs_patcher_1.refreshProjectDocs)(projectDir, config, { worker: true, queue: backend });
|
|
51
56
|
(0, config_1.writeConfig)(projectDir, { ...config, features: { ...config.features, worker: true, queue: backend } });
|
|
52
57
|
console.log(picocolors_1.default.green(`\nadded internal/platform/{queue,mail}/ and cmd/worker/ (queue backend: ${backend})`));
|
|
53
58
|
if (riverQueue) {
|
|
@@ -61,6 +66,8 @@ async function addWorker(backend, projectDir = process.cwd()) {
|
|
|
61
66
|
console.log(picocolors_1.default.yellow("note: a Redis enqueue cannot join a database transaction — see the warning on queue.Asynq"));
|
|
62
67
|
console.log(picocolors_1.default.dim("\nnext: make worker (separate terminal, or `make dev` runs both), then go build ./... to confirm"));
|
|
63
68
|
}
|
|
69
|
+
if (staleDocs.length)
|
|
70
|
+
console.log(picocolors_1.default.yellow((0, docs_patcher_1.docsRefreshWarning)(staleDocs, "add worker")));
|
|
64
71
|
}
|
|
65
72
|
// needsSmtp and needsRedis are checked independently: `add auth` run without
|
|
66
73
|
// a worker already writes SMTP_HOST on its own (auth stands alone — see
|
|
@@ -105,15 +112,18 @@ function patchMakefile(makefilePath, opts) {
|
|
|
105
112
|
console.error(picocolors_1.default.yellow(`skipped the Makefile \`dev\`/\`worker\` targets — no \`build:\` target to anchor them to in ${makefilePath}.\nAdd them by hand: \`worker:\` running \`go run ./cmd/worker\`.`));
|
|
106
113
|
return;
|
|
107
114
|
}
|
|
108
|
-
content = content.replace(/^\.PHONY: /m, `.PHONY: dev worker${opts.river ? " river-migrate" : ""} `);
|
|
115
|
+
content = content.replace(/^\.PHONY: /m, `.PHONY: dev worker${opts.river ? " river-migrate river-migrate-test" : ""} `);
|
|
109
116
|
// River keeps its own tables, versioned by River itself rather than by this
|
|
110
|
-
// project's migrations/ directory — run its CLI once per database.
|
|
111
|
-
// by nothing on purpose: it is a one-shot setup command, not a build input.
|
|
117
|
+
// project's migrations/ directory — run its pinned CLI once per database.
|
|
112
118
|
const riverTarget = opts.river
|
|
113
119
|
? "\n# create River's job tables (run once per database, and after upgrading River)\n" +
|
|
114
120
|
"river-migrate:\n" +
|
|
115
121
|
"\t@set -a; [ -f $(ENV_FILE) ] && . ./$(ENV_FILE); set +a; \\\n" +
|
|
116
|
-
'\tgo run github.com/riverqueue/river/cmd/river@
|
|
122
|
+
'\tgo run github.com/riverqueue/river/cmd/river@v0.43.0 migrate-up --line main --database-url "$$DB_DSN"\n' +
|
|
123
|
+
"\n# apply River's own schema to TEST_DB_DSN for the real worker test\n" +
|
|
124
|
+
"river-migrate-test:\n" +
|
|
125
|
+
"\t@set -a; [ -f $(ENV_FILE) ] && . ./$(ENV_FILE); set +a; \\\n" +
|
|
126
|
+
'\tgo run github.com/riverqueue/river/cmd/river@v0.43.0 migrate-up --line main --database-url "$$TEST_DB_DSN"\n'
|
|
117
127
|
: "";
|
|
118
128
|
// Both load config exactly the way Makefile.hbs says every target does:
|
|
119
129
|
// via $(ENV_FILE), and through the same sed that strips trailing comments.
|