create-svc 0.1.10 → 0.1.11
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 +46 -43
- package/bin/create-service.mjs +2 -0
- package/package.json +12 -9
- package/src/cli.test.ts +28 -10
- package/src/cli.ts +195 -30
- package/src/git-bootstrap.test.ts +40 -0
- package/src/git-bootstrap.ts +110 -0
- package/src/naming.test.ts +1 -0
- package/src/naming.ts +23 -0
- package/src/post-scaffold.test.ts +19 -0
- package/src/post-scaffold.ts +17 -4
- package/src/profiles.ts +2 -5
- package/src/scaffold.test.ts +231 -40
- package/src/scaffold.ts +84 -29
- package/src/vault.test.ts +61 -1
- package/src/vault.ts +77 -15
- package/templates/shared/.github/workflows/ci.yml +2 -1
- package/templates/shared/.github/workflows/deploy.yml +2 -0
- package/templates/shared/README.md +124 -47
- package/templates/shared/grafana/alerts.yaml +54 -0
- package/templates/shared/grafana/waitlist-dashboard.json +63 -0
- package/templates/shared/scripts/authctl.ts +231 -0
- package/templates/shared/scripts/cloudrun/bootstrap.ts +14 -5
- package/templates/shared/scripts/cloudrun/cleanup.ts +64 -4
- package/templates/shared/scripts/cloudrun/cli.ts +324 -7
- package/templates/shared/scripts/cloudrun/config.ts +11 -4
- package/templates/shared/scripts/cloudrun/deploy.ts +0 -4
- package/templates/shared/scripts/cloudrun/lib.ts +174 -41
- package/templates/shared/scripts/cloudrun/neon.ts +45 -0
- package/templates/shared/scripts/dev.ts +22 -0
- package/templates/shared/scripts/ensure-local-db.ts +3 -0
- package/templates/shared/scripts/local-docker.ts +63 -0
- package/templates/shared/scripts/local-env.ts +27 -0
- package/templates/shared/scripts/seed.ts +73 -0
- package/templates/shared/scripts/wait-for-db.ts +32 -0
- package/templates/shared/service.config.ts +59 -0
- package/templates/shared/service.yaml +24 -44
- package/templates/targets/workers/.github/workflows/ci.yml +19 -0
- package/templates/targets/workers/.github/workflows/deploy.yml +19 -0
- package/templates/targets/workers/Makefile +33 -0
- package/templates/targets/workers/README.md +75 -0
- package/templates/targets/workers/package.json +35 -0
- package/templates/targets/workers/scripts/workers/cli.ts +397 -0
- package/templates/targets/workers/src/auth.ts +178 -0
- package/templates/targets/workers/src/index.ts +198 -0
- package/templates/targets/workers/src/storage.ts +370 -0
- package/templates/targets/workers/test/app.test.ts +108 -0
- package/templates/targets/workers/tsconfig.json +11 -0
- package/templates/targets/workers/wrangler.toml +24 -0
- package/templates/variants/bun-connectrpc/Makefile +14 -8
- package/templates/variants/bun-connectrpc/gen/protos/waitlist/v1/waitlist_pb.ts +424 -0
- package/templates/variants/bun-connectrpc/migrations/0000_init.sql +12 -55
- package/templates/variants/bun-connectrpc/package.json +12 -5
- package/templates/variants/bun-connectrpc/protos/waitlist/v1/waitlist.proto +91 -0
- package/templates/variants/bun-connectrpc/scripts/codegen.ts +1 -1
- package/templates/variants/bun-connectrpc/scripts/migrate.ts +4 -1
- package/templates/variants/bun-connectrpc/src/auth.ts +200 -0
- package/templates/variants/bun-connectrpc/src/db/repository.ts +67 -420
- package/templates/variants/bun-connectrpc/src/db/schema.ts +15 -64
- package/templates/variants/bun-connectrpc/src/index.ts +76 -176
- package/templates/variants/bun-connectrpc/src/temporal/activities.ts +14 -0
- package/templates/variants/bun-connectrpc/src/temporal/worker.ts +38 -0
- package/templates/variants/bun-connectrpc/src/temporal/workflows.ts +10 -0
- package/templates/variants/bun-connectrpc/src/waitlist/service.ts +172 -0
- package/templates/variants/bun-connectrpc/src/waitlist/types.ts +45 -0
- package/templates/variants/bun-connectrpc/test/app.test.ts +4 -4
- package/templates/variants/bun-connectrpc/test/waitlist.integration.test.ts +71 -0
- package/templates/variants/bun-hono/Makefile +14 -8
- package/templates/variants/bun-hono/migrations/0000_init.sql +12 -55
- package/templates/variants/bun-hono/package.json +12 -5
- package/templates/variants/bun-hono/scripts/migrate.ts +4 -1
- package/templates/variants/bun-hono/src/auth.ts +181 -0
- package/templates/variants/bun-hono/src/db/repository.ts +68 -421
- package/templates/variants/bun-hono/src/db/schema.ts +15 -64
- package/templates/variants/bun-hono/src/index.ts +65 -180
- package/templates/variants/bun-hono/src/temporal/activities.ts +14 -0
- package/templates/variants/bun-hono/src/temporal/worker.ts +38 -0
- package/templates/variants/bun-hono/src/temporal/workflows.ts +10 -0
- package/templates/variants/bun-hono/src/waitlist/service.ts +166 -0
- package/templates/variants/bun-hono/src/waitlist/types.ts +50 -0
- package/templates/variants/bun-hono/test/app.test.ts +72 -41
- package/templates/variants/bun-hono/test/waitlist.integration.test.ts +102 -0
- package/templates/variants/go-chi/Makefile +27 -11
- package/templates/variants/go-chi/atlas.hcl +8 -0
- package/templates/variants/go-chi/cmd/server/main.go +21 -10
- package/templates/variants/go-chi/go.mod +1 -3
- package/templates/variants/go-chi/internal/app/service.go +202 -685
- package/templates/variants/go-chi/internal/auth/middleware.go +289 -0
- package/templates/variants/go-chi/internal/auth/middleware_test.go +38 -0
- package/templates/variants/go-chi/internal/config/config.go +27 -11
- package/templates/variants/go-chi/internal/httpapi/routes.go +78 -157
- package/templates/variants/go-chi/internal/httpapi/waitlist_integration_test.go +199 -0
- package/templates/variants/go-chi/internal/temporal/activities.go +27 -0
- package/templates/variants/go-chi/internal/temporal/worker.go +42 -0
- package/templates/variants/go-chi/internal/temporal/workflows.go +18 -0
- package/templates/variants/go-chi/migrations/0000_init.sql +12 -55
- package/templates/variants/go-chi/migrations/atlas.sum +2 -0
- package/templates/variants/go-chi/package.json +7 -1
- package/templates/variants/go-connectrpc/Makefile +26 -9
- package/templates/variants/go-connectrpc/atlas.hcl +8 -0
- package/templates/variants/go-connectrpc/buf.gen.yaml +2 -2
- package/templates/variants/go-connectrpc/cmd/server/main.go +23 -12
- package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlist.pb.go +960 -0
- package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlistv1connect/waitlist.connect.go +283 -0
- package/templates/variants/go-connectrpc/go.mod +1 -1
- package/templates/variants/go-connectrpc/internal/app/service.go +202 -685
- package/templates/variants/go-connectrpc/internal/auth/middleware.go +289 -0
- package/templates/variants/go-connectrpc/internal/auth/middleware_test.go +38 -0
- package/templates/variants/go-connectrpc/internal/config/config.go +27 -11
- package/templates/variants/go-connectrpc/internal/connectapi/handler.go +78 -201
- package/templates/variants/go-connectrpc/internal/connectapi/waitlist_integration_test.go +122 -0
- package/templates/variants/go-connectrpc/internal/httpapi/routes.go +147 -9
- package/templates/variants/go-connectrpc/internal/temporal/activities.go +27 -0
- package/templates/variants/go-connectrpc/internal/temporal/worker.go +42 -0
- package/templates/variants/go-connectrpc/internal/temporal/workflows.go +18 -0
- package/templates/variants/go-connectrpc/migrations/0000_init.sql +12 -55
- package/templates/variants/go-connectrpc/migrations/atlas.sum +2 -0
- package/templates/variants/go-connectrpc/package.json +7 -1
- package/templates/variants/go-connectrpc/protos/waitlist/v1/waitlist.proto +93 -0
- package/templates/root/.github/workflows/buf-publish.yml +0 -19
- package/templates/root/.github/workflows/ci.yml +0 -26
- package/templates/root/.github/workflows/deploy.yml +0 -22
- package/templates/root/Dockerfile +0 -23
- package/templates/root/README.md +0 -69
- package/templates/root/buf.gen.yaml +0 -10
- package/templates/root/buf.yaml +0 -9
- package/templates/root/cmd/server/main.go +0 -44
- package/templates/root/gen/dns/v1/dns.pb.go +0 -623
- package/templates/root/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
- package/templates/root/go.mod +0 -10
- package/templates/root/internal/app/service.go +0 -152
- package/templates/root/internal/app/token_source.go +0 -50
- package/templates/root/internal/cloudflare/client.go +0 -160
- package/templates/root/internal/config/config.go +0 -55
- package/templates/root/internal/connectapi/handler.go +0 -79
- package/templates/root/internal/httpapi/routes.go +0 -93
- package/templates/root/internal/vault/client.go +0 -148
- package/templates/root/package.json +0 -12
- package/templates/root/protos/dns/v1/dns.proto +0 -58
- package/templates/root/scripts/cloudrun/bootstrap.ts +0 -65
- package/templates/root/scripts/cloudrun/config.ts +0 -50
- package/templates/root/scripts/cloudrun/deploy.ts +0 -41
- package/templates/root/scripts/cloudrun/lib.ts +0 -244
- package/templates/root/service.yaml +0 -50
- package/templates/root/test/go.test.ts +0 -19
- package/templates/shared/scripts/cloudrun/integrations.ts +0 -111
- package/templates/variants/bun-connectrpc/gen/protos/chat/v1/chat_pb.ts +0 -1078
- package/templates/variants/bun-connectrpc/protos/chat/v1/chat.proto +0 -228
- package/templates/variants/bun-connectrpc/src/chat/service.ts +0 -384
- package/templates/variants/bun-connectrpc/src/chat/types.ts +0 -142
- package/templates/variants/bun-connectrpc/src/storage.ts +0 -72
- package/templates/variants/bun-connectrpc/src/webhooks.ts +0 -35
- package/templates/variants/bun-connectrpc/test/list-messages.integration.test.ts +0 -182
- package/templates/variants/bun-hono/src/chat/service.ts +0 -384
- package/templates/variants/bun-hono/src/chat/types.ts +0 -142
- package/templates/variants/bun-hono/src/storage.ts +0 -72
- package/templates/variants/bun-hono/src/webhooks.ts +0 -35
- package/templates/variants/bun-hono/test/list-messages.integration.test.ts +0 -256
- package/templates/variants/go-chi/buf.gen.yaml +0 -12
- package/templates/variants/go-chi/buf.yaml +0 -9
- package/templates/variants/go-chi/cmd/migrate/main.go +0 -101
- package/templates/variants/go-chi/internal/httpapi/list_messages_integration_test.go +0 -298
- package/templates/variants/go-chi/protos/chat/v1/chat.proto +0 -219
- package/templates/variants/go-connectrpc/cmd/migrate/main.go +0 -101
- package/templates/variants/go-connectrpc/gen/chat/v1/chat.pb.go +0 -2512
- package/templates/variants/go-connectrpc/gen/chat/v1/chatv1connect/chat.connect.go +0 -571
- package/templates/variants/go-connectrpc/internal/connectapi/list_messages_integration_test.go +0 -216
- package/templates/variants/go-connectrpc/protos/chat/v1/chat.proto +0 -232
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import serviceConfig from "../service.config";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
type CommandResult = {
|
|
5
|
+
success: boolean;
|
|
6
|
+
stdout: string;
|
|
7
|
+
stderr: string;
|
|
8
|
+
exitCode: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const decoder = new TextDecoder();
|
|
12
|
+
|
|
13
|
+
export type AuthDoctorResult = {
|
|
14
|
+
hasAuthctl: boolean;
|
|
15
|
+
hasResourceServerCommands: boolean;
|
|
16
|
+
detail: string;
|
|
17
|
+
resourceServerCommand?: ResourceServerCommand;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ResourceServerCommand = {
|
|
21
|
+
subject: string;
|
|
22
|
+
mutationAction?: "upsert" | "create";
|
|
23
|
+
actions: string[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ResourceServerMutationCommand = ResourceServerCommand & {
|
|
27
|
+
mutationAction: "upsert" | "create";
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function defaultAuthResourceServerArgs() {
|
|
31
|
+
const auth = serviceConfig.auth;
|
|
32
|
+
return [
|
|
33
|
+
"--resource-server",
|
|
34
|
+
auth.resource_server.id,
|
|
35
|
+
"--audience",
|
|
36
|
+
auth.resource_server.audience,
|
|
37
|
+
"--stage",
|
|
38
|
+
serviceConfig.stage_default,
|
|
39
|
+
...auth.resource_server.default_scopes.flatMap((scope) => ["--scope", scope]),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function runAuthCommand(args: string[]) {
|
|
44
|
+
const [subject, action, ...rest] = args;
|
|
45
|
+
|
|
46
|
+
if (!subject || subject === "doctor") {
|
|
47
|
+
const result = runAuthDoctor();
|
|
48
|
+
if (!result.hasAuthctl) {
|
|
49
|
+
throw new Error(result.detail);
|
|
50
|
+
}
|
|
51
|
+
return result.detail;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (subject === "resource-server" || subject === "resource-servers") {
|
|
55
|
+
const command = resolveResourceServerCommand();
|
|
56
|
+
if (!command) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"authctl is installed but does not expose resource-server commands; install @anmho/authctl@0.1.1 or newer before managing auth resource servers"
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (action === "get" || action === "list") {
|
|
62
|
+
if (!command.actions.includes(action)) {
|
|
63
|
+
throw new Error(`authctl ${command.subject} does not expose ${action}`);
|
|
64
|
+
}
|
|
65
|
+
authctl([command.subject, action, ...rest]);
|
|
66
|
+
return `Auth resource server ${action} finished`;
|
|
67
|
+
}
|
|
68
|
+
const mutation = ensureResourceServerCommandAvailable();
|
|
69
|
+
const subcommand = action ?? mutation.mutationAction;
|
|
70
|
+
if (!mutation.mutationAction || (subcommand !== mutation.mutationAction && !(subcommand === "upsert" && mutation.mutationAction === "create"))) {
|
|
71
|
+
throw new Error(`Usage: service auth resource-server [${mutation.mutationAction}] [authctl args]`);
|
|
72
|
+
}
|
|
73
|
+
authctl([mutation.subject, mutation.mutationAction, ...defaultAuthResourceServerArgs(), "--json", ...rest]);
|
|
74
|
+
return `Auth resource server ready: ${serviceConfig.auth.resource_server.id}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (subject === "client" || subject === "clients") {
|
|
78
|
+
return runClientCommand(action, rest);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
throw new Error("Usage: service auth <doctor|resource-server|client> [args]");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function ensureAuthResourceServer() {
|
|
85
|
+
const command = ensureResourceServerCommandAvailable();
|
|
86
|
+
authctl([command.subject, command.mutationAction, ...defaultAuthResourceServerArgs(), "--json"]);
|
|
87
|
+
return `Auth resource server ready: ${serviceConfig.auth.resource_server.audience}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function runAuthDoctor(): AuthDoctorResult {
|
|
91
|
+
if (!authctlPath()) {
|
|
92
|
+
return {
|
|
93
|
+
hasAuthctl: false,
|
|
94
|
+
hasResourceServerCommands: false,
|
|
95
|
+
detail: "authctl is not installed; run bun install in this generated service or link @anmho/authctl before service create",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const doctor = authctl(["doctor", "--json"], { allowFailure: true, quiet: true });
|
|
100
|
+
const resourceServerCommand = resolveResourceServerCommand();
|
|
101
|
+
const hasResourceServerCommands = Boolean(resourceServerCommand?.mutationAction);
|
|
102
|
+
|
|
103
|
+
if (!doctor.success) {
|
|
104
|
+
return {
|
|
105
|
+
hasAuthctl: true,
|
|
106
|
+
hasResourceServerCommands,
|
|
107
|
+
resourceServerCommand,
|
|
108
|
+
detail: `authctl doctor failed: ${doctor.stderr || doctor.stdout}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!hasResourceServerCommands) {
|
|
113
|
+
return {
|
|
114
|
+
hasAuthctl: true,
|
|
115
|
+
hasResourceServerCommands: false,
|
|
116
|
+
resourceServerCommand,
|
|
117
|
+
detail:
|
|
118
|
+
"authctl is installed but does not expose resource-server upsert/create; install @anmho/authctl@0.1.1 or newer before service create",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
hasAuthctl: true,
|
|
124
|
+
hasResourceServerCommands: true,
|
|
125
|
+
resourceServerCommand,
|
|
126
|
+
detail: `authctl ready for ${serviceConfig.auth.resource_server.id}`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function runClientCommand(action = "", rest: string[]) {
|
|
131
|
+
if (action === "create") {
|
|
132
|
+
authctl([
|
|
133
|
+
"clients",
|
|
134
|
+
"create",
|
|
135
|
+
"--client-app",
|
|
136
|
+
serviceConfig.auth.client.app_id,
|
|
137
|
+
"--client-identity",
|
|
138
|
+
serviceConfig.auth.client.identity,
|
|
139
|
+
...defaultClientTargetArgs(rest),
|
|
140
|
+
"--stage",
|
|
141
|
+
serviceConfig.stage_default,
|
|
142
|
+
"--yes",
|
|
143
|
+
"--json",
|
|
144
|
+
...rest,
|
|
145
|
+
]);
|
|
146
|
+
return "Auth client created";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (["list", "get", "rotate", "revoke"].includes(action)) {
|
|
150
|
+
authctl(["clients", action, ...rest]);
|
|
151
|
+
return `Auth client ${action} finished`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
throw new Error("Usage: service auth client <create|list|get|rotate|revoke> [args]");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function defaultClientTargetArgs(rest: string[]) {
|
|
158
|
+
const hasResourceServer = hasFlag(rest, "--resource-server");
|
|
159
|
+
const hasScope = hasFlag(rest, "--scope");
|
|
160
|
+
return [
|
|
161
|
+
...(hasResourceServer ? [] : ["--resource-server", serviceConfig.auth.resource_server.id]),
|
|
162
|
+
...(hasScope ? [] : serviceConfig.auth.resource_server.default_scopes.flatMap((scope) => ["--scope", scope])),
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function hasFlag(args: string[], name: string) {
|
|
167
|
+
return args.some((arg) => arg === name || arg.startsWith(`${name}=`));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function ensureResourceServerCommandAvailable(): ResourceServerMutationCommand {
|
|
171
|
+
const doctor = runAuthDoctor();
|
|
172
|
+
if (!doctor.hasAuthctl || !doctor.hasResourceServerCommands) {
|
|
173
|
+
throw new Error(doctor.detail);
|
|
174
|
+
}
|
|
175
|
+
if (!doctor.resourceServerCommand?.mutationAction) {
|
|
176
|
+
throw new Error("authctl resource-server command discovery failed");
|
|
177
|
+
}
|
|
178
|
+
return doctor.resourceServerCommand as ResourceServerMutationCommand;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function resolveResourceServerCommand(): ResourceServerCommand | undefined {
|
|
182
|
+
for (const subject of ["resource-servers", "resource-server", "resources"]) {
|
|
183
|
+
const help = authctl([subject, "--help"], { allowFailure: true, quiet: true });
|
|
184
|
+
const output = `${help.stdout}\n${help.stderr}`;
|
|
185
|
+
if (!help.success || !output.includes(subject)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const actions = ["upsert", "create", "get", "list"].filter((candidate) => output.includes(candidate));
|
|
189
|
+
const mutationAction = actions.includes("upsert") ? "upsert" : actions.includes("create") ? "create" : undefined;
|
|
190
|
+
if (actions.length > 0) {
|
|
191
|
+
return { subject, mutationAction, actions };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function authctl(args: string[], options: { allowFailure?: boolean; quiet?: boolean } = {}): CommandResult {
|
|
198
|
+
const command = authctlPath();
|
|
199
|
+
if (!command) {
|
|
200
|
+
throw new Error("authctl is not installed; run bun install in this generated service or link @anmho/authctl");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = Bun.spawnSync([command, ...args], {
|
|
204
|
+
cwd: process.cwd(),
|
|
205
|
+
env: process.env,
|
|
206
|
+
stdin: "inherit",
|
|
207
|
+
stdout: "pipe",
|
|
208
|
+
stderr: "pipe",
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const output = {
|
|
212
|
+
success: result.success,
|
|
213
|
+
stdout: result.stdout ? decoder.decode(result.stdout).trim() : "",
|
|
214
|
+
stderr: result.stderr ? decoder.decode(result.stderr).trim() : "",
|
|
215
|
+
exitCode: result.exitCode,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
if (!output.success && !options.allowFailure) {
|
|
219
|
+
throw new Error(`authctl ${args.join(" ")} failed with exit code ${output.exitCode}\n${output.stderr || output.stdout}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (output.stdout && !options.quiet) {
|
|
223
|
+
console.log(output.stdout);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return output;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function authctlPath() {
|
|
230
|
+
return existsSync("./node_modules/.bin/authctl") ? "./node_modules/.bin/authctl" : Bun.which("authctl");
|
|
231
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { config } from "./config";
|
|
2
|
-
import { publishProviderRuntimeSecrets } from "./integrations";
|
|
3
2
|
import { ensureDatabase, getConnectionUri, resolveNeonConfig } from "./neon";
|
|
4
3
|
import {
|
|
5
4
|
addSecretVersion,
|
|
@@ -9,11 +8,11 @@ import {
|
|
|
9
8
|
ensureProjectRole,
|
|
10
9
|
ensureSecretAccessor,
|
|
11
10
|
ensureServiceAccount,
|
|
12
|
-
ensureStorageBucket,
|
|
13
11
|
gcloud,
|
|
14
12
|
requireCommand,
|
|
15
13
|
requireGcloudAuth,
|
|
16
14
|
resolveDeploymentTarget,
|
|
15
|
+
resolveTemporalRuntimeConfig,
|
|
17
16
|
runMain,
|
|
18
17
|
runStep,
|
|
19
18
|
} from "./lib";
|
|
@@ -31,8 +30,6 @@ export async function bootstrap() {
|
|
|
31
30
|
});
|
|
32
31
|
|
|
33
32
|
await runStep("Ensuring Artifact Registry repository", () => ensureArtifactRepository());
|
|
34
|
-
await runStep("Ensuring attachment storage bucket", () => ensureStorageBucket());
|
|
35
|
-
|
|
36
33
|
await runStep("Granting project roles", () => {
|
|
37
34
|
ensureProjectRole(`serviceAccount:${config.runtimeServiceAccount}`, "roles/secretmanager.secretAccessor");
|
|
38
35
|
});
|
|
@@ -53,7 +50,19 @@ export async function bootstrap() {
|
|
|
53
50
|
ensureSecretAccessor(target.databaseSecretName, `serviceAccount:${config.runtimeServiceAccount}`);
|
|
54
51
|
});
|
|
55
52
|
|
|
56
|
-
await runStep("Publishing
|
|
53
|
+
await runStep("Publishing Temporal secrets", () => publishTemporalSecrets());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function publishTemporalSecrets() {
|
|
57
|
+
const temporal = resolveTemporalRuntimeConfig();
|
|
58
|
+
const apiKey = process.env.TEMPORAL_API_KEY?.trim();
|
|
59
|
+
if (!apiKey || !temporal.apiKeySecretName) {
|
|
60
|
+
return "No Temporal API key configured";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
addSecretVersion(temporal.apiKeySecretName, apiKey);
|
|
64
|
+
ensureSecretAccessor(temporal.apiKeySecretName, `serviceAccount:${config.runtimeServiceAccount}`);
|
|
65
|
+
return temporal.apiKeySecretName;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
if (import.meta.main) {
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { log } from "@clack/prompts";
|
|
1
|
+
import { confirm, isCancel, log } from "@clack/prompts";
|
|
2
2
|
import { config } from "./config";
|
|
3
3
|
import { deleteBranch, deleteDatabase, listBranches, resolveNeonConfig } from "./neon";
|
|
4
4
|
import {
|
|
5
|
+
assertOwnedResource,
|
|
5
6
|
deleteProject,
|
|
6
7
|
deleteProductionDomainMapping,
|
|
7
8
|
deleteSecret,
|
|
8
9
|
deleteService,
|
|
9
10
|
deleteServiceAccount,
|
|
11
|
+
describeCloudRunService,
|
|
12
|
+
describeProductionDomainMapping,
|
|
13
|
+
describeSecret,
|
|
10
14
|
listCloudRunServices,
|
|
11
15
|
listSecrets,
|
|
12
16
|
parseCleanupArgs,
|
|
13
17
|
requireCommand,
|
|
14
18
|
requireGcloudAuth,
|
|
19
|
+
run,
|
|
15
20
|
runMain,
|
|
16
21
|
runStep,
|
|
17
22
|
} from "./lib";
|
|
@@ -21,7 +26,12 @@ function matchesServiceResource(name: string) {
|
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
function matchesSecretResource(name: string) {
|
|
24
|
-
return
|
|
29
|
+
return (
|
|
30
|
+
name === `${config.serviceName}-database-url` ||
|
|
31
|
+
name === config.temporal.apiKeySecretName ||
|
|
32
|
+
name.startsWith(`${config.serviceName}-pr-`) ||
|
|
33
|
+
name.startsWith(`${config.serviceName}-dev-`)
|
|
34
|
+
);
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
export async function cleanup(args = Bun.argv.slice(2)) {
|
|
@@ -29,13 +39,16 @@ export async function cleanup(args = Bun.argv.slice(2)) {
|
|
|
29
39
|
requireGcloudAuth();
|
|
30
40
|
|
|
31
41
|
const options = parseCleanupArgs(args);
|
|
42
|
+
await requireDestroyConfirmation(options.force);
|
|
32
43
|
|
|
44
|
+
await runStep(`Verifying production domain mapping ${config.domain.hostname}`, () => assertProductionDomainMappingOwned());
|
|
33
45
|
await runStep(`Deleting production domain mapping ${config.domain.hostname}`, () => deleteProductionDomainMapping());
|
|
34
46
|
|
|
35
47
|
const services = await runStep("Finding Cloud Run services", () => listCloudRunServices());
|
|
36
48
|
const serviceNames = services.filter(matchesServiceResource);
|
|
37
49
|
await runStep("Deleting Cloud Run services", () => {
|
|
38
50
|
for (const serviceName of serviceNames) {
|
|
51
|
+
assertOwnedResource(`Cloud Run service ${serviceName}`, describeCloudRunService(serviceName));
|
|
39
52
|
deleteService(serviceName);
|
|
40
53
|
}
|
|
41
54
|
});
|
|
@@ -44,6 +57,7 @@ export async function cleanup(args = Bun.argv.slice(2)) {
|
|
|
44
57
|
const secretNames = secrets.filter(matchesSecretResource);
|
|
45
58
|
await runStep("Deleting service secrets", () => {
|
|
46
59
|
for (const secretName of secretNames) {
|
|
60
|
+
assertOwnedResource(`Secret ${secretName}`, describeSecret(secretName));
|
|
47
61
|
deleteSecret(secretName);
|
|
48
62
|
}
|
|
49
63
|
});
|
|
@@ -68,6 +82,8 @@ export async function cleanup(args = Bun.argv.slice(2)) {
|
|
|
68
82
|
log.step(error instanceof Error ? error.message : String(error));
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
await runStep("Deleting Grafana resources", async () => deleteGrafanaResources());
|
|
86
|
+
|
|
71
87
|
await runStep("Deleting service-specific identity resources", () => {
|
|
72
88
|
deleteServiceAccount(config.runtimeServiceAccount);
|
|
73
89
|
});
|
|
@@ -78,9 +94,53 @@ export async function cleanup(args = Bun.argv.slice(2)) {
|
|
|
78
94
|
}
|
|
79
95
|
|
|
80
96
|
log.step(`Production API hostname released: ${config.domain.hostname}`);
|
|
81
|
-
return `
|
|
97
|
+
return `Destroy finished for ${config.serviceName}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function deleteGrafanaResources() {
|
|
101
|
+
if (!(await Bun.file("./grafana").exists())) {
|
|
102
|
+
return "No grafana directory configured";
|
|
103
|
+
}
|
|
104
|
+
if (!Bun.which("gcx")) {
|
|
105
|
+
return "gcx is not installed; Grafana resources were not deleted";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
run("gcx", ["resources", "delete", "--path", "./grafana", "--yes", "--on-error", "ignore"]);
|
|
109
|
+
return "Grafana resources deleted from local manifests";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function assertProductionDomainMappingOwned() {
|
|
113
|
+
const mapping = describeProductionDomainMapping();
|
|
114
|
+
if (!mapping) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const routeName = mapping.spec?.routeName;
|
|
119
|
+
if (routeName !== config.serviceName) {
|
|
120
|
+
throw new Error(`${config.domain.hostname} maps to ${routeName || "an unknown service"}; refusing to delete ambiguous DNS mapping`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
assertOwnedResource(`Cloud Run service ${routeName}`, describeCloudRunService(routeName));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function requireDestroyConfirmation(force: boolean) {
|
|
127
|
+
if (force) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!process.stdin.isTTY) {
|
|
132
|
+
throw new Error("service destroy requires --force when running non-interactively");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const answer = await confirm({
|
|
136
|
+
message: `Destroy resources owned by ${config.serviceName}?`,
|
|
137
|
+
initialValue: false,
|
|
138
|
+
});
|
|
139
|
+
if (isCancel(answer) || !answer) {
|
|
140
|
+
throw new Error("Destroy cancelled");
|
|
141
|
+
}
|
|
82
142
|
}
|
|
83
143
|
|
|
84
144
|
if (import.meta.main) {
|
|
85
|
-
await runMain("
|
|
145
|
+
await runMain("Destroy", () => cleanup(Bun.argv.slice(2)));
|
|
86
146
|
}
|