create-svc 0.1.10 → 0.1.12
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 +51 -47
- package/index.ts +2 -2
- package/package.json +10 -9
- package/src/cli.test.ts +28 -10
- package/src/cli.ts +196 -33
- 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 +232 -41
- package/src/scaffold.ts +81 -36
- package/src/service.test.ts +30 -0
- package/src/service.ts +65 -0
- 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 +329 -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 +402 -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
- /package/bin/{create-svc.mjs → service.mjs} +0 -0
|
@@ -1,17 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
+
import { mkdir } from "node:fs/promises";
|
|
4
|
+
import { ensureAuthResourceServer, runAuthCommand, runAuthDoctor } from "../authctl";
|
|
3
5
|
import { bootstrap } from "./bootstrap";
|
|
4
6
|
import { cleanup } from "./cleanup";
|
|
5
7
|
import { deploy } from "./deploy";
|
|
6
|
-
import {
|
|
8
|
+
import { config } from "./config";
|
|
9
|
+
import {
|
|
10
|
+
accessSecretVersion,
|
|
11
|
+
assertProductionDomainAvailable,
|
|
12
|
+
assertServiceNameAvailable,
|
|
13
|
+
describeProductionDomainMapping,
|
|
14
|
+
formatError,
|
|
15
|
+
gcloud,
|
|
16
|
+
ensureProductionDomainMapping,
|
|
17
|
+
requireCommand,
|
|
18
|
+
requireGcloudAuth,
|
|
19
|
+
resolveDeploymentTarget,
|
|
20
|
+
run,
|
|
21
|
+
runMain,
|
|
22
|
+
runStep,
|
|
23
|
+
serviceOrigin,
|
|
24
|
+
} from "./lib";
|
|
7
25
|
|
|
8
26
|
async function main(argv = Bun.argv.slice(2)) {
|
|
9
27
|
const [command, ...rest] = argv;
|
|
10
28
|
|
|
11
|
-
if (command === "
|
|
12
|
-
|
|
29
|
+
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
30
|
+
console.log("Usage: service <create|deploy|migrate|seed|dashboards|dns|doctor|destroy|auth|sdk> [args]");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (command === "create") {
|
|
35
|
+
await runMain("Create", async () => {
|
|
36
|
+
assertServiceNameAvailable(config.serviceName);
|
|
37
|
+
assertProductionDomainAvailable(config.serviceName);
|
|
38
|
+
await runStep("Registering auth resource server", () => ensureAuthResourceServer());
|
|
13
39
|
await bootstrap();
|
|
14
|
-
|
|
40
|
+
const target = resolveDeploymentTarget("main");
|
|
41
|
+
const databaseUrl = await runStep("Reading production database URL", () => accessSecretVersion(target.databaseSecretName));
|
|
42
|
+
await runStep("Applying production migrations", () => runLanguageTask("migrate", { DATABASE_URL: databaseUrl }));
|
|
43
|
+
const origin = await deploy(["--ci"]);
|
|
44
|
+
await runOptionalBunScript("seed", { DATABASE_URL: databaseUrl });
|
|
45
|
+
return `Created ${origin}`;
|
|
15
46
|
});
|
|
16
47
|
return;
|
|
17
48
|
}
|
|
@@ -21,12 +52,303 @@ async function main(argv = Bun.argv.slice(2)) {
|
|
|
21
52
|
return;
|
|
22
53
|
}
|
|
23
54
|
|
|
24
|
-
if (command === "
|
|
25
|
-
await runMain("
|
|
55
|
+
if (command === "migrate") {
|
|
56
|
+
await runMain("Migrate", () => runLanguageTask("migrate"));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (command === "seed") {
|
|
61
|
+
await runMain("Seed", () => runOptionalBunScript("seed"));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (command === "dashboards") {
|
|
66
|
+
await runMain("Dashboards", () => runDashboards());
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (command === "dns") {
|
|
71
|
+
await runMain("DNS", () => repairDns());
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (command === "doctor") {
|
|
76
|
+
await runMain("Doctor", () => runDoctor());
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (command === "auth") {
|
|
81
|
+
await runMain("Auth", () => runAuthCommand(rest));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (command === "destroy") {
|
|
86
|
+
await runMain("Destroy", () => cleanup(rest));
|
|
26
87
|
return;
|
|
27
88
|
}
|
|
28
89
|
|
|
29
|
-
|
|
90
|
+
if (command === "sdk") {
|
|
91
|
+
await runMain("SDK", () => runSdk(rest));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new Error("Usage: service <create|deploy|migrate|seed|dashboards|dns|doctor|destroy|auth|sdk> [args]");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function runLanguageTask(task: "migrate", env?: Record<string, string | undefined>) {
|
|
99
|
+
if (config.runtime === "bun") {
|
|
100
|
+
run("bun", ["run", `./scripts/${task}.ts`], { env });
|
|
101
|
+
return `${task} finished`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (task === "migrate") {
|
|
105
|
+
run("make", ["migrate"], { env });
|
|
106
|
+
return `${task} finished`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw new Error(`${task} is not available for ${config.runtime}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function runOptionalBunScript(name: string, env?: Record<string, string | undefined>) {
|
|
113
|
+
const scriptPath = `./scripts/${name}.ts`;
|
|
114
|
+
if (!(await Bun.file(scriptPath).exists())) {
|
|
115
|
+
return `${name} script is not configured`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
run("bun", ["run", scriptPath], { env });
|
|
119
|
+
return `${name} finished`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function runDashboards() {
|
|
123
|
+
requireCommand("gcx");
|
|
124
|
+
run("gcx", ["dev", "lint", "run", "./grafana", "-o", "compact"]);
|
|
125
|
+
run("gcx", ["resources", "push", "--path", "./grafana"]);
|
|
126
|
+
return "Dashboards pushed";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function repairDns() {
|
|
130
|
+
ensureProductionDomainMapping(config.serviceName);
|
|
131
|
+
return `DNS mapping ready for https://${config.domain.hostname}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function runDoctor() {
|
|
135
|
+
const results: Array<{ name: string; status: "pass" | "warn" | "fail"; detail: string }> = [];
|
|
136
|
+
const target = resolveDeploymentTarget("main");
|
|
137
|
+
|
|
138
|
+
await record(results, "bun CLI", "fail", () => checkCommand("bun"));
|
|
139
|
+
await record(results, "gcloud CLI", "fail", () => checkCommand("gcloud"));
|
|
140
|
+
await record(results, "gcloud auth", "fail", () => {
|
|
141
|
+
requireGcloudAuth();
|
|
142
|
+
return "active account available";
|
|
143
|
+
});
|
|
144
|
+
await record(results, "GCP project", "fail", () => {
|
|
145
|
+
gcloud(["projects", "describe", config.project.id, "--format=value(projectId)"]);
|
|
146
|
+
return config.project.id;
|
|
147
|
+
});
|
|
148
|
+
await record(results, "Cloud Run service", "fail", () => {
|
|
149
|
+
const serviceName = gcloud([
|
|
150
|
+
"run",
|
|
151
|
+
"services",
|
|
152
|
+
"describe",
|
|
153
|
+
target.serviceName,
|
|
154
|
+
"--project",
|
|
155
|
+
config.project.id,
|
|
156
|
+
"--region",
|
|
157
|
+
config.region,
|
|
158
|
+
"--format=value(metadata.name)",
|
|
159
|
+
]).stdout;
|
|
160
|
+
return serviceName || target.serviceName;
|
|
161
|
+
});
|
|
162
|
+
await record(results, "runtime database secret", "fail", () => {
|
|
163
|
+
const value = accessSecretVersion(target.databaseSecretName);
|
|
164
|
+
if (!value.startsWith("postgres://") && !value.startsWith("postgresql://")) {
|
|
165
|
+
throw new Error(`${target.databaseSecretName} does not look like a Postgres URL`);
|
|
166
|
+
}
|
|
167
|
+
return target.databaseSecretName;
|
|
168
|
+
});
|
|
169
|
+
await record(results, "DNS mapping", "fail", () => {
|
|
170
|
+
const mapping = describeProductionDomainMapping();
|
|
171
|
+
const mappedService = mapping?.spec?.routeName;
|
|
172
|
+
if (mappedService !== target.serviceName) {
|
|
173
|
+
throw new Error(`${config.domain.hostname} maps to ${mappedService || "nothing"}, expected ${target.serviceName}`);
|
|
174
|
+
}
|
|
175
|
+
return `${config.domain.hostname} -> ${target.serviceName}`;
|
|
176
|
+
});
|
|
177
|
+
await record(results, "deployment health", "fail", async () => {
|
|
178
|
+
const response = await fetchWithTimeout(`${serviceOrigin(target)}/healthz`, 5_000);
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
throw new Error(`GET /healthz returned ${response.status}`);
|
|
181
|
+
}
|
|
182
|
+
return "GET /healthz ok";
|
|
183
|
+
});
|
|
184
|
+
await record(results, "migration assets", "fail", async () => {
|
|
185
|
+
if (!(await Bun.file("./migrations/0000_init.sql").exists())) {
|
|
186
|
+
throw new Error("missing migrations/0000_init.sql");
|
|
187
|
+
}
|
|
188
|
+
return "migrations/0000_init.sql";
|
|
189
|
+
});
|
|
190
|
+
if ((config.runtime as string) === "go") {
|
|
191
|
+
await record(results, "Atlas CLI", "fail", () => checkCommand("atlas"));
|
|
192
|
+
await record(results, "Atlas config", "fail", async () => {
|
|
193
|
+
if (!(await Bun.file("./atlas.hcl").exists())) {
|
|
194
|
+
throw new Error("missing atlas.hcl");
|
|
195
|
+
}
|
|
196
|
+
return "atlas.hcl";
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
await record(results, "dashboard tooling", "warn", () => {
|
|
200
|
+
if (!Bun.which("gcx")) {
|
|
201
|
+
throw new Error("gcx is not installed");
|
|
202
|
+
}
|
|
203
|
+
return "gcx available";
|
|
204
|
+
});
|
|
205
|
+
await record(results, "dashboard artifacts", "warn", async () => {
|
|
206
|
+
if (!(await Bun.file("./grafana").exists()) && !(await Bun.file("./dashboards").exists())) {
|
|
207
|
+
throw new Error("no grafana/ or dashboards/ directory found");
|
|
208
|
+
}
|
|
209
|
+
return "dashboard directory found";
|
|
210
|
+
});
|
|
211
|
+
await record(results, "authctl", "warn", () => runAuthDoctor().detail);
|
|
212
|
+
await record(results, "Temporal/Cron", "warn", async () => {
|
|
213
|
+
const hasBunTemporal = await Bun.file("./src/temporal/worker.ts").exists();
|
|
214
|
+
const hasGoTemporal = await Bun.file("./internal/temporal/worker.go").exists();
|
|
215
|
+
if (!hasBunTemporal && !hasGoTemporal) {
|
|
216
|
+
throw new Error("Temporal worker config is not present in this scaffold yet");
|
|
217
|
+
}
|
|
218
|
+
return "Temporal worker config present";
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if ((config.framework as string) === "connectrpc") {
|
|
222
|
+
await record(results, "ConnectRPC proto", "fail", async () => {
|
|
223
|
+
if (!(await Bun.file("./buf.yaml").exists())) {
|
|
224
|
+
throw new Error("missing buf.yaml");
|
|
225
|
+
}
|
|
226
|
+
if (!(await Bun.file("./protos/waitlist/v1/waitlist.proto").exists())) {
|
|
227
|
+
throw new Error("missing waitlist proto");
|
|
228
|
+
}
|
|
229
|
+
return "waitlist proto present";
|
|
230
|
+
});
|
|
231
|
+
await record(results, "Buf CLI", "warn", () => checkCommand("buf"));
|
|
232
|
+
await record(results, "generated SDK artifacts", "warn", async () => {
|
|
233
|
+
const bunGen = await Bun.file("./gen/protos/waitlist/v1/waitlist_pb.ts").exists();
|
|
234
|
+
const goGen = await Bun.file("./gen/waitlist/v1/waitlist.pb.go").exists();
|
|
235
|
+
if (!bunGen && !goGen) {
|
|
236
|
+
throw new Error("generated SDK artifacts are missing; run service sdk build");
|
|
237
|
+
}
|
|
238
|
+
return "local generated artifacts present";
|
|
239
|
+
});
|
|
240
|
+
await record(results, "SDK mode", "warn", async () => {
|
|
241
|
+
const text = await Bun.file(".service/sdk.json").text();
|
|
242
|
+
const state = JSON.parse(text) as { mode?: string; module?: string };
|
|
243
|
+
if (state.mode !== "local" && state.mode !== "remote") {
|
|
244
|
+
throw new Error("SDK mode must be local or remote");
|
|
245
|
+
}
|
|
246
|
+
return `${state.mode}: ${state.module || bufModule()}`;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const output = results.map(formatDoctorResult).join("\n");
|
|
251
|
+
const failures = results.filter((result) => result.status === "fail");
|
|
252
|
+
if (failures.length > 0) {
|
|
253
|
+
throw new Error(`Doctor found ${failures.length} failing check(s)\n${output}`);
|
|
254
|
+
}
|
|
255
|
+
return output;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function record(
|
|
259
|
+
results: Array<{ name: string; status: "pass" | "warn" | "fail"; detail: string }>,
|
|
260
|
+
name: string,
|
|
261
|
+
failureStatus: "warn" | "fail",
|
|
262
|
+
check: () => string | Promise<string>
|
|
263
|
+
) {
|
|
264
|
+
try {
|
|
265
|
+
results.push({ name, status: "pass", detail: await check() });
|
|
266
|
+
} catch (error) {
|
|
267
|
+
results.push({ name, status: failureStatus, detail: formatError(error) });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function checkCommand(name: string) {
|
|
272
|
+
const path = Bun.which(name);
|
|
273
|
+
if (!path) {
|
|
274
|
+
throw new Error(`${name} is not installed`);
|
|
275
|
+
}
|
|
276
|
+
return path;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function fetchWithTimeout(url: string, timeoutMs: number) {
|
|
280
|
+
return await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function formatDoctorResult(result: { name: string; status: "pass" | "warn" | "fail"; detail: string }) {
|
|
284
|
+
const marker = result.status === "pass" ? "PASS" : result.status === "warn" ? "WARN" : "FAIL";
|
|
285
|
+
return `[${marker}] ${result.name}: ${result.detail}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function runSdk(args: string[]) {
|
|
289
|
+
if ((config.framework as string) !== "connectrpc") {
|
|
290
|
+
throw new Error("SDK commands are only available for ConnectRPC services");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const [subcommand] = args;
|
|
294
|
+
if (subcommand === "publish") {
|
|
295
|
+
requireCommand("buf");
|
|
296
|
+
run("buf", ["push"]);
|
|
297
|
+
return "Schema pushed to Buf Schema Registry";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (subcommand === "build") {
|
|
301
|
+
if (config.runtime === "bun") {
|
|
302
|
+
run("bun", ["run", "gen"]);
|
|
303
|
+
} else {
|
|
304
|
+
run("make", ["gen"]);
|
|
305
|
+
}
|
|
306
|
+
await writeSdkMode("local");
|
|
307
|
+
return "Local SDK artifacts generated and selected";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (subcommand === "use-local") {
|
|
311
|
+
await assertLocalSdkArtifacts();
|
|
312
|
+
await writeSdkMode("local");
|
|
313
|
+
return "Local SDK artifacts selected";
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (subcommand === "use-remote") {
|
|
317
|
+
await writeSdkMode("remote");
|
|
318
|
+
return `Remote Buf SDK selected: ${bufModule()}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
throw new Error("Usage: service sdk <build|publish|use-local|use-remote>");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function assertLocalSdkArtifacts() {
|
|
325
|
+
const bunArtifacts = await Bun.file("./gen/protos/waitlist/v1/waitlist_pb.ts").exists();
|
|
326
|
+
const goArtifacts = await Bun.file("./gen/waitlist/v1/waitlist.pb.go").exists();
|
|
327
|
+
if (!bunArtifacts && !goArtifacts) {
|
|
328
|
+
throw new Error("Local SDK artifacts are missing. Run `service sdk build` first.");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function writeSdkMode(mode: "local" | "remote") {
|
|
333
|
+
await mkdir(".service", { recursive: true });
|
|
334
|
+
const localPath = config.runtime === "bun" ? "./gen/protos/waitlist/v1" : "./gen/waitlist/v1";
|
|
335
|
+
await Bun.write(
|
|
336
|
+
".service/sdk.json",
|
|
337
|
+
`${JSON.stringify(
|
|
338
|
+
{
|
|
339
|
+
mode,
|
|
340
|
+
module: bufModule(),
|
|
341
|
+
localPath,
|
|
342
|
+
updatedAt: new Date().toISOString(),
|
|
343
|
+
},
|
|
344
|
+
null,
|
|
345
|
+
2
|
|
346
|
+
)}\n`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function bufModule() {
|
|
351
|
+
return `buf.build/anmho/${config.serviceName}`;
|
|
30
352
|
}
|
|
31
353
|
|
|
32
354
|
if (import.meta.main) {
|
|
@@ -23,9 +23,17 @@ export const config = {
|
|
|
23
23
|
hostname: "{{API_HOSTNAME}}",
|
|
24
24
|
baseDomain: "{{API_BASE_DOMAIN}}",
|
|
25
25
|
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
auth: {
|
|
27
|
+
issuer: "https://auth.anmho.com/api/auth",
|
|
28
|
+
audience: "api://{{SERVICE_ID}}",
|
|
29
|
+
jwksUrl: "https://auth.anmho.com/api/auth/jwks",
|
|
30
|
+
},
|
|
31
|
+
temporal: {
|
|
32
|
+
enabled: false,
|
|
33
|
+
address: "localhost:7233",
|
|
34
|
+
namespace: "default",
|
|
35
|
+
taskQueue: "{{SERVICE_ID}}",
|
|
36
|
+
apiKeySecretName: "{{SERVICE_ID}}-temporal-api-key",
|
|
29
37
|
},
|
|
30
38
|
neon: {
|
|
31
39
|
projectId: "{{NEON_PROJECT_ID}}",
|
|
@@ -44,7 +52,6 @@ export const config = {
|
|
|
44
52
|
"iamcredentials.googleapis.com",
|
|
45
53
|
"secretmanager.googleapis.com",
|
|
46
54
|
"serviceusage.googleapis.com",
|
|
47
|
-
"storage.googleapis.com",
|
|
48
55
|
"sts.googleapis.com",
|
|
49
56
|
],
|
|
50
57
|
} as const;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { config } from "./config";
|
|
2
2
|
import { bootstrap } from "./bootstrap";
|
|
3
|
-
import { publishProviderRuntimeSecrets } from "./integrations";
|
|
4
3
|
import { deleteBranch, ensureBranch, ensureDatabase, getConnectionUri, listBranches, resolveNeonConfig } from "./neon";
|
|
5
4
|
import {
|
|
6
5
|
addSecretVersion,
|
|
@@ -63,9 +62,6 @@ export async function deploy(args = Bun.argv.slice(2)) {
|
|
|
63
62
|
addSecretVersion(target.databaseSecretName, connectionUri);
|
|
64
63
|
ensureSecretAccessor(target.databaseSecretName, `serviceAccount:${config.runtimeServiceAccount}`);
|
|
65
64
|
});
|
|
66
|
-
|
|
67
|
-
await runStep("Publishing environment provider secrets", () => publishProviderRuntimeSecrets(target));
|
|
68
|
-
|
|
69
65
|
const image = imageUrl();
|
|
70
66
|
await runStep("Building container image", () =>
|
|
71
67
|
gcloud(["builds", "submit", "--project", config.project.id, "--region", config.region, "--tag", image])
|