create-svc 0.1.8 → 0.1.10

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 (91) hide show
  1. package/README.md +142 -13
  2. package/package.json +9 -4
  3. package/src/cli.test.ts +29 -8
  4. package/src/cli.ts +103 -70
  5. package/src/naming.test.ts +4 -2
  6. package/src/naming.ts +9 -1
  7. package/src/neon.ts +10 -8
  8. package/src/post-scaffold.ts +7 -28
  9. package/src/profiles.ts +28 -0
  10. package/src/scaffold.test.ts +126 -15
  11. package/src/scaffold.ts +94 -23
  12. package/src/vault.test.ts +62 -5
  13. package/src/vault.ts +24 -4
  14. package/templates/shared/README.md +143 -26
  15. package/templates/shared/docker-compose.yml +19 -0
  16. package/templates/shared/scripts/cloudrun/bootstrap.ts +15 -42
  17. package/templates/shared/scripts/cloudrun/cleanup.ts +17 -31
  18. package/templates/shared/scripts/cloudrun/config.ts +14 -19
  19. package/templates/shared/scripts/cloudrun/deploy.ts +19 -10
  20. package/templates/shared/scripts/cloudrun/integrations.ts +111 -0
  21. package/templates/shared/scripts/cloudrun/lib.ts +88 -112
  22. package/templates/shared/scripts/cloudrun/neon.ts +100 -14
  23. package/templates/shared/service.yaml +44 -1
  24. package/templates/variants/bun-connectrpc/Dockerfile +1 -0
  25. package/templates/variants/bun-connectrpc/Makefile +4 -1
  26. package/templates/variants/bun-connectrpc/gen/protos/chat/v1/chat_pb.ts +1078 -0
  27. package/templates/variants/bun-connectrpc/migrations/0000_init.sql +63 -0
  28. package/templates/variants/bun-connectrpc/package.json +17 -0
  29. package/templates/variants/bun-connectrpc/protos/chat/v1/chat.proto +228 -0
  30. package/templates/variants/bun-connectrpc/scripts/codegen.ts +31 -1
  31. package/templates/variants/bun-connectrpc/scripts/migrate.ts +46 -0
  32. package/templates/variants/bun-connectrpc/src/chat/service.ts +384 -0
  33. package/templates/variants/bun-connectrpc/src/chat/types.ts +142 -0
  34. package/templates/variants/bun-connectrpc/src/db/client.ts +15 -0
  35. package/templates/variants/bun-connectrpc/src/db/repository.ts +479 -0
  36. package/templates/variants/bun-connectrpc/src/db/schema.ts +75 -0
  37. package/templates/variants/bun-connectrpc/src/index.ts +294 -22
  38. package/templates/variants/bun-connectrpc/src/storage.ts +72 -0
  39. package/templates/variants/bun-connectrpc/src/webhooks.ts +35 -0
  40. package/templates/variants/bun-connectrpc/test/app.test.ts +14 -13
  41. package/templates/variants/bun-connectrpc/test/list-messages.integration.test.ts +182 -0
  42. package/templates/variants/bun-connectrpc/tsconfig.json +2 -1
  43. package/templates/variants/bun-hono/Makefile +4 -1
  44. package/templates/variants/bun-hono/migrations/0000_init.sql +63 -0
  45. package/templates/variants/bun-hono/package.json +13 -0
  46. package/templates/variants/bun-hono/scripts/migrate.ts +46 -0
  47. package/templates/variants/bun-hono/src/chat/service.ts +384 -0
  48. package/templates/variants/bun-hono/src/chat/types.ts +142 -0
  49. package/templates/variants/bun-hono/src/db/client.ts +15 -0
  50. package/templates/variants/bun-hono/src/db/repository.ts +479 -0
  51. package/templates/variants/bun-hono/src/db/schema.ts +75 -0
  52. package/templates/variants/bun-hono/src/index.ts +254 -8
  53. package/templates/variants/bun-hono/src/storage.ts +72 -0
  54. package/templates/variants/bun-hono/src/webhooks.ts +35 -0
  55. package/templates/variants/bun-hono/test/app.test.ts +60 -6
  56. package/templates/variants/bun-hono/test/list-messages.integration.test.ts +256 -0
  57. package/templates/variants/bun-hono/tsconfig.json +1 -0
  58. package/templates/variants/go-chi/Makefile +6 -2
  59. package/templates/variants/go-chi/buf.gen.yaml +2 -0
  60. package/templates/variants/go-chi/cmd/migrate/main.go +101 -0
  61. package/templates/variants/go-chi/cmd/server/main.go +16 -15
  62. package/templates/variants/go-chi/go.mod +3 -0
  63. package/templates/variants/go-chi/internal/app/service.go +763 -71
  64. package/templates/variants/go-chi/internal/config/config.go +22 -7
  65. package/templates/variants/go-chi/internal/httpapi/list_messages_integration_test.go +298 -0
  66. package/templates/variants/go-chi/internal/httpapi/routes.go +245 -43
  67. package/templates/variants/go-chi/migrations/0000_init.sql +63 -0
  68. package/templates/variants/go-chi/protos/chat/v1/chat.proto +219 -0
  69. package/templates/variants/go-chi/test/go.test.ts +4 -1
  70. package/templates/variants/go-connectrpc/Makefile +6 -2
  71. package/templates/variants/go-connectrpc/buf.gen.yaml +2 -0
  72. package/templates/variants/go-connectrpc/cmd/migrate/main.go +101 -0
  73. package/templates/variants/go-connectrpc/cmd/server/main.go +35 -11
  74. package/templates/variants/go-connectrpc/gen/chat/v1/chat.pb.go +2512 -0
  75. package/templates/variants/go-connectrpc/gen/chat/v1/chatv1connect/chat.connect.go +571 -0
  76. package/templates/variants/go-connectrpc/go.mod +4 -0
  77. package/templates/variants/go-connectrpc/internal/app/service.go +763 -71
  78. package/templates/variants/go-connectrpc/internal/config/config.go +22 -7
  79. package/templates/variants/go-connectrpc/internal/connectapi/handler.go +254 -42
  80. package/templates/variants/go-connectrpc/internal/connectapi/list_messages_integration_test.go +216 -0
  81. package/templates/variants/go-connectrpc/internal/httpapi/routes.go +41 -56
  82. package/templates/variants/go-connectrpc/migrations/0000_init.sql +63 -0
  83. package/templates/variants/go-connectrpc/protos/chat/v1/chat.proto +232 -0
  84. package/templates/shared/.env.example +0 -10
  85. package/templates/variants/go-chi/gen/dns/v1/dns.pb.go +0 -623
  86. package/templates/variants/go-chi/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  87. package/templates/variants/go-chi/internal/connectapi/handler.go +0 -79
  88. package/templates/variants/go-chi/protos/dns/v1/dns.proto +0 -58
  89. package/templates/variants/go-connectrpc/gen/dns/v1/dns.pb.go +0 -623
  90. package/templates/variants/go-connectrpc/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  91. package/templates/variants/go-connectrpc/protos/dns/v1/dns.proto +0 -58
