create-svc 0.1.42 → 0.1.43

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-svc",
3
- "version": "0.1.42",
3
+ "version": "0.1.43",
4
4
  "description": "Local microservice bootstrap CLI for Cloud Run and Workers services with Neon-backed data.",
5
5
  "module": "index.ts",
6
6
  "type": "module",
@@ -7,7 +7,7 @@ import { manualGitHubDeleteCommand } from "../../git-bootstrap";
7
7
  import { ensureAuthClient, ensureAuthResourceServer, runAuthCommand, runAuthDoctor } from "../authctl";
8
8
  import { stopLocalDev } from "../local-dev";
9
9
  import { serviceConfig } from "../runtime";
10
- import { isLocalDatabaseUrl, resolveCommandPath } from "./lib";
10
+ import { isLocalDatabaseUrl, isMissingDatabaseError, resolveCommandPath } from "./lib";
11
11
 
12
12
  const config = {
13
13
  serviceName: serviceConfig.service_id,
@@ -37,7 +37,7 @@ export async function main(argv = Bun.argv.slice(2)) {
37
37
  ensureAuthResourceServer();
38
38
  ensureAuthClient();
39
39
  const databaseUrl = await resolveDatabaseUrl({ preferRemote: true });
40
- await applySchema(databaseUrl);
40
+ await applySchemaWithRetries(databaseUrl);
41
41
  await ensureHyperdrive(databaseUrl);
42
42
  run("wrangler", ["deploy"]);
43
43
  return `Created https://${config.hostname}`;
@@ -259,8 +259,13 @@ async function deleteHyperdrive() {
259
259
  async function resolveDatabaseUrl(options: { preferRemote?: boolean } = {}) {
260
260
  const direct = Bun.env.DATABASE_URL?.trim();
261
261
  const apiKey = resolveNeonApiKey();
262
- if (direct && (!options.preferRemote || !isLocalDatabaseUrl(direct) || !apiKey)) {
263
- return direct;
262
+ if (direct) {
263
+ if (!options.preferRemote || !isLocalDatabaseUrl(direct)) {
264
+ return direct;
265
+ }
266
+ if (!apiKey) {
267
+ throw new Error("NEON_API_KEY or readable Vault Neon provider path is required for Workers production create; ignoring local DATABASE_URL");
268
+ }
264
269
  }
265
270
 
266
271
  if (!apiKey) {
@@ -416,6 +421,23 @@ create index if not exists waitlist_triggers_status_created_idx
416
421
  }
417
422
  }
418
423
 
424
+ async function applySchemaWithRetries(databaseUrl: string) {
425
+ let lastError: unknown;
426
+ for (let attempt = 1; attempt <= 10; attempt += 1) {
427
+ try {
428
+ await applySchema(databaseUrl);
429
+ return;
430
+ } catch (error) {
431
+ lastError = error;
432
+ if (!isMissingDatabaseError(error) || attempt === 10) {
433
+ break;
434
+ }
435
+ await Bun.sleep(2_000);
436
+ }
437
+ }
438
+ throw lastError;
439
+ }
440
+
419
441
  async function runDoctor() {
420
442
  const results: Array<{ name: string; status: DoctorStatus; detail: string }> = [];
421
443
 
@@ -3,7 +3,7 @@ import { mkdir, rm, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
  import { mkdtempSync } from "node:fs";
6
- import { isLocalDatabaseUrl, resolveCommandPath } from "./lib";
6
+ import { isLocalDatabaseUrl, isMissingDatabaseError, resolveCommandPath } from "./lib";
7
7
 
8
8
  test("resolveCommandPath prefers repo-local bins", async () => {
9
9
  const root = mkdtempSync(join(tmpdir(), "create-svc-workers-bin-"));
@@ -26,3 +26,7 @@ test("isLocalDatabaseUrl detects localhost database URLs", () => {
26
26
  expect(isLocalDatabaseUrl("not a url")).toBe(false);
27
27
  });
28
28
 
29
+ test("isMissingDatabaseError detects Neon database propagation failures", () => {
30
+ expect(isMissingDatabaseError(new Error('database "codex_workers" does not exist'))).toBe(true);
31
+ expect(isMissingDatabaseError(new Error("password authentication failed"))).toBe(false);
32
+ });
@@ -18,3 +18,7 @@ export function isLocalDatabaseUrl(value: string) {
18
18
  }
19
19
  }
20
20
 
21
+ export function isMissingDatabaseError(error: unknown) {
22
+ const message = error instanceof Error ? error.message : String(error);
23
+ return /database ".+" does not exist/.test(message);
24
+ }