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.
Files changed (168) hide show
  1. package/README.md +46 -43
  2. package/bin/create-service.mjs +2 -0
  3. package/package.json +12 -9
  4. package/src/cli.test.ts +28 -10
  5. package/src/cli.ts +195 -30
  6. package/src/git-bootstrap.test.ts +40 -0
  7. package/src/git-bootstrap.ts +110 -0
  8. package/src/naming.test.ts +1 -0
  9. package/src/naming.ts +23 -0
  10. package/src/post-scaffold.test.ts +19 -0
  11. package/src/post-scaffold.ts +17 -4
  12. package/src/profiles.ts +2 -5
  13. package/src/scaffold.test.ts +231 -40
  14. package/src/scaffold.ts +84 -29
  15. package/src/vault.test.ts +61 -1
  16. package/src/vault.ts +77 -15
  17. package/templates/shared/.github/workflows/ci.yml +2 -1
  18. package/templates/shared/.github/workflows/deploy.yml +2 -0
  19. package/templates/shared/README.md +124 -47
  20. package/templates/shared/grafana/alerts.yaml +54 -0
  21. package/templates/shared/grafana/waitlist-dashboard.json +63 -0
  22. package/templates/shared/scripts/authctl.ts +231 -0
  23. package/templates/shared/scripts/cloudrun/bootstrap.ts +14 -5
  24. package/templates/shared/scripts/cloudrun/cleanup.ts +64 -4
  25. package/templates/shared/scripts/cloudrun/cli.ts +324 -7
  26. package/templates/shared/scripts/cloudrun/config.ts +11 -4
  27. package/templates/shared/scripts/cloudrun/deploy.ts +0 -4
  28. package/templates/shared/scripts/cloudrun/lib.ts +174 -41
  29. package/templates/shared/scripts/cloudrun/neon.ts +45 -0
  30. package/templates/shared/scripts/dev.ts +22 -0
  31. package/templates/shared/scripts/ensure-local-db.ts +3 -0
  32. package/templates/shared/scripts/local-docker.ts +63 -0
  33. package/templates/shared/scripts/local-env.ts +27 -0
  34. package/templates/shared/scripts/seed.ts +73 -0
  35. package/templates/shared/scripts/wait-for-db.ts +32 -0
  36. package/templates/shared/service.config.ts +59 -0
  37. package/templates/shared/service.yaml +24 -44
  38. package/templates/targets/workers/.github/workflows/ci.yml +19 -0
  39. package/templates/targets/workers/.github/workflows/deploy.yml +19 -0
  40. package/templates/targets/workers/Makefile +33 -0
  41. package/templates/targets/workers/README.md +75 -0
  42. package/templates/targets/workers/package.json +35 -0
  43. package/templates/targets/workers/scripts/workers/cli.ts +397 -0
  44. package/templates/targets/workers/src/auth.ts +178 -0
  45. package/templates/targets/workers/src/index.ts +198 -0
  46. package/templates/targets/workers/src/storage.ts +370 -0
  47. package/templates/targets/workers/test/app.test.ts +108 -0
  48. package/templates/targets/workers/tsconfig.json +11 -0
  49. package/templates/targets/workers/wrangler.toml +24 -0
  50. package/templates/variants/bun-connectrpc/Makefile +14 -8
  51. package/templates/variants/bun-connectrpc/gen/protos/waitlist/v1/waitlist_pb.ts +424 -0
  52. package/templates/variants/bun-connectrpc/migrations/0000_init.sql +12 -55
  53. package/templates/variants/bun-connectrpc/package.json +12 -5
  54. package/templates/variants/bun-connectrpc/protos/waitlist/v1/waitlist.proto +91 -0
  55. package/templates/variants/bun-connectrpc/scripts/codegen.ts +1 -1
  56. package/templates/variants/bun-connectrpc/scripts/migrate.ts +4 -1
  57. package/templates/variants/bun-connectrpc/src/auth.ts +200 -0
  58. package/templates/variants/bun-connectrpc/src/db/repository.ts +67 -420
  59. package/templates/variants/bun-connectrpc/src/db/schema.ts +15 -64
  60. package/templates/variants/bun-connectrpc/src/index.ts +76 -176
  61. package/templates/variants/bun-connectrpc/src/temporal/activities.ts +14 -0
  62. package/templates/variants/bun-connectrpc/src/temporal/worker.ts +38 -0
  63. package/templates/variants/bun-connectrpc/src/temporal/workflows.ts +10 -0
  64. package/templates/variants/bun-connectrpc/src/waitlist/service.ts +172 -0
  65. package/templates/variants/bun-connectrpc/src/waitlist/types.ts +45 -0
  66. package/templates/variants/bun-connectrpc/test/app.test.ts +4 -4
  67. package/templates/variants/bun-connectrpc/test/waitlist.integration.test.ts +71 -0
  68. package/templates/variants/bun-hono/Makefile +14 -8
  69. package/templates/variants/bun-hono/migrations/0000_init.sql +12 -55
  70. package/templates/variants/bun-hono/package.json +12 -5
  71. package/templates/variants/bun-hono/scripts/migrate.ts +4 -1
  72. package/templates/variants/bun-hono/src/auth.ts +181 -0
  73. package/templates/variants/bun-hono/src/db/repository.ts +68 -421
  74. package/templates/variants/bun-hono/src/db/schema.ts +15 -64
  75. package/templates/variants/bun-hono/src/index.ts +65 -180
  76. package/templates/variants/bun-hono/src/temporal/activities.ts +14 -0
  77. package/templates/variants/bun-hono/src/temporal/worker.ts +38 -0
  78. package/templates/variants/bun-hono/src/temporal/workflows.ts +10 -0
  79. package/templates/variants/bun-hono/src/waitlist/service.ts +166 -0
  80. package/templates/variants/bun-hono/src/waitlist/types.ts +50 -0
  81. package/templates/variants/bun-hono/test/app.test.ts +72 -41
  82. package/templates/variants/bun-hono/test/waitlist.integration.test.ts +102 -0
  83. package/templates/variants/go-chi/Makefile +27 -11
  84. package/templates/variants/go-chi/atlas.hcl +8 -0
  85. package/templates/variants/go-chi/cmd/server/main.go +21 -10
  86. package/templates/variants/go-chi/go.mod +1 -3
  87. package/templates/variants/go-chi/internal/app/service.go +202 -685
  88. package/templates/variants/go-chi/internal/auth/middleware.go +289 -0
  89. package/templates/variants/go-chi/internal/auth/middleware_test.go +38 -0
  90. package/templates/variants/go-chi/internal/config/config.go +27 -11
  91. package/templates/variants/go-chi/internal/httpapi/routes.go +78 -157
  92. package/templates/variants/go-chi/internal/httpapi/waitlist_integration_test.go +199 -0
  93. package/templates/variants/go-chi/internal/temporal/activities.go +27 -0
  94. package/templates/variants/go-chi/internal/temporal/worker.go +42 -0
  95. package/templates/variants/go-chi/internal/temporal/workflows.go +18 -0
  96. package/templates/variants/go-chi/migrations/0000_init.sql +12 -55
  97. package/templates/variants/go-chi/migrations/atlas.sum +2 -0
  98. package/templates/variants/go-chi/package.json +7 -1
  99. package/templates/variants/go-connectrpc/Makefile +26 -9
  100. package/templates/variants/go-connectrpc/atlas.hcl +8 -0
  101. package/templates/variants/go-connectrpc/buf.gen.yaml +2 -2
  102. package/templates/variants/go-connectrpc/cmd/server/main.go +23 -12
  103. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlist.pb.go +960 -0
  104. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlistv1connect/waitlist.connect.go +283 -0
  105. package/templates/variants/go-connectrpc/go.mod +1 -1
  106. package/templates/variants/go-connectrpc/internal/app/service.go +202 -685
  107. package/templates/variants/go-connectrpc/internal/auth/middleware.go +289 -0
  108. package/templates/variants/go-connectrpc/internal/auth/middleware_test.go +38 -0
  109. package/templates/variants/go-connectrpc/internal/config/config.go +27 -11
  110. package/templates/variants/go-connectrpc/internal/connectapi/handler.go +78 -201
  111. package/templates/variants/go-connectrpc/internal/connectapi/waitlist_integration_test.go +122 -0
  112. package/templates/variants/go-connectrpc/internal/httpapi/routes.go +147 -9
  113. package/templates/variants/go-connectrpc/internal/temporal/activities.go +27 -0
  114. package/templates/variants/go-connectrpc/internal/temporal/worker.go +42 -0
  115. package/templates/variants/go-connectrpc/internal/temporal/workflows.go +18 -0
  116. package/templates/variants/go-connectrpc/migrations/0000_init.sql +12 -55
  117. package/templates/variants/go-connectrpc/migrations/atlas.sum +2 -0
  118. package/templates/variants/go-connectrpc/package.json +7 -1
  119. package/templates/variants/go-connectrpc/protos/waitlist/v1/waitlist.proto +93 -0
  120. package/templates/root/.github/workflows/buf-publish.yml +0 -19
  121. package/templates/root/.github/workflows/ci.yml +0 -26
  122. package/templates/root/.github/workflows/deploy.yml +0 -22
  123. package/templates/root/Dockerfile +0 -23
  124. package/templates/root/README.md +0 -69
  125. package/templates/root/buf.gen.yaml +0 -10
  126. package/templates/root/buf.yaml +0 -9
  127. package/templates/root/cmd/server/main.go +0 -44
  128. package/templates/root/gen/dns/v1/dns.pb.go +0 -623
  129. package/templates/root/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  130. package/templates/root/go.mod +0 -10
  131. package/templates/root/internal/app/service.go +0 -152
  132. package/templates/root/internal/app/token_source.go +0 -50
  133. package/templates/root/internal/cloudflare/client.go +0 -160
  134. package/templates/root/internal/config/config.go +0 -55
  135. package/templates/root/internal/connectapi/handler.go +0 -79
  136. package/templates/root/internal/httpapi/routes.go +0 -93
  137. package/templates/root/internal/vault/client.go +0 -148
  138. package/templates/root/package.json +0 -12
  139. package/templates/root/protos/dns/v1/dns.proto +0 -58
  140. package/templates/root/scripts/cloudrun/bootstrap.ts +0 -65
  141. package/templates/root/scripts/cloudrun/config.ts +0 -50
  142. package/templates/root/scripts/cloudrun/deploy.ts +0 -41
  143. package/templates/root/scripts/cloudrun/lib.ts +0 -244
  144. package/templates/root/service.yaml +0 -50
  145. package/templates/root/test/go.test.ts +0 -19
  146. package/templates/shared/scripts/cloudrun/integrations.ts +0 -111
  147. package/templates/variants/bun-connectrpc/gen/protos/chat/v1/chat_pb.ts +0 -1078
  148. package/templates/variants/bun-connectrpc/protos/chat/v1/chat.proto +0 -228
  149. package/templates/variants/bun-connectrpc/src/chat/service.ts +0 -384
  150. package/templates/variants/bun-connectrpc/src/chat/types.ts +0 -142
  151. package/templates/variants/bun-connectrpc/src/storage.ts +0 -72
  152. package/templates/variants/bun-connectrpc/src/webhooks.ts +0 -35
  153. package/templates/variants/bun-connectrpc/test/list-messages.integration.test.ts +0 -182
  154. package/templates/variants/bun-hono/src/chat/service.ts +0 -384
  155. package/templates/variants/bun-hono/src/chat/types.ts +0 -142
  156. package/templates/variants/bun-hono/src/storage.ts +0 -72
  157. package/templates/variants/bun-hono/src/webhooks.ts +0 -35
  158. package/templates/variants/bun-hono/test/list-messages.integration.test.ts +0 -256
  159. package/templates/variants/go-chi/buf.gen.yaml +0 -12
  160. package/templates/variants/go-chi/buf.yaml +0 -9
  161. package/templates/variants/go-chi/cmd/migrate/main.go +0 -101
  162. package/templates/variants/go-chi/internal/httpapi/list_messages_integration_test.go +0 -298
  163. package/templates/variants/go-chi/protos/chat/v1/chat.proto +0 -219
  164. package/templates/variants/go-connectrpc/cmd/migrate/main.go +0 -101
  165. package/templates/variants/go-connectrpc/gen/chat/v1/chat.pb.go +0 -2512
  166. package/templates/variants/go-connectrpc/gen/chat/v1/chatv1connect/chat.connect.go +0 -571
  167. package/templates/variants/go-connectrpc/internal/connectapi/list_messages_integration_test.go +0 -216
  168. package/templates/variants/go-connectrpc/protos/chat/v1/chat.proto +0 -232
