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.
Files changed (171) hide show
  1. package/README.md +51 -47
  2. package/index.ts +2 -2
  3. package/package.json +10 -9
  4. package/src/cli.test.ts +28 -10
  5. package/src/cli.ts +196 -33
  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 +232 -41
  14. package/src/scaffold.ts +81 -36
  15. package/src/service.test.ts +30 -0
  16. package/src/service.ts +65 -0
  17. package/src/vault.test.ts +61 -1
  18. package/src/vault.ts +77 -15
  19. package/templates/shared/.github/workflows/ci.yml +2 -1
  20. package/templates/shared/.github/workflows/deploy.yml +2 -0
  21. package/templates/shared/README.md +124 -47
  22. package/templates/shared/grafana/alerts.yaml +54 -0
  23. package/templates/shared/grafana/waitlist-dashboard.json +63 -0
  24. package/templates/shared/scripts/authctl.ts +231 -0
  25. package/templates/shared/scripts/cloudrun/bootstrap.ts +14 -5
  26. package/templates/shared/scripts/cloudrun/cleanup.ts +64 -4
  27. package/templates/shared/scripts/cloudrun/cli.ts +329 -7
  28. package/templates/shared/scripts/cloudrun/config.ts +11 -4
  29. package/templates/shared/scripts/cloudrun/deploy.ts +0 -4
  30. package/templates/shared/scripts/cloudrun/lib.ts +174 -41
  31. package/templates/shared/scripts/cloudrun/neon.ts +45 -0
  32. package/templates/shared/scripts/dev.ts +22 -0
  33. package/templates/shared/scripts/ensure-local-db.ts +3 -0
  34. package/templates/shared/scripts/local-docker.ts +63 -0
  35. package/templates/shared/scripts/local-env.ts +27 -0
  36. package/templates/shared/scripts/seed.ts +73 -0
  37. package/templates/shared/scripts/wait-for-db.ts +32 -0
  38. package/templates/shared/service.config.ts +59 -0
  39. package/templates/shared/service.yaml +24 -44
  40. package/templates/targets/workers/.github/workflows/ci.yml +19 -0
  41. package/templates/targets/workers/.github/workflows/deploy.yml +19 -0
  42. package/templates/targets/workers/Makefile +33 -0
  43. package/templates/targets/workers/README.md +75 -0
  44. package/templates/targets/workers/package.json +35 -0
  45. package/templates/targets/workers/scripts/workers/cli.ts +402 -0
  46. package/templates/targets/workers/src/auth.ts +178 -0
  47. package/templates/targets/workers/src/index.ts +198 -0
  48. package/templates/targets/workers/src/storage.ts +370 -0
  49. package/templates/targets/workers/test/app.test.ts +108 -0
  50. package/templates/targets/workers/tsconfig.json +11 -0
  51. package/templates/targets/workers/wrangler.toml +24 -0
  52. package/templates/variants/bun-connectrpc/Makefile +14 -8
  53. package/templates/variants/bun-connectrpc/gen/protos/waitlist/v1/waitlist_pb.ts +424 -0
  54. package/templates/variants/bun-connectrpc/migrations/0000_init.sql +12 -55
  55. package/templates/variants/bun-connectrpc/package.json +12 -5
  56. package/templates/variants/bun-connectrpc/protos/waitlist/v1/waitlist.proto +91 -0
  57. package/templates/variants/bun-connectrpc/scripts/codegen.ts +1 -1
  58. package/templates/variants/bun-connectrpc/scripts/migrate.ts +4 -1
  59. package/templates/variants/bun-connectrpc/src/auth.ts +200 -0
  60. package/templates/variants/bun-connectrpc/src/db/repository.ts +67 -420
  61. package/templates/variants/bun-connectrpc/src/db/schema.ts +15 -64
  62. package/templates/variants/bun-connectrpc/src/index.ts +76 -176
  63. package/templates/variants/bun-connectrpc/src/temporal/activities.ts +14 -0
  64. package/templates/variants/bun-connectrpc/src/temporal/worker.ts +38 -0
  65. package/templates/variants/bun-connectrpc/src/temporal/workflows.ts +10 -0
  66. package/templates/variants/bun-connectrpc/src/waitlist/service.ts +172 -0
  67. package/templates/variants/bun-connectrpc/src/waitlist/types.ts +45 -0
  68. package/templates/variants/bun-connectrpc/test/app.test.ts +4 -4
  69. package/templates/variants/bun-connectrpc/test/waitlist.integration.test.ts +71 -0
  70. package/templates/variants/bun-hono/Makefile +14 -8
  71. package/templates/variants/bun-hono/migrations/0000_init.sql +12 -55
  72. package/templates/variants/bun-hono/package.json +12 -5
  73. package/templates/variants/bun-hono/scripts/migrate.ts +4 -1
  74. package/templates/variants/bun-hono/src/auth.ts +181 -0
  75. package/templates/variants/bun-hono/src/db/repository.ts +68 -421
  76. package/templates/variants/bun-hono/src/db/schema.ts +15 -64
  77. package/templates/variants/bun-hono/src/index.ts +65 -180
  78. package/templates/variants/bun-hono/src/temporal/activities.ts +14 -0
  79. package/templates/variants/bun-hono/src/temporal/worker.ts +38 -0
  80. package/templates/variants/bun-hono/src/temporal/workflows.ts +10 -0
  81. package/templates/variants/bun-hono/src/waitlist/service.ts +166 -0
  82. package/templates/variants/bun-hono/src/waitlist/types.ts +50 -0
  83. package/templates/variants/bun-hono/test/app.test.ts +72 -41
  84. package/templates/variants/bun-hono/test/waitlist.integration.test.ts +102 -0
  85. package/templates/variants/go-chi/Makefile +27 -11
  86. package/templates/variants/go-chi/atlas.hcl +8 -0
  87. package/templates/variants/go-chi/cmd/server/main.go +21 -10
  88. package/templates/variants/go-chi/go.mod +1 -3
  89. package/templates/variants/go-chi/internal/app/service.go +202 -685
  90. package/templates/variants/go-chi/internal/auth/middleware.go +289 -0
  91. package/templates/variants/go-chi/internal/auth/middleware_test.go +38 -0
  92. package/templates/variants/go-chi/internal/config/config.go +27 -11
  93. package/templates/variants/go-chi/internal/httpapi/routes.go +78 -157
  94. package/templates/variants/go-chi/internal/httpapi/waitlist_integration_test.go +199 -0
  95. package/templates/variants/go-chi/internal/temporal/activities.go +27 -0
  96. package/templates/variants/go-chi/internal/temporal/worker.go +42 -0
  97. package/templates/variants/go-chi/internal/temporal/workflows.go +18 -0
  98. package/templates/variants/go-chi/migrations/0000_init.sql +12 -55
  99. package/templates/variants/go-chi/migrations/atlas.sum +2 -0
  100. package/templates/variants/go-chi/package.json +7 -1
  101. package/templates/variants/go-connectrpc/Makefile +26 -9
  102. package/templates/variants/go-connectrpc/atlas.hcl +8 -0
  103. package/templates/variants/go-connectrpc/buf.gen.yaml +2 -2
  104. package/templates/variants/go-connectrpc/cmd/server/main.go +23 -12
  105. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlist.pb.go +960 -0
  106. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlistv1connect/waitlist.connect.go +283 -0
  107. package/templates/variants/go-connectrpc/go.mod +1 -1
  108. package/templates/variants/go-connectrpc/internal/app/service.go +202 -685
  109. package/templates/variants/go-connectrpc/internal/auth/middleware.go +289 -0
  110. package/templates/variants/go-connectrpc/internal/auth/middleware_test.go +38 -0
  111. package/templates/variants/go-connectrpc/internal/config/config.go +27 -11
  112. package/templates/variants/go-connectrpc/internal/connectapi/handler.go +78 -201
  113. package/templates/variants/go-connectrpc/internal/connectapi/waitlist_integration_test.go +122 -0
  114. package/templates/variants/go-connectrpc/internal/httpapi/routes.go +147 -9
  115. package/templates/variants/go-connectrpc/internal/temporal/activities.go +27 -0
  116. package/templates/variants/go-connectrpc/internal/temporal/worker.go +42 -0
  117. package/templates/variants/go-connectrpc/internal/temporal/workflows.go +18 -0
  118. package/templates/variants/go-connectrpc/migrations/0000_init.sql +12 -55
  119. package/templates/variants/go-connectrpc/migrations/atlas.sum +2 -0
  120. package/templates/variants/go-connectrpc/package.json +7 -1
  121. package/templates/variants/go-connectrpc/protos/waitlist/v1/waitlist.proto +93 -0
  122. package/templates/root/.github/workflows/buf-publish.yml +0 -19
  123. package/templates/root/.github/workflows/ci.yml +0 -26
  124. package/templates/root/.github/workflows/deploy.yml +0 -22
  125. package/templates/root/Dockerfile +0 -23
  126. package/templates/root/README.md +0 -69
  127. package/templates/root/buf.gen.yaml +0 -10
  128. package/templates/root/buf.yaml +0 -9
  129. package/templates/root/cmd/server/main.go +0 -44
  130. package/templates/root/gen/dns/v1/dns.pb.go +0 -623
  131. package/templates/root/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  132. package/templates/root/go.mod +0 -10
  133. package/templates/root/internal/app/service.go +0 -152
  134. package/templates/root/internal/app/token_source.go +0 -50
  135. package/templates/root/internal/cloudflare/client.go +0 -160
  136. package/templates/root/internal/config/config.go +0 -55
  137. package/templates/root/internal/connectapi/handler.go +0 -79
  138. package/templates/root/internal/httpapi/routes.go +0 -93
  139. package/templates/root/internal/vault/client.go +0 -148
  140. package/templates/root/package.json +0 -12
  141. package/templates/root/protos/dns/v1/dns.proto +0 -58
  142. package/templates/root/scripts/cloudrun/bootstrap.ts +0 -65
  143. package/templates/root/scripts/cloudrun/config.ts +0 -50
  144. package/templates/root/scripts/cloudrun/deploy.ts +0 -41
  145. package/templates/root/scripts/cloudrun/lib.ts +0 -244
  146. package/templates/root/service.yaml +0 -50
  147. package/templates/root/test/go.test.ts +0 -19
  148. package/templates/shared/scripts/cloudrun/integrations.ts +0 -111
  149. package/templates/variants/bun-connectrpc/gen/protos/chat/v1/chat_pb.ts +0 -1078
  150. package/templates/variants/bun-connectrpc/protos/chat/v1/chat.proto +0 -228
  151. package/templates/variants/bun-connectrpc/src/chat/service.ts +0 -384
  152. package/templates/variants/bun-connectrpc/src/chat/types.ts +0 -142
  153. package/templates/variants/bun-connectrpc/src/storage.ts +0 -72
  154. package/templates/variants/bun-connectrpc/src/webhooks.ts +0 -35
  155. package/templates/variants/bun-connectrpc/test/list-messages.integration.test.ts +0 -182
  156. package/templates/variants/bun-hono/src/chat/service.ts +0 -384
  157. package/templates/variants/bun-hono/src/chat/types.ts +0 -142
  158. package/templates/variants/bun-hono/src/storage.ts +0 -72
  159. package/templates/variants/bun-hono/src/webhooks.ts +0 -35
  160. package/templates/variants/bun-hono/test/list-messages.integration.test.ts +0 -256
  161. package/templates/variants/go-chi/buf.gen.yaml +0 -12
  162. package/templates/variants/go-chi/buf.yaml +0 -9
  163. package/templates/variants/go-chi/cmd/migrate/main.go +0 -101
  164. package/templates/variants/go-chi/internal/httpapi/list_messages_integration_test.go +0 -298
  165. package/templates/variants/go-chi/protos/chat/v1/chat.proto +0 -219
  166. package/templates/variants/go-connectrpc/cmd/migrate/main.go +0 -101
  167. package/templates/variants/go-connectrpc/gen/chat/v1/chat.pb.go +0 -2512
  168. package/templates/variants/go-connectrpc/gen/chat/v1/chatv1connect/chat.connect.go +0 -571
  169. package/templates/variants/go-connectrpc/internal/connectapi/list_messages_integration_test.go +0 -216
  170. package/templates/variants/go-connectrpc/protos/chat/v1/chat.proto +0 -232
  171. /package/bin/{create-svc.mjs → service.mjs} +0 -0
@@ -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
- }