@nakedev/go-scaffold 0.1.4 → 0.3.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/LICENSE +21 -0
- package/README.md +133 -44
- package/dist/commands/auth.js +116 -11
- package/dist/commands/create.js +13 -1
- package/dist/commands/generate.js +21 -11
- package/dist/commands/method.js +32 -3
- package/dist/commands/observability.js +114 -0
- package/dist/commands/rbac.js +19 -2
- package/dist/commands/undo.js +331 -0
- package/dist/commands/worker.js +92 -32
- package/dist/index.js +366 -63
- package/dist/prompts/auth-wizard.js +29 -0
- package/dist/prompts/create-wizard.js +5 -2
- package/dist/prompts/generate-wizard.js +57 -0
- package/dist/prompts/worker-wizard.js +25 -0
- package/dist/templates/auth-manifest.js +20 -3
- package/dist/templates/create-manifest.js +13 -20
- package/dist/templates/observability-manifest.js +24 -0
- package/dist/templates/rbac-manifest.js +1 -0
- package/dist/templates/worker-manifest.js +23 -6
- package/dist/utils/auth-patcher.js +96 -21
- package/dist/utils/config.js +58 -10
- package/dist/utils/gocheck.js +57 -5
- package/dist/utils/golangci-patcher.js +73 -0
- package/dist/utils/gomod-patcher.js +53 -0
- package/dist/utils/main-patcher.js +58 -4
- package/dist/utils/marker-patch.js +125 -3
- package/dist/utils/method-patcher.js +17 -2
- package/dist/utils/module-location.js +37 -1
- package/dist/utils/naming.js +50 -2
- package/dist/utils/observability-patcher.js +107 -0
- package/dist/utils/platform-patcher.js +98 -12
- package/dist/utils/rbac-patcher.js +60 -10
- package/package.json +3 -5
- package/templates/add/auth/internal/app/user/errors.go.hbs +7 -0
- package/templates/add/auth/internal/app/user/handler.go.hbs +64 -23
- package/templates/add/auth/internal/app/user/jwt.go.hbs +31 -7
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +39 -0
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +3 -0
- package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +26 -0
- package/templates/add/auth/internal/app/user/model/user.go.hbs +9 -1
- package/templates/add/auth/internal/app/user/repository.go.hbs +64 -11
- package/templates/add/auth/internal/app/user/repository_test.go.hbs +192 -0
- package/templates/add/auth/internal/app/user/service.go.hbs +105 -21
- package/templates/add/auth/internal/app/user/service_test.go.hbs +81 -2
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +9 -138
- package/templates/add/auth/internal/app/user/tokenstore_pg.go.hbs +144 -0
- package/templates/add/auth/internal/app/user/tokenstore_redis.go.hbs +147 -0
- package/templates/add/auth/internal/shared/middleware/ratelimit.go.hbs +21 -19
- package/templates/add/auth/internal/shared/middleware/ratelimit_memory.go.hbs +63 -0
- package/templates/add/auth/internal/shared/middleware/ratelimit_redis.go.hbs +32 -0
- package/templates/add/auth/migrations/create_auth_tokens.down.sql.hbs +1 -0
- package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +16 -0
- package/templates/add/auth/migrations/create_identities.down.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_identities.up.sql.hbs +9 -5
- package/templates/add/auth/migrations/create_login_throttle.down.sql.hbs +1 -0
- package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +10 -0
- package/templates/add/auth/migrations/create_users.down.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_users.up.sql.hbs +13 -2
- package/templates/add/rbac/internal/app/role/dto.go.hbs +8 -3
- package/templates/add/rbac/internal/app/role/handler.go.hbs +4 -1
- package/templates/add/rbac/internal/app/role/model/permission.go.hbs +3 -0
- package/templates/add/rbac/internal/app/role/model/role.go.hbs +4 -0
- package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +3 -0
- package/templates/add/rbac/internal/app/role/repository.go.hbs +12 -11
- package/templates/add/rbac/internal/app/role/repository_test.go.hbs +176 -0
- package/templates/add/rbac/internal/app/role/service.go.hbs +7 -0
- package/templates/add/rbac/internal/shared/middleware/authz.go.hbs +19 -0
- package/templates/add/rbac/internal/shared/middleware/authz_test.go.hbs +1 -1
- package/templates/add/rbac/migrations/add_roles.down.sql.hbs +5 -5
- package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -11
- package/templates/add/worker/cmd/worker/main.go.hbs +30 -24
- package/templates/add/worker/internal/platform/mail/mail.go.hbs +21 -0
- package/templates/add/worker/internal/platform/mail/task.go.hbs +31 -34
- package/templates/add/worker/internal/platform/queue/asynq.go.hbs +140 -0
- package/templates/add/worker/internal/platform/queue/queue.go.hbs +87 -0
- package/templates/add/worker/internal/platform/queue/river.go.hbs +148 -0
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +59 -12
- package/templates/create/base/.dockerignore.hbs +13 -0
- package/templates/create/base/.env.example.hbs +18 -9
- package/templates/create/base/.github/dependabot.yml.hbs +20 -0
- package/templates/create/base/.github/workflows/ci.yml.hbs +12 -2
- package/templates/create/base/.golangci.yml.hbs +27 -0
- package/templates/create/base/AGENTS.md.hbs +8 -4
- package/templates/create/base/Dockerfile.hbs +42 -0
- package/templates/create/base/Makefile.hbs +43 -13
- package/templates/create/base/README.md.hbs +45 -8
- package/templates/create/base/cmd/api/main.go.hbs +17 -130
- package/templates/create/base/cmd/api/wiring.go.hbs +161 -0
- package/templates/create/base/go.mod.hbs +4 -4
- package/templates/create/base/internal/platform/database/database.go.hbs +30 -11
- package/templates/create/base/internal/shared/config/config.go.hbs +13 -7
- package/templates/create/base/internal/shared/pagination/pagination.go.hbs +11 -0
- package/templates/create/base/internal/shared/tx/tx.go.hbs +47 -0
- package/templates/create/base/redocly.yaml.hbs +21 -0
- package/templates/create/features/docs/architecture.md.hbs +32 -11
- package/templates/create/features/docs/openapi.yaml.hbs +0 -4
- package/templates/create/features/docs/patterns.md.hbs +82 -8
- package/templates/create/features/docs/techstack.md.hbs +8 -3
- package/templates/generate/module/dto.go.hbs +8 -1
- package/templates/generate/module/errors.go.hbs +5 -0
- package/templates/generate/module/field-column.down.sql.hbs +2 -0
- package/templates/generate/module/field-column.up.sql.hbs +15 -0
- package/templates/generate/module/handler_test.go.hbs +8 -1
- package/templates/generate/module/migration.down.sql.hbs +3 -1
- package/templates/generate/module/migration.up.sql.hbs +7 -2
- package/templates/generate/module/minimal/dto.go.hbs +3 -1
- package/templates/generate/module/model/model.go.hbs +10 -1
- package/templates/generate/module/permission.up.sql.hbs +3 -1
- package/templates/generate/module/repository.go.hbs +60 -6
- package/templates/generate/module/repository_test.go.hbs +30 -0
- package/templates/generate/module/service.go.hbs +10 -1
- package/templates/generate/module/service_test.go.hbs +45 -0
- package/dist/commands/remove.js +0 -88
- package/scripts/smoke-test.mjs +0 -2058
- package/templates/add/worker/internal/platform/queue/client.go.hbs +0 -31
- package/templates/add/worker/internal/platform/queue/server.go.hbs +0 -68
- package/tests/integration/default-module.test.mjs +0 -46
- package/tests/integration/generator-naming.test.mjs +0 -81
- package/tests/integration/generator-unit-test-seams.test.mjs +0 -91
- package/tests/integration/legacy-method-compat.test.mjs +0 -222
- package/tests/integration/remove-module.test.mjs +0 -58
- package/tests/unit/naming.test.mjs +0 -94
- package/tests/unit/smoke-isolation.test.mjs +0 -35
package/dist/commands/worker.js
CHANGED
|
@@ -11,65 +11,125 @@ const config_1 = require("../utils/config");
|
|
|
11
11
|
const template_renderer_1 = require("../utils/template-renderer");
|
|
12
12
|
const worker_manifest_1 = require("../templates/worker-manifest");
|
|
13
13
|
const platform_patcher_1 = require("../utils/platform-patcher");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const gocheck_1 = require("../utils/gocheck");
|
|
15
|
+
const gomod_patcher_1 = require("../utils/gomod-patcher");
|
|
16
|
+
const auth_patcher_1 = require("../utils/auth-patcher");
|
|
17
|
+
// addWorker scaffolds async job processing: the backend-neutral queue
|
|
18
|
+
// contract (platform/queue), one adapter for the chosen backing store, SMTP
|
|
19
|
+
// mail (platform/mail, with an email:send job as the one thing a fresh
|
|
17
20
|
// worker actually handles), and cmd/worker itself. Opt-in — most projects
|
|
18
|
-
// don't need a queue on day one, and an empty worker with no
|
|
19
|
-
// registered is a stranger scaffold than
|
|
20
|
-
async function addWorker(projectDir = process.cwd()) {
|
|
21
|
+
// don't need a queue on day one, and an empty worker with no job kinds
|
|
22
|
+
// registered is a stranger scaffold than not having one.
|
|
23
|
+
async function addWorker(backend, projectDir = process.cwd()) {
|
|
21
24
|
const config = (0, config_1.readConfig)(projectDir);
|
|
22
25
|
const queueDir = path_1.default.join(projectDir, "internal", "platform", "queue");
|
|
23
26
|
if (fs_extra_1.default.existsSync(queueDir)) {
|
|
24
27
|
throw new Error(`${queueDir} already exists — worker infrastructure looks like it's already been added`);
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
(0,
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const parsedBefore = (0, gocheck_1.parseChecks)(projectDir);
|
|
30
|
+
const riverQueue = backend === "river";
|
|
31
|
+
await (0, template_renderer_1.applyTemplateEntries)(projectDir, (0, worker_manifest_1.workerFiles)(backend), { goModule: config.goModule, riverQueue });
|
|
32
|
+
(0, platform_patcher_1.patchConfigForWorker)(path_1.default.join(projectDir, "internal", "shared", "config", "config.go"), { redis: !riverQueue });
|
|
33
|
+
if (!riverQueue) {
|
|
34
|
+
(0, platform_patcher_1.patchMainGoForWorker)(path_1.default.join(projectDir, "cmd", "api", "wiring.go"), config.goModule);
|
|
35
|
+
(0, platform_patcher_1.patchComposeForRedis)(path_1.default.join(projectDir, "docker-compose.yml"));
|
|
36
|
+
(0, platform_patcher_1.patchCiForRedis)(path_1.default.join(projectDir, ".github", "workflows", "ci.yml"));
|
|
37
|
+
}
|
|
38
|
+
// pinned to what this scaffold was written against — see gomod-patcher
|
|
39
|
+
(0, gomod_patcher_1.patchGoModRequires)(path_1.default.join(projectDir, "go.mod"), riverQueue
|
|
40
|
+
? ["github.com/riverqueue/river v0.43.0", "github.com/riverqueue/river/riverdriver/riverdatabasesql v0.43.0"]
|
|
41
|
+
: ["github.com/hibiken/asynq v0.26.0", "github.com/redis/go-redis/v9 v9.22.0"]);
|
|
42
|
+
// auth added before the worker wired a synchronous mailer — now that there
|
|
43
|
+
// is a queue, move it onto it
|
|
44
|
+
const mailerUpgraded = (0, auth_patcher_1.upgradeMailerToQueue)(path_1.default.join(projectDir, "cmd", "api", "wiring.go"), config.goModule, backend);
|
|
45
|
+
patchEnvExample(path_1.default.join(projectDir, ".env.example"), { redis: !riverQueue });
|
|
46
|
+
patchMakefile(path_1.default.join(projectDir, "Makefile"), { river: riverQueue });
|
|
31
47
|
(0, template_renderer_1.gofmtTree)(projectDir);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
// parse-only: river/asynq aren't in go.mod until the user runs `go mod
|
|
49
|
+
// tidy`, so `go vet` can't be the gate here.
|
|
50
|
+
(0, gocheck_1.assertStillParses)(projectDir, parsedBefore, `added worker (${backend})`);
|
|
51
|
+
(0, config_1.writeConfig)(projectDir, { ...config, features: { ...config.features, worker: true, queue: backend } });
|
|
52
|
+
console.log(picocolors_1.default.green(`\nadded internal/platform/{queue,mail}/ and cmd/worker/ (queue backend: ${backend})`));
|
|
53
|
+
if (riverQueue) {
|
|
54
|
+
console.log("jobs are rows in your Postgres — no extra service, and an enqueue inside tx.Do commits with it");
|
|
55
|
+
console.log(picocolors_1.default.dim("\nnext: make river-migrate (creates River's tables), then make worker"));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(mailerUpgraded
|
|
59
|
+
? "wired Redis into cmd/api (readyz check) — auth's mailer now enqueues onto it instead of blocking on SMTP"
|
|
60
|
+
: "wired Redis into cmd/api (readyz check) — cmd/api does not enqueue anything yet");
|
|
61
|
+
console.log(picocolors_1.default.yellow("note: a Redis enqueue cannot join a database transaction — see the warning on queue.Asynq"));
|
|
62
|
+
console.log(picocolors_1.default.dim("\nnext: make worker (separate terminal, or `make dev` runs both), then go build ./... to confirm"));
|
|
63
|
+
}
|
|
36
64
|
}
|
|
37
|
-
|
|
65
|
+
// needsSmtp and needsRedis are checked independently: `add auth` run without
|
|
66
|
+
// a worker already writes SMTP_HOST on its own (auth stands alone — see
|
|
67
|
+
// addAuth), so by the time a Redis-backed worker arrives after it, the old
|
|
68
|
+
// single `if (content.includes("SMTP_HOST")) return` guard fired on the SMTP
|
|
69
|
+
// block alone and quietly skipped the Redis block right along with it —
|
|
70
|
+
// REDIS_URL never made it into .env.example even though config.go now reads
|
|
71
|
+
// it. Each block gets its own guard so one being done doesn't hide the other.
|
|
72
|
+
function patchEnvExample(envExamplePath, opts) {
|
|
38
73
|
if (!fs_extra_1.default.existsSync(envExamplePath))
|
|
39
74
|
return;
|
|
40
75
|
let content = fs_extra_1.default.readFileSync(envExamplePath, "utf8");
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
76
|
+
const needsRedis = opts.redis && !content.includes("REDIS_URL");
|
|
77
|
+
const needsSmtp = !content.includes("SMTP_HOST");
|
|
78
|
+
if (!needsRedis && !needsSmtp)
|
|
79
|
+
return; // both already added
|
|
80
|
+
content = content.replace(/\n?$/, "\n");
|
|
81
|
+
if (needsRedis) {
|
|
82
|
+
content += "\nREDIS_URL=redis://localhost:6379/0\n";
|
|
83
|
+
}
|
|
84
|
+
if (needsSmtp) {
|
|
85
|
+
content +=
|
|
46
86
|
"\n# leave SMTP_HOST unset to log emails instead of sending them (dev default)\n" +
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
"SMTP_HOST=\n" +
|
|
88
|
+
"SMTP_PORT=587\n" +
|
|
89
|
+
"SMTP_USERNAME=\n" +
|
|
90
|
+
"SMTP_PASSWORD=\n" +
|
|
91
|
+
"SMTP_FROM=no-reply@example.local\n";
|
|
92
|
+
}
|
|
52
93
|
fs_extra_1.default.writeFileSync(envExamplePath, content);
|
|
53
94
|
}
|
|
54
|
-
function patchMakefile(makefilePath) {
|
|
95
|
+
function patchMakefile(makefilePath, opts) {
|
|
55
96
|
if (!fs_extra_1.default.existsSync(makefilePath))
|
|
56
97
|
return;
|
|
57
98
|
let content = fs_extra_1.default.readFileSync(makefilePath, "utf8");
|
|
58
99
|
if (content.includes("\nworker:\n"))
|
|
59
100
|
return; // already added
|
|
60
|
-
content = content.replace(/^\.PHONY: /m,
|
|
101
|
+
content = content.replace(/^\.PHONY: /m, `.PHONY: dev worker${opts.river ? " river-migrate" : ""} `);
|
|
102
|
+
// River keeps its own tables, versioned by River itself rather than by this
|
|
103
|
+
// project's migrations/ directory — run its CLI once per database. Pinned
|
|
104
|
+
// by nothing on purpose: it is a one-shot setup command, not a build input.
|
|
105
|
+
const riverTarget = opts.river
|
|
106
|
+
? "\n# create River's job tables (run once per database, and after upgrading River)\n" +
|
|
107
|
+
"river-migrate:\n" +
|
|
108
|
+
"\t@set -a; [ -f $(ENV_FILE) ] && . ./$(ENV_FILE); set +a; \\\n" +
|
|
109
|
+
'\tgo run github.com/riverqueue/river/cmd/river@latest migrate-up --line main --database-url "$$DB_DSN"\n'
|
|
110
|
+
: "";
|
|
111
|
+
// Both load config exactly the way Makefile.hbs says every target does:
|
|
112
|
+
// via $(ENV_FILE), and through the same sed that strips trailing comments.
|
|
113
|
+
// Without it, .env.example's own `APP_ENV=development # prod: production`
|
|
114
|
+
// reaches `export` as a bare `#` and prints an error on every run.
|
|
115
|
+
const loadEnv = "@set -a; [ -f $(ENV_FILE) ] && . ./$(ENV_FILE); set +a;";
|
|
61
116
|
const targets = "\n# run both API + worker in one terminal — Ctrl+C kills both\n" +
|
|
62
117
|
"dev:\n" +
|
|
63
|
-
|
|
118
|
+
`\t${loadEnv} \\\n` +
|
|
64
119
|
"\t(trap 'kill 0' SIGINT SIGTERM; \\\n" +
|
|
65
120
|
"\t go run ./cmd/api & \\\n" +
|
|
66
121
|
"\t go run ./cmd/worker & \\\n" +
|
|
67
122
|
"\t wait)\n" +
|
|
68
123
|
"\n" +
|
|
69
|
-
|
|
124
|
+
`# background worker for async job processing (email, ...) — requires ${opts.river ? "Postgres (make river-migrate first)" : "Redis"}.\n` +
|
|
70
125
|
"# Use `make dev` to run both in one terminal, or run this in a separate one.\n" +
|
|
71
126
|
"worker:\n" +
|
|
72
|
-
|
|
73
|
-
|
|
127
|
+
`\t${loadEnv} go run ./cmd/worker\n` +
|
|
128
|
+
riverTarget;
|
|
129
|
+
// Function replacer: targets contains literal "$$" (Make's escape for a
|
|
130
|
+
// shell "$") which String.replace would otherwise collapse to a single "$"
|
|
131
|
+
// when the replacement is a plain string — turning "$$DB_DSN" into
|
|
132
|
+
// "$DB_DSN" and silently handing the wrong value to every command below.
|
|
133
|
+
content = content.replace(/\nbuild:/, () => `${targets}\nbuild:`);
|
|
74
134
|
fs_extra_1.default.writeFileSync(makefilePath, content);
|
|
75
135
|
}
|