@@ -1,244 +0,0 @@
1
- import { config, manifestEnv } from "./config";
2
-
3
- type CommandOptions = {
4
- allowFailure?: boolean;
5
- capture?: boolean;
6
- input?: string;
7
- };
8
-
9
- const decoder = new TextDecoder();
10
-
11
- export function requireCommand(name: string) {
12
- if (!Bun.which(name)) {
13
- throw new Error(`missing required command: ${name}`);
14
- }
15
- }
16
-
17
- export function run(command: string, args: string[], options: CommandOptions = {}) {
18
- const result = Bun.spawnSync([command, ...args], {
19
- cwd: process.cwd(),
20
- env: process.env,
21
- stdin: options.input,
22
- stdout: options.capture || options.allowFailure ? "pipe" : "inherit",
23
- stderr: options.capture || options.allowFailure ? "pipe" : "inherit",
24
- });
25
-
26
- const stdout = result.stdout ? decoder.decode(result.stdout).trim() : "";
27
- const stderr = result.stderr ? decoder.decode(result.stderr).trim() : "";
28
-
29
- if (!result.success && !options.allowFailure) {
30
- throw new Error([`command failed: ${command} ${args.join(" ")}`, stdout, stderr].filter(Boolean).join("\n"));
31
- }
32
-
33
- return {
34
- success: result.success,
35
- stdout,
36
- stderr,
37
- exitCode: result.exitCode,
38
- };
39
- }
40
-
41
- export function gcloud(args: string[], options: CommandOptions = {}) {
42
- return run("gcloud", args, options);
43
- }
44
-
45
- export function gh(args: string[], options: CommandOptions = {}) {
46
- return run("gh", args, options);
47
- }
48
-
49
- export function ensureServiceAccount(email: string) {
50
- if (gcloud(["iam", "service-accounts", "describe", email, "--project", config.projectId], { allowFailure: true }).success) {
51
- return;
52
- }
53
-
54
- const accountId = email.split("@")[0] ?? email;
55
- gcloud(["iam", "service-accounts", "create", accountId, "--project", config.projectId, "--display-name", accountId]);
56
- }
57
-
58
- export function ensureProjectRole(member: string, role: string) {
59
- gcloud(["projects", "add-iam-policy-binding", config.projectId, "--member", member, "--role", role]);
60
- }
61
-
62
- export function ensureServiceAccountRole(serviceAccount: string, member: string, role: string) {
63
- gcloud([
64
- "iam",
65
- "service-accounts",
66
- "add-iam-policy-binding",
67
- serviceAccount,
68
- "--project",
69
- config.projectId,
70
- "--member",
71
- member,
72
- "--role",
73
- role,
74
- ]);
75
- }
76
-
77
- export function ensureSecret(secretName: string, bootstrapEnv: string) {
78
- if (gcloud(["secrets", "describe", secretName, "--project", config.projectId], { allowFailure: true }).success) {
79
- return;
80
- }
81
-
82
- const value = process.env[bootstrapEnv]?.trim() ?? "";
83
- if (!value) {
84
- throw new Error(`missing bootstrap value for secret ${secretName}; set ${bootstrapEnv}`);
85
- }
86
-
87
- gcloud(["secrets", "create", secretName, "--project", config.projectId, "--replication-policy", "automatic"]);
88
- gcloud(["secrets", "versions", "add", secretName, "--project", config.projectId, "--data-file=-"], { input: value });
89
- }
90
-
91
- export function ensureSecretAccessor(secretName: string, member: string) {
92
- gcloud(["secrets", "add-iam-policy-binding", secretName, "--project", config.projectId, "--member", member, "--role", "roles/secretmanager.secretAccessor"]);
93
- }
94
-
95
- export function projectNumber() {
96
- return gcloud(["projects", "describe", config.projectId, "--format=value(projectNumber)"], { capture: true }).stdout;
97
- }
98
-
99
- export function workloadIdentityPoolResource() {
100
- return `projects/${projectNumber()}/locations/global/workloadIdentityPools/${config.workloadIdentityPoolId}`;
101
- }
102
-
103
- export function workloadIdentityProviderResource() {
104
- return `${workloadIdentityPoolResource()}/providers/${config.workloadIdentityProviderId}`;
105
- }
106
-
107
- export function ensureWorkloadIdentityPool() {
108
- if (
109
- gcloud(["iam", "workload-identity-pools", "describe", config.workloadIdentityPoolId, "--project", config.projectId, "--location", "global"], {
110
- allowFailure: true,
111
- }).success
112
- ) {
113
- return;
114
- }
115
-
116
- gcloud([
117
- "iam",
118
- "workload-identity-pools",
119
- "create",
120
- config.workloadIdentityPoolId,
121
- "--project",
122
- config.projectId,
123
- "--location",
124
- "global",
125
- "--display-name",
126
- "GitHub Actions",
127
- ]);
128
- }
129
-
130
- export function ensureWorkloadIdentityProvider() {
131
- if (
132
- gcloud(
133
- [
134
- "iam",
135
- "workload-identity-pools",
136
- "providers",
137
- "describe",
138
- config.workloadIdentityProviderId,
139
- "--project",
140
- config.projectId,
141
- "--location",
142
- "global",
143
- "--workload-identity-pool",
144
- config.workloadIdentityPoolId,
145
- ],
146
- { allowFailure: true }
147
- ).success
148
- ) {
149
- return;
150
- }
151
-
152
- gcloud([
153
- "iam",
154
- "workload-identity-pools",
155
- "providers",
156
- "create-oidc",
157
- config.workloadIdentityProviderId,
158
- "--project",
159
- config.projectId,
160
- "--location",
161
- "global",
162
- "--workload-identity-pool",
163
- config.workloadIdentityPoolId,
164
- "--display-name",
165
- `${config.serviceName} GitHub`,
166
- "--issuer-uri",
167
- "https://token.actions.githubusercontent.com",
168
- "--attribute-mapping",
169
- "google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner",
170
- "--attribute-condition",
171
- `assertion.repository=='${config.githubRepo}'`,
172
- ]);
173
- }
174
-
175
- export function setGithubVariable(name: string, value: string) {
176
- gh(["variable", "set", name, "--repo", config.githubRepo, "--body", value]);
177
- }
178
-
179
- export function setGithubSecret(name: string, value: string) {
180
- gh(["secret", "set", name, "--repo", config.githubRepo], { input: value });
181
- }
182
-
183
- export function ensureArtifactRepository() {
184
- if (
185
- gcloud(
186
- ["artifacts", "repositories", "describe", config.artifactRepository, "--project", config.projectId, "--location", config.region],
187
- { allowFailure: true }
188
- ).success
189
- ) {
190
- return;
191
- }
192
-
193
- gcloud([
194
- "artifacts",
195
- "repositories",
196
- "create",
197
- config.artifactRepository,
198
- "--project",
199
- config.projectId,
200
- "--location",
201
- config.region,
202
- "--repository-format",
203
- "docker",
204
- ]);
205
- }
206
-
207
- export function imageTag() {
208
- const gitSha = run("git", ["rev-parse", "--short", "HEAD"], { allowFailure: true, capture: true }).stdout;
209
- return gitSha || `${Date.now()}`;
210
- }
211
-
212
- export function imageUrl(tag = imageTag()) {
213
- return `${config.region}-docker.pkg.dev/${config.projectId}/${config.artifactRepository}/${config.serviceName}:${tag}`;
214
- }
215
-
216
- export async function renderManifest(image: string) {
217
- const template = await Bun.file(new URL("../../service.yaml", import.meta.url)).text();
218
- const values = {
219
- ...manifestEnv,
220
- IMAGE_URL: image,
221
- };
222
-
223
- return template.replace(/\$\{([A-Z0-9_]+)\}/g, (_, key: string) => {
224
- const value = values[key as keyof typeof values];
225
- if (!value) {
226
- throw new Error(`missing manifest value for ${key}`);
227
- }
228
- return value;
229
- });
230
- }
231
-
232
- export async function writeRenderedManifest(image: string) {
233
- const rendered = await renderManifest(image);
234
- const path = new URL("../../.cloudrun.rendered.yaml", import.meta.url);
235
- await Bun.write(path, rendered);
236
- return path;
237
- }
238
-
239
- export function serviceUrl() {
240
- return gcloud(
241
- ["run", "services", "describe", config.serviceName, "--project", config.projectId, "--region", config.region, "--format=value(status.url)"],
242
- { capture: true }
243
- ).stdout;
244
- }
@@ -1,50 +0,0 @@
1
- apiVersion: serving.knative.dev/v1
2
- kind: Service
3
- metadata:
4
- name: ${SERVICE_NAME}
5
- annotations:
6
- run.googleapis.com/ingress: all
7
- spec:
8
- template:
9
- spec:
10
- serviceAccountName: ${RUNTIME_SERVICE_ACCOUNT}
11
- containerConcurrency: 80
12
- containers:
13
- - image: ${IMAGE_URL}
14
- ports:
15
- - name: h2c
16
- containerPort: 8080
17
- env:
18
- - name: VAULT_ADDR
19
- value: ${VAULT_ADDR}
20
- - name: VAULT_SECRET_PATH
21
- value: ${VAULT_SECRET_PATH}
22
- - name: VAULT_SECRET_KEY
23
- value: ${VAULT_SECRET_KEY}
24
- - name: CLOUDFLARE_ZONE_ID
25
- value: ${CLOUDFLARE_ZONE_ID}
26
- - name: VAULT_ROLE_ID_FILE
27
- value: /var/run/secrets/vault-role-id/value
28
- - name: VAULT_SECRET_ID_FILE
29
- value: /var/run/secrets/vault-secret-id/value
30
- volumeMounts:
31
- - name: vault-role-id
32
- mountPath: /var/run/secrets/vault-role-id
33
- - name: vault-secret-id
34
- mountPath: /var/run/secrets/vault-secret-id
35
- volumes:
36
- - name: vault-role-id
37
- secret:
38
- secretName: ${VAULT_ROLE_ID_SECRET}
39
- items:
40
- - key: latest
41
- path: value
42
- - name: vault-secret-id
43
- secret:
44
- secretName: ${VAULT_SECRET_ID_SECRET}
45
- items:
46
- - key: latest
47
- path: value
48
- traffic:
49
- - latestRevision: true
50
- percent: 100
@@ -1,19 +0,0 @@
1
- import { expect, test } from "bun:test";
2
-
3
- const decoder = new TextDecoder();
4
-
5
- test(
6
- "go test ./...",
7
- { timeout: 60_000 },
8
- () => {
9
- const result = Bun.spawnSync(["go", "test", "./..."], {
10
- cwd: process.cwd(),
11
- stdout: "pipe",
12
- stderr: "pipe",
13
- env: process.env,
14
- });
15
-
16
- const output = [decoder.decode(result.stdout), decoder.decode(result.stderr)].join("").trim();
17
- expect(result.exitCode, output || "go test ./... failed").toBe(0);
18
- }
19
- );
@@ -1,111 +0,0 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
- import { config } from "./config";
4
- import {
5
- addSecretVersion,
6
- ensureSecretAccessor,
7
- runtimeSecretNames,
8
- type DeploymentTarget,
9
- } from "./lib";
10
-
11
- type ProviderSecret = {
12
- envName: keyof ReturnType<typeof runtimeSecretNames>;
13
- provider: string;
14
- field: string;
15
- };
16
-
17
- const PROVIDER_SECRETS: ProviderSecret[] = [
18
- { envName: "CLERK_SECRET_KEY", provider: "clerk", field: "secret_key" },
19
- { envName: "CLERK_WEBHOOK_SECRET", provider: "clerk", field: "webhook_secret" },
20
- { envName: "STRIPE_SECRET_KEY", provider: "stripe", field: "secret_key" },
21
- { envName: "STRIPE_WEBHOOK_SECRET", provider: "stripe", field: "webhook_secret" },
22
- { envName: "REVENUECAT_API_KEY", provider: "revenuecat", field: "api_key" },
23
- { envName: "REVENUECAT_WEBHOOK_SECRET", provider: "revenuecat", field: "webhook_secret" },
24
- { envName: "RESEND_API_KEY", provider: "resend", field: "api_key" },
25
- { envName: "POSTHOG_API_KEY", provider: "posthog", field: "api_key" },
26
- ];
27
-
28
- export async function publishProviderRuntimeSecrets(target: DeploymentTarget) {
29
- const secretNames = runtimeSecretNames(target);
30
- const missing: string[] = [];
31
-
32
- for (const secret of PROVIDER_SECRETS) {
33
- const value = await resolveProviderSecret(secret);
34
- if (!value) {
35
- missing.push(formatMissingSecret(secret));
36
- continue;
37
- }
38
-
39
- addSecretVersion(secretNames[secret.envName], value);
40
- ensureSecretAccessor(secretNames[secret.envName], `serviceAccount:${config.runtimeServiceAccount}`);
41
- }
42
-
43
- if (missing.length > 0) {
44
- throw new Error(
45
- [
46
- "Provider bootstrap credentials are required for the strict production bootstrap path.",
47
- "Set the missing environment variables or write the matching Vault fields, then rerun the same bootstrap/deploy command.",
48
- ...missing.map((item) => `- ${item}`),
49
- ].join("\n")
50
- );
51
- }
52
- }
53
-
54
- async function resolveProviderSecret(secret: ProviderSecret) {
55
- const direct = process.env[secret.envName]?.trim();
56
- if (direct) {
57
- return direct;
58
- }
59
-
60
- const addr = process.env.VAULT_ADDR?.trim() ?? "";
61
- const token = await resolveVaultToken();
62
- if (!addr || !token) {
63
- return "";
64
- }
65
-
66
- const mount = process.env.VAULT_SECRET_MOUNT?.trim() ?? "secret";
67
- const path = providerVaultPath(secret.provider);
68
- const normalizedAddr = addr.replace(/\/+$/g, "");
69
- const normalizedMount = mount.replace(/^\/+|\/+$/g, "");
70
- const response = await fetch(`${normalizedAddr}/v1/${normalizedMount}/data/${path}`, {
71
- headers: {
72
- "X-Vault-Token": token,
73
- },
74
- });
75
-
76
- if (!response.ok) {
77
- return "";
78
- }
79
-
80
- const payload = (await response.json()) as {
81
- data?: {
82
- data?: Record<string, string | undefined>;
83
- };
84
- };
85
-
86
- return payload.data?.data?.[secret.field]?.trim() ?? "";
87
- }
88
-
89
- async function resolveVaultToken() {
90
- const direct = process.env.VAULT_TOKEN?.trim();
91
- if (direct) {
92
- return direct;
93
- }
94
-
95
- const tokenFile = process.env.VAULT_TOKEN_FILE?.trim() || join(process.env.HOME?.trim() || homedir(), ".vault-token");
96
-
97
- try {
98
- return (await Bun.file(tokenFile).text()).trim();
99
- } catch {
100
- return "";
101
- }
102
- }
103
-
104
- function providerVaultPath(provider: string) {
105
- const override = process.env[`VAULT_${provider.toUpperCase()}_PATH`]?.trim();
106
- return (override || `prod/providers/${provider}`).replace(/^\/+/g, "");
107
- }
108
-
109
- function formatMissingSecret(secret: ProviderSecret) {
110
- return `${secret.envName} or Vault secret/${providerVaultPath(secret.provider)} field ${secret.field}`;
111
- }