@@ -15,10 +15,9 @@ type DeployArgs = {
15
15
 
16
16
  type CleanupArgs = {
17
17
  destroyProject: boolean;
18
- destroyRepo: boolean;
19
18
  };
20
19
 
21
- type DeploymentTarget = {
20
+ export type DeploymentTarget = {
22
21
  environment: "main" | "preview" | "personal";
23
22
  serviceName: string;
24
23
  branchName: string;
@@ -33,6 +32,7 @@ type CommandResult = {
33
32
  };
34
33
 
35
34
  const decoder = new TextDecoder();
35
+ const encoder = new TextEncoder();
36
36
 
37
37
  export class CommandError extends Error {
38
38
  command: string;
@@ -58,11 +58,27 @@ export function requireCommand(name: string) {
58
58
  }
59
59
  }
60
60
 
61
+ export function requireGcloudAuth() {
62
+ const activeAccount = gcloud(["auth", "list", "--filter=status:ACTIVE", "--format=value(account)"], {
63
+ allowFailure: true,
64
+ }).stdout.trim();
65
+
66
+ if (!activeAccount) {
67
+ throw new Error(
68
+ [
69
+ "gcloud is installed but no active Google Cloud account is available.",
70
+ "Run `gcloud auth login` on this machine before using bootstrap, deploy, or cleanup.",
71
+ "If you also rely on Application Default Credentials for other tooling, run `gcloud auth application-default login` as well.",
72
+ ].join(" ")
73
+ );
74
+ }
75
+ }
76
+
61
77
  export function run(command: string, args: string[], options: CommandOptions = {}): CommandResult {
62
78
  const result = Bun.spawnSync([command, ...args], {
63
79
  cwd: process.cwd(),
64
80
  env: process.env,
65
- stdin: options.input,
81
+ stdin: options.input === undefined ? undefined : encoder.encode(options.input),
66
82
  stdout: "pipe",
67
83
  stderr: "pipe",
68
84
  });
@@ -89,10 +105,6 @@ export function gcloud(args: string[], options: CommandOptions = {}) {
89
105
  return run("gcloud", normalized, options);
90
106
  }
91
107
 
92
- export function gh(args: string[], options: CommandOptions = {}) {
93
- return run("gh", args, options);
94
- }
95
-
96
108
  export async function runStep<T>(label: string, task: () => Promise<T> | T) {
97
109
  const indicator = spinner();
98
110
  indicator.start(label);
@@ -228,116 +240,26 @@ export function ensureArtifactRepository() {
228
240
  ]);
229
241
  }
230
242
 
231
- export function projectNumber() {
232
- return gcloud(["projects", "describe", config.project.id, "--format=value(projectNumber)"]).stdout;
233
- }
234
-
235
- export function workloadIdentityPoolResource() {
236
- return `projects/${projectNumber()}/locations/global/workloadIdentityPools/${config.workloadIdentityPoolId}`;
237
- }
238
-
239
- export function workloadIdentityProviderResource() {
240
- return `${workloadIdentityPoolResource()}/providers/${config.workloadIdentityProviderId}`;
241
- }
242
-
243
- export function ensureWorkloadIdentityPool() {
244
- if (
245
- gcloud(["iam", "workload-identity-pools", "describe", config.workloadIdentityPoolId, "--project", config.project.id, "--location", "global"], {
246
- allowFailure: true,
247
- }).success
248
- ) {
243
+ export function ensureStorageBucket() {
244
+ if (gcloud(["storage", "buckets", "describe", `gs://${config.storage.attachmentBucket}`, "--project", config.project.id], { allowFailure: true }).success) {
249
245
  return;
250
246
  }
251
247
 
252
248
  gcloud([
253
- "iam",
254
- "workload-identity-pools",
249
+ "storage",
250
+ "buckets",
255
251
  "create",
256
- config.workloadIdentityPoolId,
252
+ `gs://${config.storage.attachmentBucket}`,
257
253
  "--project",
258
254
  config.project.id,
259
255
  "--location",
260
- "global",
261
- "--display-name",
262
- "GitHub Actions",
263
- ]);
264
- }
265
-
266
- export function ensureWorkloadIdentityProvider() {
267
- if (
268
- gcloud(
269
- [
270
- "iam",
271
- "workload-identity-pools",
272
- "providers",
273
- "describe",
274
- config.workloadIdentityProviderId,
275
- "--project",
276
- config.project.id,
277
- "--location",
278
- "global",
279
- "--workload-identity-pool",
280
- config.workloadIdentityPoolId,
281
- ],
282
- { allowFailure: true }
283
- ).success
284
- ) {
285
- return;
286
- }
287
-
288
- gcloud([
289
- "iam",
290
- "workload-identity-pools",
291
- "providers",
292
- "create-oidc",
293
- config.workloadIdentityProviderId,
294
- "--project",
295
- config.project.id,
296
- "--location",
297
- "global",
298
- "--workload-identity-pool",
299
- config.workloadIdentityPoolId,
300
- "--display-name",
301
- `${config.serviceName} GitHub`,
302
- "--issuer-uri",
303
- "https://token.actions.githubusercontent.com",
304
- "--attribute-mapping",
305
- "google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner",
306
- "--attribute-condition",
307
- `assertion.repository=='${config.github.repo}'`,
256
+ config.region,
257
+ "--uniform-bucket-level-access",
308
258
  ]);
309
259
  }
310
260
 
311
- export function deleteWorkloadIdentityProvider() {
312
- gcloud(
313
- [
314
- "iam",
315
- "workload-identity-pools",
316
- "providers",
317
- "delete",
318
- config.workloadIdentityProviderId,
319
- "--project",
320
- config.project.id,
321
- "--location",
322
- "global",
323
- "--workload-identity-pool",
324
- config.workloadIdentityPoolId,
325
- "--quiet",
326
- ],
327
- { allowFailure: true }
328
- );
329
- }
330
-
331
- export function setGithubVariable(name: string, value: string) {
332
- gh(["variable", "set", name, "--repo", config.github.repo, "--body", value]);
333
- }
334
-
335
- export function deleteGithubVariable(name: string) {
336
- gh(["variable", "delete", name, "--repo", config.github.repo], { allowFailure: true });
337
- }
338
-
339
- export function deleteGithubRepository() {
340
- gh(["repo", "delete", config.github.repo, "--yes"]);
261
+ export function projectNumber() {
262
+ return gcloud(["projects", "describe", config.project.id, "--format=value(projectNumber)"]).stdout;
341
263
  }
342
264
 
343
265
  export function imageTag() {
@@ -408,7 +330,6 @@ export function parseDeployArgs(argv: string[]): DeployArgs {
408
330
  export function parseCleanupArgs(argv: string[]): CleanupArgs {
409
331
  const parsed: CleanupArgs = {
410
332
  destroyProject: false,
411
- destroyRepo: false,
412
333
  };
413
334
 
414
335
  for (const token of argv) {
@@ -416,11 +337,6 @@ export function parseCleanupArgs(argv: string[]): CleanupArgs {
416
337
  parsed.destroyProject = true;
417
338
  continue;
418
339
  }
419
-
420
- if (token === "--repo") {
421
- parsed.destroyRepo = true;
422
- continue;
423
- }
424
340
  }
425
341
 
426
342
  return parsed;
@@ -458,6 +374,19 @@ export function resolveDeploymentTarget(environment: DeployArgs["environment"],
458
374
  };
459
375
  }
460
376
 
377
+ export function runtimeSecretNames(target: DeploymentTarget) {
378
+ return {
379
+ CLERK_SECRET_KEY: `${target.serviceName}-clerk-secret-key`,
380
+ CLERK_WEBHOOK_SECRET: `${target.serviceName}-clerk-webhook-secret`,
381
+ STRIPE_SECRET_KEY: `${target.serviceName}-stripe-secret-key`,
382
+ STRIPE_WEBHOOK_SECRET: `${target.serviceName}-stripe-webhook-secret`,
383
+ REVENUECAT_API_KEY: `${target.serviceName}-revenuecat-api-key`,
384
+ REVENUECAT_WEBHOOK_SECRET: `${target.serviceName}-revenuecat-webhook-secret`,
385
+ RESEND_API_KEY: `${target.serviceName}-resend-api-key`,
386
+ POSTHOG_API_KEY: `${target.serviceName}-posthog-api-key`,
387
+ } as const;
388
+ }
389
+
461
390
  export async function renderManifest(image: string, target: DeploymentTarget) {
462
391
  const template = await Bun.file(new URL("../../service.yaml", import.meta.url)).text();
463
392
  const values = {
@@ -465,8 +394,11 @@ export async function renderManifest(image: string, target: DeploymentTarget) {
465
394
  RUNTIME_SERVICE_ACCOUNT: config.runtimeServiceAccount,
466
395
  IMAGE_URL: image,
467
396
  DATABASE_URL_SECRET: target.databaseSecretName,
397
+ ...runtimeSecretNames(target),
468
398
  SERVICE_RUNTIME: config.runtime,
469
399
  SERVICE_FRAMEWORK: config.framework,
400
+ ATTACHMENT_BUCKET: config.storage.attachmentBucket,
401
+ ATTACHMENT_PUBLIC_BASE_URL: config.storage.attachmentPublicBaseUrl,
470
402
  };
471
403
 
472
404
  return template.replace(/\$\{([A-Z0-9_]+)\}/g, (_, key: string) => {
@@ -491,6 +423,50 @@ export function serviceUrl(serviceName: string) {
491
423
  ).stdout;
492
424
  }
493
425
 
426
+ export function serviceDomain(target: DeploymentTarget) {
427
+ if (target.environment === "main") {
428
+ return config.domain.hostname;
429
+ }
430
+
431
+ return `${target.serviceName}-${config.project.id}-${config.region}.a.run.app`;
432
+ }
433
+
434
+ export function serviceOrigin(target: DeploymentTarget) {
435
+ if (target.environment === "main") {
436
+ return `https://${config.domain.hostname}`;
437
+ }
438
+
439
+ const url = serviceUrl(target.serviceName);
440
+ return url || `https://${serviceDomain(target)}`;
441
+ }
442
+
443
+ export function ensureProductionDomainMapping(serviceName: string) {
444
+ if (gcloud(["beta", "run", "domain-mappings", "describe", "--domain", config.domain.hostname, "--project", config.project.id], { allowFailure: true }).success) {
445
+ return;
446
+ }
447
+
448
+ gcloud([
449
+ "beta",
450
+ "run",
451
+ "domain-mappings",
452
+ "create",
453
+ "--service",
454
+ serviceName,
455
+ "--domain",
456
+ config.domain.hostname,
457
+ "--project",
458
+ config.project.id,
459
+ "--region",
460
+ config.region,
461
+ ]);
462
+ }
463
+
464
+ export function deleteProductionDomainMapping() {
465
+ gcloud(["beta", "run", "domain-mappings", "delete", "--domain", config.domain.hostname, "--project", config.project.id, "--quiet"], {
466
+ allowFailure: true,
467
+ });
468
+ }
469
+
494
470
  export function listCloudRunServices() {
495
471
  return gcloud(["run", "services", "list", "--project", config.project.id, "--region", config.region, "--format=value(metadata.name)"]).stdout
496
472
  .split("\n")
@@ -1,11 +1,28 @@
1
1
  import { createApiClient } from "@neondatabase/api-client";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
2
4
  import { config } from "./config";
3
5
 
6
+ type NeonProject = {
7
+ id: string;
8
+ name: string;
9
+ };
10
+
4
11
  type NeonBranch = {
5
12
  id: string;
6
13
  name: string;
7
14
  };
8
15
 
16
+ type ResolvedNeonConfig = {
17
+ projectId: string;
18
+ baseBranchId: string;
19
+ baseBranchName: string;
20
+ databaseName: string;
21
+ roleName: string;
22
+ previewBranchPrefix: string;
23
+ personalBranchPrefix: string;
24
+ };
25
+
9
26
  async function resolveNeonApiKey() {
10
27
  const direct = process.env.NEON_API_KEY?.trim();
11
28
  if (direct) {
@@ -13,13 +30,13 @@ async function resolveNeonApiKey() {
13
30
  }
14
31
 
15
32
  const addr = process.env.VAULT_ADDR?.trim() ?? "";
16
- const token = process.env.VAULT_TOKEN?.trim() ?? "";
33
+ const token = await resolveVaultToken();
17
34
  const mount = process.env.VAULT_SECRET_MOUNT?.trim() ?? "secret";
18
- const path = process.env.VAULT_NEON_API_KEY_PATH?.trim() ?? "provider/neon-api-key";
19
- const field = process.env.VAULT_NEON_API_KEY_FIELD?.trim() ?? "value";
35
+ const path = process.env.VAULT_NEON_API_KEY_PATH?.trim() ?? "prod/providers/neon";
36
+ const field = process.env.VAULT_NEON_API_KEY_FIELD?.trim() ?? "api_key";
20
37
 
21
38
  if (!addr || !token) {
22
- throw new Error("NEON_API_KEY is required for Neon provisioning, or set VAULT_ADDR and VAULT_TOKEN");
39
+ throw new Error("NEON_API_KEY is required for Neon provisioning, or set VAULT_ADDR with VAULT_TOKEN, VAULT_TOKEN_FILE, or ~/.vault-token");
23
40
  }
24
41
 
25
42
  const normalizedAddr = addr.replace(/\/+$/g, "");
@@ -49,20 +66,88 @@ async function resolveNeonApiKey() {
49
66
  return apiKey;
50
67
  }
51
68
 
69
+ async function resolveVaultToken() {
70
+ const direct = process.env.VAULT_TOKEN?.trim();
71
+ if (direct) {
72
+ return direct;
73
+ }
74
+
75
+ const tokenFile = process.env.VAULT_TOKEN_FILE?.trim() || join(process.env.HOME?.trim() || homedir(), ".vault-token");
76
+
77
+ try {
78
+ return (await Bun.file(tokenFile).text()).trim();
79
+ } catch {
80
+ return "";
81
+ }
82
+ }
83
+
52
84
  async function neonClient() {
53
85
  const apiKey = await resolveNeonApiKey();
54
86
  return createApiClient({ apiKey });
55
87
  }
56
88
 
89
+ export async function listProjects() {
90
+ const payload = await (await neonClient()).listProjects({ limit: 100 });
91
+ const projects = ((payload.data as { projects?: Array<{ id?: string; name?: string }> } | undefined)?.projects ?? []);
92
+ return projects
93
+ .map((project: { id?: string; name?: string }) => ({
94
+ id: project.id ?? "",
95
+ name: project.name ?? project.id ?? "",
96
+ }))
97
+ .filter((project: NeonProject): project is NeonProject => Boolean(project.id))
98
+ .sort((left: NeonProject, right: NeonProject) => left.name.localeCompare(right.name));
99
+ }
100
+
57
101
  export async function listBranches(projectId: string) {
58
102
  const payload = await (await neonClient()).listProjectBranches({ projectId });
59
- return (payload.branches ?? [])
60
- .map((branch) => ({
103
+ const branches = ((payload.data as { branches?: Array<{ id?: string; name?: string }> } | undefined)?.branches ?? []);
104
+ return branches
105
+ .map((branch: { id?: string; name?: string }) => ({
61
106
  id: branch.id ?? "",
62
107
  name: branch.name ?? branch.id ?? "",
63
108
  }))
64
- .filter((branch): branch is NeonBranch => Boolean(branch.id))
65
- .sort((left, right) => left.name.localeCompare(right.name));
109
+ .filter((branch: NeonBranch): branch is NeonBranch => Boolean(branch.id))
110
+ .sort((left: NeonBranch, right: NeonBranch) => left.name.localeCompare(right.name));
111
+ }
112
+
113
+ export async function resolveNeonConfig(): Promise<ResolvedNeonConfig> {
114
+ const configuredProjectId = config.neon.projectId.trim();
115
+ const configuredBaseBranchId = config.neon.baseBranchId.trim();
116
+ const configuredBaseBranchName = config.neon.baseBranchName.trim() || "main";
117
+
118
+ if (configuredProjectId && configuredBaseBranchId) {
119
+ return {
120
+ projectId: configuredProjectId,
121
+ baseBranchId: configuredBaseBranchId,
122
+ baseBranchName: configuredBaseBranchName,
123
+ databaseName: config.neon.databaseName,
124
+ roleName: config.neon.roleName,
125
+ previewBranchPrefix: config.neon.previewBranchPrefix,
126
+ personalBranchPrefix: config.neon.personalBranchPrefix,
127
+ };
128
+ }
129
+
130
+ const projects = await listProjects();
131
+ const project = projects[0];
132
+ if (!project) {
133
+ throw new Error(`No Neon projects are available for ${config.serviceName}`);
134
+ }
135
+
136
+ const branches = await listBranches(project.id);
137
+ const branch = branches.find((candidate) => candidate.name === configuredBaseBranchName) ?? branches[0];
138
+ if (!branch) {
139
+ throw new Error(`No Neon branches are available in project ${project.id}`);
140
+ }
141
+
142
+ return {
143
+ projectId: project.id,
144
+ baseBranchId: branch.id,
145
+ baseBranchName: branch.name,
146
+ databaseName: config.neon.databaseName,
147
+ roleName: config.neon.roleName,
148
+ previewBranchPrefix: config.neon.previewBranchPrefix,
149
+ personalBranchPrefix: config.neon.personalBranchPrefix,
150
+ };
66
151
  }
67
152
 
68
153
  export async function ensureDatabase(projectId: string, branchId: string, databaseName: string) {
@@ -81,6 +166,7 @@ export async function ensureDatabase(projectId: string, branchId: string, databa
81
166
  await client.createProjectBranchDatabase(projectId, branchId, {
82
167
  database: {
83
168
  name: databaseName,
169
+ owner_name: config.neon.roleName,
84
170
  },
85
171
  });
86
172
  }
@@ -110,12 +196,12 @@ export async function ensureBranch(projectId: string, branchName: string, parent
110
196
  },
111
197
  endpoints: [
112
198
  {
113
- type: "read_write",
199
+ type: "read_write" as never,
114
200
  },
115
201
  ],
116
202
  });
117
203
 
118
- const branch = payload.branch;
204
+ const branch = (payload.data as { branch?: { id?: string; name?: string } } | undefined)?.branch;
119
205
  if (!branch?.id) {
120
206
  throw new Error(`Neon did not return a branch for ${branchName}`);
121
207
  }
@@ -141,12 +227,12 @@ export async function deleteBranch(projectId: string, branchId: string) {
141
227
  export async function getConnectionUri(projectId: string, branchId: string, databaseName: string, roleName: string) {
142
228
  const payload = await (await neonClient()).getConnectionUri({
143
229
  projectId,
144
- branchId,
145
- databaseName,
146
- roleName,
230
+ branch_id: branchId,
231
+ database_name: databaseName,
232
+ role_name: roleName,
147
233
  });
148
234
 
149
- const uri = payload.uri;
235
+ const uri = (payload.data as { uri?: string } | undefined)?.uri;
150
236
  if (!uri) {
151
237
  throw new Error(`Neon did not return a connection URI for ${databaseName} in ${config.serviceName}`);
152
238
  }
@@ -22,7 +22,50 @@ spec:
22
22
  secretKeyRef:
23
23
  name: ${DATABASE_URL_SECRET}
24
24
  key: latest
25
+ - name: CLERK_SECRET_KEY
26
+ valueFrom:
27
+ secretKeyRef:
28
+ name: ${CLERK_SECRET_KEY}
29
+ key: latest
30
+ - name: CLERK_WEBHOOK_SECRET
31
+ valueFrom:
32
+ secretKeyRef:
33
+ name: ${CLERK_WEBHOOK_SECRET}
34
+ key: latest
35
+ - name: STRIPE_SECRET_KEY
36
+ valueFrom:
37
+ secretKeyRef:
38
+ name: ${STRIPE_SECRET_KEY}
39
+ key: latest
40
+ - name: STRIPE_WEBHOOK_SECRET
41
+ valueFrom:
42
+ secretKeyRef:
43
+ name: ${STRIPE_WEBHOOK_SECRET}
44
+ key: latest
45
+ - name: REVENUECAT_API_KEY
46
+ valueFrom:
47
+ secretKeyRef:
48
+ name: ${REVENUECAT_API_KEY}
49
+ key: latest
50
+ - name: REVENUECAT_WEBHOOK_SECRET
51
+ valueFrom:
52
+ secretKeyRef:
53
+ name: ${REVENUECAT_WEBHOOK_SECRET}
54
+ key: latest
55
+ - name: RESEND_API_KEY
56
+ valueFrom:
57
+ secretKeyRef:
58
+ name: ${RESEND_API_KEY}
59
+ key: latest
60
+ - name: POSTHOG_API_KEY
61
+ valueFrom:
62
+ secretKeyRef:
63
+ name: ${POSTHOG_API_KEY}
64
+ key: latest
65
+ - name: ATTACHMENT_BUCKET
66
+ value: ${ATTACHMENT_BUCKET}
67
+ - name: ATTACHMENT_PUBLIC_BASE_URL
68
+ value: ${ATTACHMENT_PUBLIC_BASE_URL}
25
69
  traffic:
26
70
  - latestRevision: true
27
71
  percent: 100
28
-
@@ -5,6 +5,7 @@ WORKDIR /app
5
5
  COPY package.json ./
6
6
  RUN bun install --production
7
7
 
8
+ COPY gen ./gen
8
9
  COPY src ./src
9
10
 
10
11
  ENV PORT=8080
@@ -1,10 +1,13 @@
1
- .PHONY: dev gen lint test bootstrap deploy cleanup
1
+ .PHONY: dev migrate gen lint test bootstrap deploy cleanup
2
2
 
3
3
  CLOUDRUN := npx --no-install svc-cloudrun
4
4
 
5
5
  dev:
6
6
  bun run ./src/index.ts
7
7
 
8
+ migrate:
9
+ bun run ./scripts/migrate.ts
10
+
8
11
  gen:
9
12
  bun run ./scripts/codegen.ts
10
13