naro-cli 0.24.0 → 0.25.0

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.
@@ -310,6 +310,13 @@ function sanitizeRoutes(value) {
310
310
  var MAX_FILE_BYTES = 25 * 1024 * 1024;
311
311
  var MAX_ARTIFACT_BYTES = 64 * 1024 * 1024;
312
312
 
313
+ // ../../packages/core/src/brand-case.ts
314
+ var CLI_WORDS = "analytics|auth|backend|cancel|confirm|db|deploy|deployments|domains|env|environments|filters|functions|gen|git|insights|inspect|link|login|logout|logs|migration|policy|project|projects|promote|protection|realtime|secrets|storage|token|unlink|usage|whoami|dev|init|help|version|rollback";
315
+ var IDENTIFIER = new RegExp(
316
+ `(?:[@_\\-/.:$]naro\\b|\\bnaro[_\\-/:]|\\bnaro\\.\\w|\\bnaro (?:${CLI_WORDS}) (?:[a-z][a-z-]*|--)|\\bnaro --|\\bnaro (?:\u2026|\\.\\.\\.)|\\bnaro [a-z][a-z-]*:)`,
317
+ "g"
318
+ );
319
+
313
320
  // ../../packages/core/src/deployment.ts
314
321
  var PHASES = [
315
322
  "QUEUED",
@@ -453,7 +460,7 @@ function quoted(span, slug, switchContext) {
453
460
  }
454
461
  }
455
462
  var QUOTED = /`(naro [^`\n]+)`/g;
456
- var BARE = /(?<![`\w/-])naro ([a-z]+)((?: [a-z][a-z-]*)*)/g;
463
+ var BARE = /(?<![`\w/-])naro ([a-z]+)((?: (?!naro\b)[a-z][a-z-]*)*)/g;
457
464
  function bare(match, command, rest, after, slug, switchContext) {
458
465
  const vocabulary = SUBCOMMANDS[command];
459
466
  if (vocabulary === void 0) return match;
@@ -463,7 +470,7 @@ function bare(match, command, rest, after, slug, switchContext) {
463
470
  if (count2 === 0) return match;
464
471
  const head = ["naro", command, ...words.slice(0, count2)];
465
472
  const tail = words.slice(count2);
466
- const following = after.split(/\n|[.;,)](?:\s|$)/, 1)[0] ?? "";
473
+ const following = after.split(/\n|[.;,)](?:\s|$)|\uE000/, 1)[0] ?? "";
467
474
  if (/(?:^|\s)--env[\s=]/.test(following) || after.startsWith(` \u2014 ${PRODUCTION_SWITCH_NOTE}`))
468
475
  return match;
469
476
  const shown = head.join(" ");
@@ -633,7 +640,13 @@ var DEPLOYMENT_ERROR_CODES = [
633
640
  /** A deployment failure: the environment was deleted before its deployment finished. */
634
641
  "ENVIRONMENT_DELETED",
635
642
  /** The edge refused the environment's protection change; the database change is rolled back. */
636
- "ENVIRONMENT_PROTECTION_PUBLISH_FAILED"
643
+ "ENVIRONMENT_PROTECTION_PUBLISH_FAILED",
644
+ /** The variable came from the Supabase connection; only its card changes it (spec 2026-11-04 §6.3). */
645
+ "ENV_MANAGED",
646
+ /** The 10-minute Supabase sign-in cookie is gone or expired (spec 2026-11-04 §6.4). */
647
+ "SUPABASE_SESSION_EXPIRED",
648
+ /** The Supabase Management API refused or did not answer (spec 2026-11-04 §6.4). */
649
+ "SUPABASE_API_FAILED"
637
650
  ];
638
651
  var DeploymentError = class extends Error {
639
652
  code;
@@ -650,6 +663,15 @@ var DeploymentError = class extends Error {
650
663
  // ../../packages/core/src/functions.ts
651
664
  var FUNCTION_NAME_RE = /^[a-z][a-z0-9_-]{0,63}$/;
652
665
  var FUNCTION_WORKER_NAME_PREFIX = "naro-fn-";
666
+ var FUNCTION_INVOKE_METHODS = [
667
+ "GET",
668
+ "POST",
669
+ "PUT",
670
+ "PATCH",
671
+ "DELETE"
672
+ ];
673
+ var FUNCTION_INVOKE_MAX_RESPONSE_BYTES = 65536;
674
+ var FUNCTION_INVOKE_TIMEOUT_MS = 12e4;
653
675
  function isFunctionName(name) {
654
676
  return FUNCTION_NAME_RE.test(name);
655
677
  }
@@ -710,6 +732,33 @@ function projectPath(teamSlug, projectSlug) {
710
732
  // ../../packages/core/src/platform-mail.ts
711
733
  var encoder = new TextEncoder();
712
734
 
735
+ // ../../packages/core/src/read-capped.ts
736
+ async function readCapped(response, limit) {
737
+ const reader = response.body?.getReader();
738
+ if (!reader) return { text: "", truncated: false };
739
+ const chunks = [];
740
+ let size = 0;
741
+ while (size <= limit) {
742
+ const { done, value } = await reader.read();
743
+ if (done) break;
744
+ chunks.push(value);
745
+ size += value.byteLength;
746
+ }
747
+ if (size > limit) await reader.cancel().catch(() => void 0);
748
+ const all = new Uint8Array(Math.min(size, limit));
749
+ let offset = 0;
750
+ for (const chunk of chunks) {
751
+ const part = chunk.subarray(
752
+ 0,
753
+ Math.min(chunk.byteLength, all.byteLength - offset)
754
+ );
755
+ all.set(part, offset);
756
+ offset += part.byteLength;
757
+ if (offset >= all.byteLength) break;
758
+ }
759
+ return { text: new TextDecoder().decode(all), truncated: size > limit };
760
+ }
761
+
713
762
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
714
763
  var external_exports = {};
715
764
  __export(external_exports, {
@@ -15614,7 +15663,7 @@ var REQUESTS = {
15614
15663
  blocked: "Sites on this team answer 503 until the month ends (UTC).",
15615
15664
  running: "Sites keep running for now.",
15616
15665
  button: "View usage",
15617
- footer: (team) => `You're receiving this because you're an owner or billing member of ${team} on naro.`
15666
+ footer: (team) => `You're receiving this because you're an owner or billing member of ${team} on Naro.`
15618
15667
  },
15619
15668
  ko: {
15620
15669
  subject80: (team) => `${team} \uD300\uC774 \uC774\uBC88 \uB2EC \uC694\uCCAD \uD55C\uB3C4\uC758 80%\uB97C \uC37C\uC2B5\uB2C8\uB2E4`,
@@ -15624,7 +15673,7 @@ var REQUESTS = {
15624
15673
  blocked: "\uC774\uBC88 \uB2EC\uC774 \uB05D\uB0A0 \uB54C\uAE4C\uC9C0(UTC) \uC774 \uD300\uC758 \uC0AC\uC774\uD2B8\uB294 503\uC73C\uB85C \uC751\uB2F5\uD569\uB2C8\uB2E4.",
15625
15674
  running: "\uC9C0\uAE08\uC740 \uC0AC\uC774\uD2B8\uAC00 \uACC4\uC18D \uB3D9\uC791\uD569\uB2C8\uB2E4.",
15626
15675
  button: "\uC0AC\uC6A9\uB7C9 \uBCF4\uAE30",
15627
- footer: (team) => `naro\uC5D0\uC11C ${team} \uD300\uC758 owner \uB610\uB294 billing \uC5ED\uD560\uC774\uB77C \uC774 \uBA54\uC77C\uC744 \uBC1B\uC558\uC2B5\uB2C8\uB2E4.`
15676
+ footer: (team) => `Naro\uC5D0\uC11C ${team} \uD300\uC758 owner \uB610\uB294 billing \uC5ED\uD560\uC774\uB77C \uC774 \uBA54\uC77C\uC744 \uBC1B\uC558\uC2B5\uB2C8\uB2E4.`
15628
15677
  },
15629
15678
  ja: {
15630
15679
  subject80: (team) => `${team} \u30C1\u30FC\u30E0\u304C\u4ECA\u6708\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u4E0A\u9650\u306E80%\u3092\u4F7F\u7528\u3057\u307E\u3057\u305F`,
@@ -15634,7 +15683,7 @@ var REQUESTS = {
15634
15683
  blocked: "\u6708\u672B(UTC)\u307E\u3067\u3001\u3053\u306E\u30C1\u30FC\u30E0\u306E\u30B5\u30A4\u30C8\u306F503\u3092\u8FD4\u3057\u307E\u3059\u3002",
15635
15684
  running: "\u73FE\u5728\u3001\u30B5\u30A4\u30C8\u306F\u5F15\u304D\u7D9A\u304D\u52D5\u4F5C\u3057\u3066\u3044\u307E\u3059\u3002",
15636
15685
  button: "\u4F7F\u7528\u91CF\u3092\u898B\u308B",
15637
- footer: (team) => `naro \u3067 ${team} \u30C1\u30FC\u30E0\u306E owner \u307E\u305F\u306F billing \u30ED\u30FC\u30EB\u306E\u305F\u3081\u3001\u3053\u306E\u30E1\u30FC\u30EB\u3092\u304A\u9001\u308A\u3057\u3066\u3044\u307E\u3059\u3002`
15686
+ footer: (team) => `Naro \u3067 ${team} \u30C1\u30FC\u30E0\u306E owner \u307E\u305F\u306F billing \u30ED\u30FC\u30EB\u306E\u305F\u3081\u3001\u3053\u306E\u30E1\u30FC\u30EB\u3092\u304A\u9001\u308A\u3057\u3066\u3044\u307E\u3059\u3002`
15638
15687
  }
15639
15688
  };
15640
15689
  var BUILDS = {
@@ -16342,7 +16391,7 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
16342
16391
  if (await exists3(workerEntryPath)) {
16343
16392
  throw new DeploymentError(
16344
16393
  "ARTIFACT_INVALID",
16345
- `The project already has a ${GENERATED_WORKER_ENTRY}. naro writes its vinext Worker entry under that name and will not overwrite a file of the project's.`
16394
+ `The project already has a ${GENERATED_WORKER_ENTRY}. Naro writes its vinext Worker entry under that name and will not overwrite a file of the project's.`
16346
16395
  );
16347
16396
  }
16348
16397
  const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
@@ -16395,7 +16444,7 @@ function parseVinextCheckReport(output) {
16395
16444
  const plain = output.replace(ANSI_ESCAPE, "");
16396
16445
  const match = SUMMARY_LINE.exec(plain);
16397
16446
  if (!match) return null;
16398
- const issueLines = [
16447
+ const issueLines2 = [
16399
16448
  ...new Set(
16400
16449
  plain.split("\n").map((line) => ISSUE_LINE.exec(line)?.[1]?.trim()).filter((line) => Boolean(line))
16401
16450
  )
@@ -16410,7 +16459,7 @@ function parseVinextCheckReport(output) {
16410
16459
  supported: Number(match[2]),
16411
16460
  partial: Number(match[3]),
16412
16461
  issues: Number(match[4]),
16413
- issueLines,
16462
+ issueLines: issueLines2,
16414
16463
  partialLines
16415
16464
  };
16416
16465
  }
@@ -16762,10 +16811,10 @@ async function angularOutput(root) {
16762
16811
  } catch {
16763
16812
  workspace = null;
16764
16813
  }
16765
- const project = Object.values(workspace?.projects ?? {}).find(
16814
+ const project2 = Object.values(workspace?.projects ?? {}).find(
16766
16815
  (entry) => entry.projectType === "application"
16767
16816
  );
16768
- const build = project?.architect?.build;
16817
+ const build = project2?.architect?.build;
16769
16818
  const outputPath = build?.options?.outputPath;
16770
16819
  const legacy = build?.builder?.endsWith(":browser") ?? false;
16771
16820
  const clean = (path) => path.replace(/^\.?\/+/, "").replace(/\/+$/, "");
@@ -16777,7 +16826,7 @@ async function angularOutput(root) {
16777
16826
  ""
16778
16827
  );
16779
16828
  const name = Object.entries(workspace?.projects ?? {}).find(
16780
- ([, entry]) => entry === project
16829
+ ([, entry]) => entry === project2
16781
16830
  )?.[0];
16782
16831
  if (name) return `dist/${name}/browser`;
16783
16832
  throw new DeploymentError(
@@ -19681,13 +19730,13 @@ function page(options) {
19681
19730
  <head>
19682
19731
  <meta charset="utf-8">
19683
19732
  <meta name="viewport" content="width=device-width,initial-scale=1">
19684
- <title>${title} \xB7 naro</title>
19733
+ <title>${title} \xB7 Naro</title>
19685
19734
  <link rel="icon" href="${FAVICON}">
19686
19735
  <style>:root{--tone:${tone.color};--tone-soft:${tone.soft};--tone-edge:${tone.edge};--glow:${tone.glow}}${STYLES}</style>
19687
19736
  </head>
19688
19737
  <body>
19689
19738
  <main class="card">
19690
- <span class="brand"><span class="brand-mark">n</span>naro</span>
19739
+ <span class="brand"><span class="brand-mark">n</span>Naro</span>
19691
19740
  <div class="glyph">${tone.icon}</div>
19692
19741
  <h1>${title}</h1>
19693
19742
  <p>${escapeHtml(options.message)}</p>
@@ -19976,6 +20025,271 @@ function openBrowser(url2) {
19976
20025
  child.unref();
19977
20026
  }
19978
20027
 
20028
+ // ../../packages/sdk/src/dns.ts
20029
+ var DnsApi = class {
20030
+ constructor(client) {
20031
+ this.client = client;
20032
+ }
20033
+ base(teamSlug, suffix = "") {
20034
+ return `/api/teams/${encodeURIComponent(teamSlug)}/dns-zones${suffix}`;
20035
+ }
20036
+ zone(teamSlug, zoneId, suffix = "") {
20037
+ return this.base(teamSlug, `/${encodeURIComponent(zoneId)}${suffix}`);
20038
+ }
20039
+ /** The team's zones and the project domains each one could carry. */
20040
+ async state(teamSlug) {
20041
+ return this.client.request(this.base(teamSlug), {
20042
+ context: "Read naro DNS"
20043
+ });
20044
+ }
20045
+ /** Starts moving a domain onto naro DNS; the zone answers its name servers. */
20046
+ async addZone(teamSlug, name) {
20047
+ const body = await this.client.request(
20048
+ this.base(teamSlug),
20049
+ { method: "POST", body: { name }, context: "Start a DNS move" }
20050
+ );
20051
+ return body.zone;
20052
+ }
20053
+ /** Cancels a move, or takes an active zone off naro DNS. */
20054
+ async removeZone(teamSlug, zoneId) {
20055
+ await this.client.request(this.zone(teamSlug, zoneId), {
20056
+ method: "DELETE",
20057
+ context: "Remove a DNS zone"
20058
+ });
20059
+ }
20060
+ /** Reads the zone's status and asks Cloudflare for an activation check. */
20061
+ async check(teamSlug, zoneId) {
20062
+ const body = await this.client.request(
20063
+ this.zone(teamSlug, zoneId, "/check"),
20064
+ { method: "POST", context: "Check a DNS zone" }
20065
+ );
20066
+ return body.zone;
20067
+ }
20068
+ /** The mail records (MX, SPF, DMARC, DKIM) to keep before the move. */
20069
+ async mailCheck(teamSlug, zoneId) {
20070
+ return this.client.request(
20071
+ this.zone(teamSlug, zoneId, "/precheck"),
20072
+ { context: "Check mail records" }
20073
+ );
20074
+ }
20075
+ /** The records Cloudflare's scan of the current DNS found. */
20076
+ async scan(teamSlug, zoneId) {
20077
+ const body = await this.client.request(
20078
+ this.zone(teamSlug, zoneId, "/scan"),
20079
+ { context: "Read the DNS scan" }
20080
+ );
20081
+ return body.records;
20082
+ }
20083
+ /** Adds every scanned record to the zone. */
20084
+ async importScan(teamSlug, zoneId) {
20085
+ return this.client.request(
20086
+ this.zone(teamSlug, zoneId, "/scan"),
20087
+ { method: "POST", context: "Import the DNS scan" }
20088
+ );
20089
+ }
20090
+ async records(teamSlug, zoneId) {
20091
+ return this.client.request(
20092
+ this.zone(teamSlug, zoneId, "/records"),
20093
+ { context: "List DNS records" }
20094
+ );
20095
+ }
20096
+ /** Adds a record, always DNS only. */
20097
+ async addRecord(teamSlug, zoneId, input) {
20098
+ const body = await this.client.request(
20099
+ this.zone(teamSlug, zoneId, "/records"),
20100
+ { method: "POST", body: input, context: "Add a DNS record" }
20101
+ );
20102
+ return body.record;
20103
+ }
20104
+ /** Overwrites the whole record; its type cannot change. */
20105
+ async replaceRecord(teamSlug, zoneId, recordId, input) {
20106
+ const body = await this.client.request(
20107
+ this.zone(teamSlug, zoneId, `/records/${encodeURIComponent(recordId)}`),
20108
+ { method: "PATCH", body: input, context: "Change a DNS record" }
20109
+ );
20110
+ return body.record;
20111
+ }
20112
+ async removeRecord(teamSlug, zoneId, recordId) {
20113
+ await this.client.request(
20114
+ this.zone(teamSlug, zoneId, `/records/${encodeURIComponent(recordId)}`),
20115
+ { method: "DELETE", context: "Remove a DNS record" }
20116
+ );
20117
+ }
20118
+ };
20119
+
20120
+ // ../../packages/sdk/src/edge-config.ts
20121
+ var project = (projectId, suffix) => `/api/projects/${encodeURIComponent(projectId)}${suffix}`;
20122
+ var FirewallApi = class {
20123
+ constructor(client) {
20124
+ this.client = client;
20125
+ }
20126
+ async list(projectId) {
20127
+ const body = await this.client.request(
20128
+ project(projectId, "/firewall"),
20129
+ { context: "List firewall rules" }
20130
+ );
20131
+ return body.rules;
20132
+ }
20133
+ /** `VALIDATION_FAILED` for a bad kind or value, `RULE_LIMIT` past the cap. */
20134
+ async add(projectId, input) {
20135
+ const body = {
20136
+ kind: input.kind,
20137
+ value: input.value
20138
+ };
20139
+ if (input.rateLimit !== void 0) body.rate_limit = input.rateLimit;
20140
+ if (input.periodSeconds !== void 0)
20141
+ body.period_seconds = input.periodSeconds;
20142
+ if (input.enabled !== void 0) body.enabled = input.enabled;
20143
+ const answer = await this.client.request(
20144
+ project(projectId, "/firewall"),
20145
+ { method: "POST", body, context: "Add firewall rule" }
20146
+ );
20147
+ return answer.rule;
20148
+ }
20149
+ async setEnabled(projectId, ruleId, enabled) {
20150
+ const answer = await this.client.request(
20151
+ project(projectId, `/firewall/${encodeURIComponent(ruleId)}`),
20152
+ { method: "PATCH", body: { enabled }, context: "Change firewall rule" }
20153
+ );
20154
+ return answer.rule;
20155
+ }
20156
+ async remove(projectId, ruleId) {
20157
+ await this.client.request(
20158
+ project(projectId, `/firewall/${encodeURIComponent(ruleId)}`),
20159
+ { method: "DELETE", context: "Remove firewall rule" }
20160
+ );
20161
+ }
20162
+ };
20163
+ var FlagsApi = class {
20164
+ constructor(client) {
20165
+ this.client = client;
20166
+ }
20167
+ async list(projectId) {
20168
+ const body = await this.client.request(
20169
+ project(projectId, "/flags"),
20170
+ { context: "List flags" }
20171
+ );
20172
+ return body.flags;
20173
+ }
20174
+ /** Off unless `enabled`. `FLAG_LIMIT` past 32. */
20175
+ async add(projectId, name, enabled = false) {
20176
+ const answer = await this.client.request(
20177
+ project(projectId, "/flags"),
20178
+ { method: "POST", body: { name, enabled }, context: "Add flag" }
20179
+ );
20180
+ return answer.flag;
20181
+ }
20182
+ async setEnabled(projectId, flagId, enabled) {
20183
+ const answer = await this.client.request(
20184
+ project(projectId, `/flags/${encodeURIComponent(flagId)}`),
20185
+ { method: "PATCH", body: { enabled }, context: "Change flag" }
20186
+ );
20187
+ return answer.flag;
20188
+ }
20189
+ async remove(projectId, flagId) {
20190
+ await this.client.request(
20191
+ project(projectId, `/flags/${encodeURIComponent(flagId)}`),
20192
+ { method: "DELETE", context: "Remove flag" }
20193
+ );
20194
+ }
20195
+ };
20196
+ var BindingsApi = class {
20197
+ constructor(client) {
20198
+ this.client = client;
20199
+ }
20200
+ async list(projectId) {
20201
+ const body = await this.client.request(
20202
+ project(projectId, "/storage"),
20203
+ { context: "List storage bindings" }
20204
+ );
20205
+ return body.storage;
20206
+ }
20207
+ /**
20208
+ * Makes the Cloudflare resource and binds it at the next deploy.
20209
+ * `VALIDATION_FAILED` for a bad type or binding name (ASSETS and DB are
20210
+ * reserved), `STORAGE_PROVISION_FAILED` (502) when Cloudflare refused.
20211
+ */
20212
+ async add(projectId, type, bindingName) {
20213
+ const answer = await this.client.request(
20214
+ project(projectId, "/storage"),
20215
+ {
20216
+ method: "POST",
20217
+ body: { type, bindingName },
20218
+ context: "Add storage binding"
20219
+ }
20220
+ );
20221
+ return answer.storage;
20222
+ }
20223
+ /**
20224
+ * Deletes the Cloudflare resource and what it holds — it cannot be undone.
20225
+ * `STORAGE_MANAGED` refuses the Naro database.
20226
+ */
20227
+ async remove(projectId, bindingId) {
20228
+ await this.client.request(
20229
+ project(projectId, `/storage/${encodeURIComponent(bindingId)}`),
20230
+ { method: "DELETE", context: "Remove storage binding" }
20231
+ );
20232
+ }
20233
+ };
20234
+
20235
+ // ../../packages/sdk/src/ops.ts
20236
+ var BillingApi = class {
20237
+ constructor(client) {
20238
+ this.client = client;
20239
+ }
20240
+ /** `usage.read`: `403 FORBIDDEN "Your role cannot read this team's billing."` otherwise. */
20241
+ async get(teamSlug) {
20242
+ const body = await this.client.request(`/api/teams/${encodeURIComponent(teamSlug)}/billing`, {
20243
+ context: "Read billing"
20244
+ });
20245
+ return {
20246
+ team: body.team,
20247
+ subscription: body.subscription,
20248
+ canManage: body.canManage,
20249
+ overage: body.overage
20250
+ };
20251
+ }
20252
+ };
20253
+ var ObservabilityApi = class {
20254
+ constructor(client) {
20255
+ this.client = client;
20256
+ }
20257
+ /** 7d unless given; `400 VALIDATION_FAILED "range must be 24h, 7d or 30d."` otherwise. */
20258
+ async report(projectId, options = {}) {
20259
+ const query = options.range === void 0 ? "" : `?range=${encodeURIComponent(options.range)}`;
20260
+ return this.client.request(
20261
+ `/api/projects/${encodeURIComponent(projectId)}/observability${query}`,
20262
+ { context: "Read observability" }
20263
+ );
20264
+ }
20265
+ };
20266
+
20267
+ // ../../packages/sdk/src/supabase-links.ts
20268
+ var SupabaseApi = class {
20269
+ constructor(client) {
20270
+ this.client = client;
20271
+ }
20272
+ async links(projectId) {
20273
+ const body = await this.client.request(
20274
+ `/api/projects/${encodeURIComponent(projectId)}/supabase`,
20275
+ { context: "Read Supabase links" }
20276
+ );
20277
+ return body.links;
20278
+ }
20279
+ /**
20280
+ * Removes the link of one target (production, preview or an environment's
20281
+ * slug), or every link the role may write when `target` is left out, with
20282
+ * the env rows they hold. Answers how many went.
20283
+ */
20284
+ async disconnect(projectId, options = {}) {
20285
+ const query = options.target === void 0 ? "" : `?target=${encodeURIComponent(options.target)}`;
20286
+ return this.client.request(
20287
+ `/api/projects/${encodeURIComponent(projectId)}/supabase${query}`,
20288
+ { method: "DELETE", context: "Disconnect Supabase" }
20289
+ );
20290
+ }
20291
+ };
20292
+
19979
20293
  // ../../packages/sdk/src/environments.ts
19980
20294
  function isReservedEnvTarget(value) {
19981
20295
  return RESERVED_ENVIRONMENT_SLUGS.includes(value);
@@ -20110,6 +20424,13 @@ var NaroClient = class _NaroClient {
20110
20424
  deployments;
20111
20425
  env;
20112
20426
  domains;
20427
+ dns;
20428
+ firewall;
20429
+ flags;
20430
+ bindings;
20431
+ billing;
20432
+ observability;
20433
+ supabase;
20113
20434
  logs;
20114
20435
  db;
20115
20436
  backend;
@@ -20133,6 +20454,13 @@ var NaroClient = class _NaroClient {
20133
20454
  this.deployments = new DeploymentsApi(this);
20134
20455
  this.env = new EnvApi(this);
20135
20456
  this.domains = new DomainsApi(this);
20457
+ this.dns = new DnsApi(this);
20458
+ this.firewall = new FirewallApi(this);
20459
+ this.flags = new FlagsApi(this);
20460
+ this.bindings = new BindingsApi(this);
20461
+ this.billing = new BillingApi(this);
20462
+ this.observability = new ObservabilityApi(this);
20463
+ this.supabase = new SupabaseApi(this);
20136
20464
  this.logs = new LogsApi(this);
20137
20465
  this.db = new DbApi(this);
20138
20466
  this.backend = new BackendApi(this);
@@ -20217,7 +20545,93 @@ var TeamsApi = class {
20217
20545
  { context: "Read team usage" }
20218
20546
  );
20219
20547
  }
20548
+ path(teamSlug, suffix = "") {
20549
+ return `/api/teams/${encodeURIComponent(teamSlug)}${suffix}`;
20550
+ }
20551
+ /** A new team, owned by the caller. `TEAM_SLUG_TAKEN` (409) for a slug in use. */
20552
+ async create(slug, name) {
20553
+ const body = await this.client.request("/api/teams", {
20554
+ method: "POST",
20555
+ body: { slug, name },
20556
+ context: "Create team"
20557
+ });
20558
+ return teamSummary(body.team);
20559
+ }
20560
+ /** Renames the team or changes its slug (`team.update`); only the fields given. */
20561
+ async update(teamSlug, patch) {
20562
+ const body = await this.client.request(
20563
+ this.path(teamSlug),
20564
+ { method: "PATCH", body: patch, context: "Update team" }
20565
+ );
20566
+ return teamSummary(body.team);
20567
+ }
20568
+ /**
20569
+ * Deletes the team with its projects and their data. Owner only;
20570
+ * `PERSONAL_TEAM` and `SUBSCRIPTION_ACTIVE` (cancel the subscription first)
20571
+ * refuse it. The route tears every project down before it answers, so this
20572
+ * waits two minutes, like `projects.delete`.
20573
+ */
20574
+ async delete(teamSlug) {
20575
+ await this.client.request(this.path(teamSlug), {
20576
+ method: "DELETE",
20577
+ context: "Delete team",
20578
+ timeoutMs: PROVISION_TIMEOUT_MS
20579
+ });
20580
+ }
20581
+ /** The members the caller may see: all of them with `team.read`, else only themselves. */
20582
+ async members(teamSlug) {
20583
+ const body = await this.client.request(
20584
+ this.path(teamSlug, "/members"),
20585
+ { context: "List team members" }
20586
+ );
20587
+ return body.members;
20588
+ }
20589
+ /** `team.members.manage`; `LAST_OWNER` (409) keeps the team's last owner. */
20590
+ async setMemberRole(teamSlug, userId, role) {
20591
+ await this.client.request(
20592
+ this.path(teamSlug, `/members/${encodeURIComponent(userId)}`),
20593
+ { method: "PATCH", body: { role }, context: "Change member role" }
20594
+ );
20595
+ }
20596
+ /** Removes a member, or leaves the team when it is the caller. */
20597
+ async removeMember(teamSlug, userId) {
20598
+ await this.client.request(
20599
+ this.path(teamSlug, `/members/${encodeURIComponent(userId)}`),
20600
+ { method: "DELETE", context: "Remove member" }
20601
+ );
20602
+ }
20603
+ async invites(teamSlug) {
20604
+ const body = await this.client.request(
20605
+ this.path(teamSlug, "/invites"),
20606
+ { context: "List invites" }
20607
+ );
20608
+ return body.invites;
20609
+ }
20610
+ /**
20611
+ * A new invite link; with `email` the control plane also mails it.
20612
+ * `PERSONAL_TEAM` refuses a personal team, `INVALID_EMAIL` a bad address.
20613
+ */
20614
+ async createInvite(teamSlug, options = {}) {
20615
+ return this.client.request(
20616
+ this.path(teamSlug, "/invites"),
20617
+ { method: "POST", body: options, context: "Create invite" }
20618
+ );
20619
+ }
20620
+ async revokeInvite(teamSlug, inviteId) {
20621
+ await this.client.request(
20622
+ this.path(teamSlug, `/invites/${encodeURIComponent(inviteId)}`),
20623
+ { method: "DELETE", context: "Revoke invite" }
20624
+ );
20625
+ }
20220
20626
  };
20627
+ function teamSummary(row) {
20628
+ return {
20629
+ id: row.id,
20630
+ slug: row.slug,
20631
+ name: row.name,
20632
+ isPersonal: row.is_personal
20633
+ };
20634
+ }
20221
20635
  var ProjectsApi = class {
20222
20636
  constructor(client) {
20223
20637
  this.client = client;
@@ -20236,6 +20650,28 @@ var ProjectsApi = class {
20236
20650
  );
20237
20651
  return projectInfo(body.project);
20238
20652
  }
20653
+ /**
20654
+ * Deletes the project and everything under it: its workers, its D1 and
20655
+ * Storage, its domains, environments and deployment history. It cannot be
20656
+ * undone. `project.delete` (owner and admin); a suspended project is `403
20657
+ * FORBIDDEN`, one that is gone or out of sight `404 NOT_FOUND`. Only the
20658
+ * CLI calls it, behind typing the name — naro-mcp has no tool for it
20659
+ * (project_cli_mcp_backlog #6).
20660
+ *
20661
+ * Two minutes, like `backend.enable`: the route answers only after it has
20662
+ * torn down the workers, D1s, backends and environments one after another
20663
+ * (PR #420 review). A TIMEOUT does not mean the project is still there.
20664
+ */
20665
+ async delete(projectId) {
20666
+ await this.client.request(
20667
+ `/api/projects/${encodeURIComponent(projectId)}`,
20668
+ {
20669
+ method: "DELETE",
20670
+ context: "Delete project",
20671
+ timeoutMs: PROVISION_TIMEOUT_MS
20672
+ }
20673
+ );
20674
+ }
20239
20675
  /**
20240
20676
  * Connects a project to a GitHub repository, turning `git push` into a
20241
20677
  * deployment.
@@ -20269,6 +20705,52 @@ var ProjectsApi = class {
20269
20705
  );
20270
20706
  return projectInfo(body.project);
20271
20707
  }
20708
+ /**
20709
+ * The dashboard's General settings: only the fields given are sent, and the
20710
+ * control plane checks them (`400 VALIDATION_FAILED "Check the project
20711
+ * settings."`, `SUBDOMAIN_INVALID`, `SUBDOMAIN_RESERVED`, `409
20712
+ * SUBDOMAIN_TAKEN`). A new subdomain moves the project's URL at once: the
20713
+ * old `{subdomain}.naro.sh` stops answering. Build settings need
20714
+ * `project.update`.
20715
+ */
20716
+ async update(projectId, patch) {
20717
+ const body = await this.client.request(
20718
+ `/api/projects/${encodeURIComponent(projectId)}`,
20719
+ { method: "PATCH", body: patch, context: "Update project" }
20720
+ );
20721
+ return projectInfo(body.project);
20722
+ }
20723
+ /** Whether a subdomain is free: `reason` is `taken`, `reserved` or the format problem. */
20724
+ async subdomainAvailability(name) {
20725
+ return this.client.request(
20726
+ `/api/subdomains/availability?name=${encodeURIComponent(name)}`,
20727
+ { context: "Check subdomain" }
20728
+ );
20729
+ }
20730
+ /** The project roles given to the team's contributors. */
20731
+ async access(projectId) {
20732
+ const body = await this.client.request(`/api/projects/${encodeURIComponent(projectId)}/access`, {
20733
+ context: "List project access"
20734
+ });
20735
+ return body.assignments;
20736
+ }
20737
+ /** Gives a contributor a project role (`project.update`); a member of another role is refused. */
20738
+ async grantAccess(projectId, userId, role) {
20739
+ await this.client.request(
20740
+ `/api/projects/${encodeURIComponent(projectId)}/access`,
20741
+ {
20742
+ method: "PUT",
20743
+ body: { userId, role },
20744
+ context: "Grant project access"
20745
+ }
20746
+ );
20747
+ }
20748
+ async revokeAccess(projectId, userId) {
20749
+ await this.client.request(
20750
+ `/api/projects/${encodeURIComponent(projectId)}/access?userId=${encodeURIComponent(userId)}`,
20751
+ { method: "DELETE", context: "Revoke project access" }
20752
+ );
20753
+ }
20272
20754
  };
20273
20755
  var GithubApi = class {
20274
20756
  constructor(client) {
@@ -20510,7 +20992,7 @@ var EnvApi = class {
20510
20992
  */
20511
20993
  async takenBindingNames(projectId) {
20512
20994
  const base2 = `/api/projects/${encodeURIComponent(projectId)}`;
20513
- const [storage, project] = await Promise.all([
20995
+ const [storage, project2] = await Promise.all([
20514
20996
  this.client.request(
20515
20997
  `${base2}/storage`,
20516
20998
  { context: "List storage" }
@@ -20524,7 +21006,7 @@ var EnvApi = class {
20524
21006
  STATIC_ASSETS_BINDING,
20525
21007
  ...storage.storage.map((row) => row.bindingName)
20526
21008
  ]);
20527
- if (project.project.database_provider === "d1")
21009
+ if (project2.project.database_provider === "d1")
20528
21010
  names.add(MANAGED_DATABASE_BINDING);
20529
21011
  return [...names];
20530
21012
  }
@@ -21816,6 +22298,41 @@ var StorageApi = class {
21816
22298
  );
21817
22299
  return body.objects;
21818
22300
  }
22301
+ /**
22302
+ * One object under another name in its bucket (Storage 3-1i), moved by the
22303
+ * project's Naro worker with the stored service key. It never replaces an
22304
+ * object (`409 OBJECT_EXISTS`); both names are held to the name rules
22305
+ * (`INVALID_PATH`). An old worker answers `NOT_FOUND` until
22306
+ * `naro backend enable` updates it.
22307
+ */
22308
+ async moveObject(projectId, bucket, from, to) {
22309
+ return this.client.request(
22310
+ this.bucketPath(projectId, bucket, "/objects/move"),
22311
+ { method: "POST", body: { from, to }, context: "Move an object" }
22312
+ );
22313
+ }
22314
+ /**
22315
+ * A token that lets whoever holds it upload `path` once, for two hours,
22316
+ * without a key (Storage 3-1e §4.5) — the bytes go straight to the
22317
+ * project's worker, never through the control plane. `upsert` lets it
22318
+ * replace a file that is there (`409 OBJECT_EXISTS` otherwise, at upload).
22319
+ * `signedUrl` is built the worker's way: each path segment encoded.
22320
+ */
22321
+ async createUploadUrl(projectId, bucket, path, options = {}) {
22322
+ const body = await this.client.request(
22323
+ this.bucketPath(projectId, bucket, "/objects/upload-url"),
22324
+ {
22325
+ method: "POST",
22326
+ body: { path, ...options.upsert === true && { upsert: true } },
22327
+ context: "Create an upload URL"
22328
+ }
22329
+ );
22330
+ const encoded = body.path.split("/").map(encodeURIComponent).join("/");
22331
+ return {
22332
+ ...body,
22333
+ signedUrl: `${body.url}/_naro/v1/storage/object/upload/sign/${encodeURIComponent(bucket)}/${encoded}?token=${encodeURIComponent(body.token)}`
22334
+ };
22335
+ }
21819
22336
  /**
21820
22337
  * A link that downloads one object without a key, for `expiresIn` seconds
21821
22338
  * (1–3600 through the control plane; `VALIDATION_FAILED` past it), on the
@@ -23400,10 +23917,10 @@ var REALTIME_COMMAND_PROBLEM = "realtime.messages takes SELECT, INSERT and ALL p
23400
23917
  var REALTIME_COLUMNS_PROBLEM = "realtime.messages policies are checked once per join, so they can read topic, extension and private only";
23401
23918
  var REALTIME_TOPIC_PROBLEM = "realtime.topic() is available only in policies of realtime.messages";
23402
23919
  function quotedRealtimeMessagesProblem(written) {
23403
- return `a table named "${written}" cannot be used in policies, as naro keeps the name for Realtime: write realtime.messages, without quotes, for Realtime's messages`;
23920
+ return `a table named "${written}" cannot be used in policies, as Naro keeps the name for Realtime: write realtime.messages, without quotes, for Realtime's messages`;
23404
23921
  }
23405
23922
  function quotedRealtimeAliasProblem(written) {
23406
- return `an alias named "${written}" cannot be used in policies, as naro keeps the name for Realtime: give the subquery another alias`;
23923
+ return `an alias named "${written}" cannot be used in policies, as Naro keeps the name for Realtime: give the subquery another alias`;
23407
23924
  }
23408
23925
  function realtimeFunctionProblem(name) {
23409
23926
  return `function realtime.${name} is not available in policies: use realtime.topic()`;
@@ -23888,7 +24405,7 @@ function storageHelperColumnProblem(fn) {
23888
24405
  return `storage.${fn}() takes the name column of storage.objects`;
23889
24406
  }
23890
24407
  function quotedStorageObjectsProblem(written) {
23891
- return `a table named "${written}" cannot be used in policies, as naro keeps the name for Storage: write storage.objects, without quotes, for Storage's objects`;
24408
+ return `a table named "${written}" cannot be used in policies, as Naro keeps the name for Storage: write storage.objects, without quotes, for Storage's objects`;
23892
24409
  }
23893
24410
  function count(state, token) {
23894
24411
  if (++state.nodes > MAX_EXPRESSION_NODES)
@@ -24346,7 +24863,7 @@ function readTableName(stream) {
24346
24863
  if (stream.acceptOperator(".")) {
24347
24864
  if (first.quoted ? name !== "public" : asciiLower(name) !== "public")
24348
24865
  throw new PolicySyntaxError(
24349
- `schema ${name} does not exist: naro's tables are in public`,
24866
+ `schema ${name} does not exist: Naro's tables are in public`,
24350
24867
  first.position
24351
24868
  );
24352
24869
  token = stream.peek();
@@ -24457,7 +24974,7 @@ function readJwtAccess(stream) {
24457
24974
  const next = stream.peek();
24458
24975
  if (isOperator(next, "->") || isOperator(next, "->>"))
24459
24976
  throw new PolicySyntaxError(
24460
- "naro's auth.jwt() claims are not nested",
24977
+ "Naro's auth.jwt() claims are not nested",
24461
24978
  next.position
24462
24979
  );
24463
24980
  return {
@@ -24556,7 +25073,7 @@ function readPrimary(state) {
24556
25073
  stream.next();
24557
25074
  if (isOperator(stream.peek(), "(") || isOperator(stream.peek(), "."))
24558
25075
  throw new PolicySyntaxError(
24559
- `${token.value}.${name.value} is not a column: naro reads column or table.column`,
25076
+ `${token.value}.${name.value} is not a column: Naro reads column or table.column`,
24560
25077
  token.position
24561
25078
  );
24562
25079
  return { kind: "column", qualifier: token.value, name: name.value };
@@ -24609,7 +25126,7 @@ function readRealtimeColumn(state) {
24609
25126
  const next = stream.peek();
24610
25127
  if (isOperator(next, "(") || isOperator(next, "."))
24611
25128
  throw new PolicySyntaxError(
24612
- `realtime.messages.${name} is not a column: naro reads realtime.messages.column`,
25129
+ `realtime.messages.${name} is not a column: Naro reads realtime.messages.column`,
24613
25130
  next.position
24614
25131
  );
24615
25132
  return { kind: "column", qualifier: REALTIME_MESSAGES, name };
@@ -26531,7 +27048,7 @@ function textTruthProblem(snapshot, table, expr, names = [asciiLower(table.name)
26531
27048
  function selectCheckRefusal(path, column2, problem) {
26532
27049
  const reason = {
26533
27050
  generated: `is not available: the select policy reads the generated column ${column2}, which a new row cannot be checked against`,
26534
- collation: `is not available: the select policy reads ${column2}, whose collation naro cannot read, so a new row cannot be checked against it`,
27051
+ collation: `is not available: the select policy reads ${column2}, whose collation Naro cannot read, so a new row cannot be checked against it`,
26535
27052
  pattern: `is not available: the select policy compares the number column ${column2} with like or ilike, which a new row cannot be checked against`,
26536
27053
  textual: `is not available: the select policy turns the number column ${column2} into text, which a new row cannot be checked against`,
26537
27054
  readBack: `is not available: the select policy reads ${column2}, a number in a column that declares no number type, as text, which a new row cannot be checked against`,
@@ -26929,7 +27446,7 @@ function checkProblemReason(column2, problem) {
26929
27446
  case "generated":
26930
27447
  return `${column2} is a generated column, which a WITH CHECK cannot read (an update policy without WITH CHECK checks its USING)`;
26931
27448
  case "collation":
26932
- return `${column2} has a collation naro cannot read, which a WITH CHECK needs to compare it`;
27449
+ return `${column2} has a collation Naro cannot read, which a WITH CHECK needs to compare it`;
26933
27450
  case "pattern":
26934
27451
  return `${column2} is a number column, which a WITH CHECK cannot compare with LIKE or ILIKE`;
26935
27452
  case "textual":
@@ -26945,7 +27462,7 @@ function usingProblemReason(name, problem) {
26945
27462
  case "generated":
26946
27463
  return `${name} is a generated column, which a USING judged on a row's values cannot read`;
26947
27464
  case "collation":
26948
- return `${name} has a collation naro cannot read, which a USING judged on a row's values needs to compare it`;
27465
+ return `${name} has a collation Naro cannot read, which a USING judged on a row's values needs to compare it`;
26949
27466
  case "pattern":
26950
27467
  return `${name} is a number column, which a USING judged on a row's values cannot compare with LIKE or ILIKE`;
26951
27468
  case "textual":
@@ -27343,7 +27860,7 @@ function checkCase(table, terms, guard, pass, action, nonce) {
27343
27860
  fail(
27344
27861
  invalidQuery(
27345
27862
  action === "insert" ? "returning" : "values",
27346
- `is not available: naro cannot read the schema of ${table.name}, so a new row cannot be checked against its select policy`
27863
+ `is not available: Naro cannot read the schema of ${table.name}, so a new row cannot be checked against its select policy`
27347
27864
  )
27348
27865
  );
27349
27866
  fail(policyInvalid(first.policy, action));
@@ -27505,7 +28022,7 @@ function checkFragment(target2, req, snapshot, command, row, nonce, stored, with
27505
28022
  const first = terms.find((term) => term.policy !== void 0)?.policy;
27506
28023
  return fail(
27507
28024
  first ? policyInvalid(first, command) : dataError("INTERNAL_ERROR"),
27508
- `naro cannot read the schema of ${target2.name}, so a check of the stored row cannot be guarded`
28025
+ `Naro cannot read the schema of ${target2.name}, so a check of the stored row cannot be guarded`
27509
28026
  );
27510
28027
  }
27511
28028
  return [schemaGuard(name, sql, command, checked), ...guards, ...terms];
@@ -28686,7 +29203,7 @@ var Reader = class {
28686
29203
  }
28687
29204
  if (this.takeWord("WHERE")) top.resolved.push(this.where());
28688
29205
  this.returning(top.resolved);
28689
- } else throw new Unreadable("not a statement naro compiles");
29206
+ } else throw new Unreadable("not a statement Naro compiles");
28690
29207
  return top;
28691
29208
  }
28692
29209
  };
@@ -30826,9 +31343,9 @@ var SYSTEM_MIGRATIONS = [
30826
31343
  ];
30827
31344
 
30828
31345
  // ../../packages/data-core/src/policy-migration.ts
30829
- var GRANT_REFUSAL = "naro has no GRANT or REVOKE: every table is behind row-level security, so write a policy instead (CREATE POLICY \u2026).";
31346
+ var GRANT_REFUSAL = "Naro has no GRANT or REVOKE: every table is behind row-level security, so write a policy instead (CREATE POLICY \u2026).";
30830
31347
  function disableRefusal(table) {
30831
- return `naro keeps row-level security on for every table. To let everyone through, create a policy: CREATE POLICY "open" ON ${policyTableSql(table)} USING (true) WITH CHECK (true);`;
31348
+ return `Naro keeps row-level security on for every table. To let everyone through, create a policy: CREATE POLICY "open" ON ${policyTableSql(table)} USING (true) WITH CHECK (true);`;
30832
31349
  }
30833
31350
  function isStorageObjects(table) {
30834
31351
  return asciiLower(table) === STORAGE_OBJECTS;
@@ -31549,6 +32066,159 @@ function lintSchema(input) {
31549
32066
  ];
31550
32067
  }
31551
32068
 
32069
+ // ../../packages/sdk/src/lint-database.ts
32070
+ function hasCode(error51, code) {
32071
+ return typeof error51 === "object" && error51 !== null && error51.code === code;
32072
+ }
32073
+ var DIAGNOSTICS_SQL = [
32074
+ `SELECT name FROM sqlite_master WHERE type = 'trigger' AND ${systemTablePredicate("name")} ORDER BY name;`,
32075
+ "SELECT name, type FROM pragma_table_list WHERE schema = 'main' AND type IN ('virtual', 'shadow') ORDER BY name;",
32076
+ "SELECT * FROM pragma_quick_check;",
32077
+ "SELECT * FROM pragma_foreign_key_check;"
32078
+ ].join("\n");
32079
+ var POLICIES_TRUNCATED_NOTICE = "\u26A0 The policies were cut at 10,000 rows, so they were not checked.";
32080
+ var POLICY_TABLE_PROBE = `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '${NAMED_POLICY_TABLE}'`;
32081
+ var POLICY_READ_SQL = [NAMED_POLICY_SQL.list, INDEX_SQL, ...SCHEMA_SQL].map((sql) => `${sql};`).join("\n");
32082
+ function objectsOf(statement2) {
32083
+ return statement2.rows.map(
32084
+ (row) => Object.fromEntries(
32085
+ statement2.columns.map((column2, index) => [column2, row[index]])
32086
+ )
32087
+ );
32088
+ }
32089
+ async function readPolicies(client, projectId, warn2) {
32090
+ let backend;
32091
+ try {
32092
+ backend = await client.backend.info(projectId);
32093
+ } catch (error51) {
32094
+ if (hasCode(error51, "BACKEND_DISABLED")) return void 0;
32095
+ throw error51;
32096
+ }
32097
+ if (backend.status === "none") return void 0;
32098
+ const probe = await client.db.query(projectId, {
32099
+ sql: POLICY_TABLE_PROBE,
32100
+ readOnly: true
32101
+ });
32102
+ if ((probe.statements[0]?.rows.length ?? 0) === 0) return void 0;
32103
+ const answer = await client.db.query(projectId, { sql: POLICY_READ_SQL });
32104
+ const [stored, indexes, ...schema] = answer.statements;
32105
+ const expected = SCHEMA_SQL.length + 2;
32106
+ if (stored === void 0 || indexes === void 0 || answer.statements.length !== expected)
32107
+ throw new NaroError(
32108
+ "DATABASE_QUERY_FAILED",
32109
+ `The policies could not be read: the database answered with ${String(answer.statements.length)} result sets instead of ${String(expected)}.`
32110
+ );
32111
+ if (answer.truncated) {
32112
+ warn2(POLICIES_TRUNCATED_NOTICE);
32113
+ return void 0;
32114
+ }
32115
+ const policies = [];
32116
+ const unreadable = [];
32117
+ for (const row of objectsOf(stored)) {
32118
+ const policy = loadPolicy(row);
32119
+ if (policy === void 0) continue;
32120
+ if (policy.invalid !== null)
32121
+ unreadable.push({
32122
+ table: policy.table,
32123
+ name: policy.name,
32124
+ reason: policy.invalid
32125
+ });
32126
+ else
32127
+ policies.push({
32128
+ table: policy.table,
32129
+ name: policy.name,
32130
+ permissive: policy.permissive,
32131
+ command: policy.command,
32132
+ roles: policy.roles,
32133
+ using: policy.using,
32134
+ check: policy.check,
32135
+ owner: policy.owner
32136
+ });
32137
+ }
32138
+ const snapshot = snapshotFromRows(schema.flatMap(objectsOf));
32139
+ return {
32140
+ tables: tableNames(snapshot),
32141
+ policies,
32142
+ unreadable,
32143
+ snapshot,
32144
+ indexes: indexesFromRows(objectsOf(indexes))
32145
+ };
32146
+ }
32147
+ function firstCell(row) {
32148
+ const value = row[0];
32149
+ return value === null || value === void 0 ? "" : String(value);
32150
+ }
32151
+ function rowidOf(text3) {
32152
+ if (text3 === "") return null;
32153
+ const value = Number(text3);
32154
+ return Number.isFinite(value) ? value : null;
32155
+ }
32156
+ async function lintDatabase(client, projectId, warn2) {
32157
+ const tables = await client.db.tables(projectId);
32158
+ const answer = await client.db.query(projectId, { sql: DIAGNOSTICS_SQL });
32159
+ const [triggers, objects, quick, broken] = answer.statements;
32160
+ if (triggers === void 0 || objects === void 0 || quick === void 0 || broken === void 0)
32161
+ throw new NaroError(
32162
+ "DATABASE_QUERY_FAILED",
32163
+ `The diagnostics could not be read: the database answered with ${String(answer.statements.length)} result sets instead of 4.`
32164
+ );
32165
+ const virtualTables = [];
32166
+ const shadow = /* @__PURE__ */ new Set();
32167
+ for (const row of objects.rows) {
32168
+ const name = cellText(objects, row, "name");
32169
+ if (cellText(objects, row, "type") === "shadow") shadow.add(name);
32170
+ else virtualTables.push(name);
32171
+ }
32172
+ const foreignKeyCheck = broken.rows.map((row) => ({
32173
+ table: cellText(broken, row, "table"),
32174
+ rowid: rowidOf(cellText(broken, row, "rowid")),
32175
+ parent: cellText(broken, row, "parent")
32176
+ }));
32177
+ const policies = await readPolicies(client, projectId, warn2);
32178
+ const input = {
32179
+ tables: tables.filter((table) => !shadow.has(table.name)),
32180
+ foreignKeyCheck,
32181
+ quickCheck: quick.rows.map(firstCell),
32182
+ triggers: triggers.rows.map((row) => cellText(triggers, row, "name")),
32183
+ virtualTables,
32184
+ ...policies === void 0 ? {} : { policies }
32185
+ };
32186
+ const issues = lintSchema(input);
32187
+ const errors = issues.filter((issue2) => issue2.severity === "error").length;
32188
+ return {
32189
+ issues,
32190
+ errors,
32191
+ warnings: issues.length - errors,
32192
+ tables: countLintedTables(input),
32193
+ truncated: answer.truncated
32194
+ };
32195
+ }
32196
+
32197
+ // ../../packages/sdk/src/migration-repair.ts
32198
+ var MIGRATION_NAME_RE = /^[A-Za-z0-9_.-]{1,200}\.sql$/;
32199
+ var LEDGER_DDL = "CREATE TABLE IF NOT EXISTS d1_migrations (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)";
32200
+ var LEDGER_INSERT = "INSERT INTO d1_migrations (name) SELECT ? WHERE NOT EXISTS (SELECT 1 FROM d1_migrations WHERE name = ?)";
32201
+ var LEDGER_DELETE = "DELETE FROM d1_migrations WHERE name IN (?, ?)";
32202
+ function withoutSuffix(key) {
32203
+ return key.endsWith(".sql") ? key.slice(0, -".sql".length) : key;
32204
+ }
32205
+ var MIGRATION_NAME_MESSAGE = "name must end with .sql and contain only letters, digits, _ . -";
32206
+ function repairIsNoop(ledger, key, status) {
32207
+ const recorded = ledger.some(
32208
+ (row) => row.name === key || row.name === withoutSuffix(key)
32209
+ );
32210
+ return status === "applied" ? recorded : !recorded;
32211
+ }
32212
+ async function repairLedger(client, projectId, key, status, ledgerIsEmpty) {
32213
+ if (status === "applied" && ledgerIsEmpty)
32214
+ await client.db.query(projectId, { sql: LEDGER_DDL });
32215
+ const answer = await client.db.query(projectId, {
32216
+ sql: status === "applied" ? LEDGER_INSERT : LEDGER_DELETE,
32217
+ params: status === "applied" ? [key, key] : [key, withoutSuffix(key)]
32218
+ });
32219
+ return (answer.statements[0]?.meta.changes ?? 0) > 0;
32220
+ }
32221
+
31552
32222
  // ../../packages/sdk/src/migrations.ts
31553
32223
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
31554
32224
  import { resolve as resolve9 } from "path";
@@ -31630,7 +32300,7 @@ function migrationsPendingError(pending) {
31630
32300
  );
31631
32301
  }
31632
32302
  function migrationNameMismatch(sent, recorded) {
31633
- return `The control plane recorded the migration as ${recorded}, but naro sent ${sent} and wrote the file under that name. Until the two match, naro migration list shows them apart and naro db push would apply the file again.`;
32303
+ return `The control plane recorded the migration as ${recorded}, but Naro sent ${sent} and wrote the file under that name. Until the two match, naro migration list shows them apart and naro db push would apply the file again.`;
31634
32304
  }
31635
32305
 
31636
32306
  // ../../packages/sdk/src/reset-plan.ts
@@ -32507,6 +33177,427 @@ async function copyWorkingTree(source, checkout) {
32507
33177
  };
32508
33178
  }
32509
33179
 
33180
+ // src/data-environment.ts
33181
+ var DATA_ENV_MESSAGES = {
33182
+ name: "--env must be production or an environment name.",
33183
+ preview: "--env preview has no data of its own: preview deployments use production's. Drop --env, or name an environment.",
33184
+ projectWide: (command) => `--env is not an option of ${command}: turning the backend on or off applies to every environment.`
33185
+ };
33186
+ function dataEnvironment(flags) {
33187
+ const value = flags.env;
33188
+ if (value === void 0 || value === "production") return null;
33189
+ if (value === "preview")
33190
+ throw new NaroError("VALIDATION_FAILED", DATA_ENV_MESSAGES.preview);
33191
+ if (environmentSlugProblem(value) !== null)
33192
+ throw new NaroError("VALIDATION_FAILED", DATA_ENV_MESSAGES.name);
33193
+ return value;
33194
+ }
33195
+ async function dataClient(context) {
33196
+ return (await context.client()).inEnvironment(dataEnvironment(context.flags));
33197
+ }
33198
+ function databaseName(environment) {
33199
+ return environment === null ? "the project database" : `the ${environment} environment's database`;
33200
+ }
33201
+ function enableBackendCommand(environment) {
33202
+ return environment === null ? "naro backend enable" : `naro environments provision ${environment}`;
33203
+ }
33204
+ function envFlag(environment) {
33205
+ return environment === null ? "" : ` --env ${environment}`;
33206
+ }
33207
+ function forEnvironment(text3, environment) {
33208
+ return environmentGuidance(text3, environment);
33209
+ }
33210
+
33211
+ // src/commands/migration.ts
33212
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
33213
+ import { join as join27, relative as relative7 } from "path";
33214
+
33215
+ // src/prompt.ts
33216
+ import { createInterface as createInterface2 } from "readline";
33217
+ import { Writable } from "stream";
33218
+ function confirmHint(defaultYes) {
33219
+ return defaultYes ? "(Y/n)" : "(y/N)";
33220
+ }
33221
+ function readConfirmAnswer(answer, defaultYes) {
33222
+ const normalized2 = answer.trim().toLowerCase();
33223
+ if (normalized2 === "") return defaultYes;
33224
+ if (normalized2 === "y" || normalized2 === "yes") return true;
33225
+ if (!defaultYes) return false;
33226
+ if (normalized2 === "n" || normalized2 === "no") return false;
33227
+ return null;
33228
+ }
33229
+ function isInteractive() {
33230
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
33231
+ }
33232
+ async function readStdin() {
33233
+ let data = "";
33234
+ for await (const chunk of process.stdin) data += String(chunk);
33235
+ return data.replace(/\r?\n$/, "");
33236
+ }
33237
+ var TtyPrompter = class {
33238
+ async ask(question) {
33239
+ const rl = createInterface2({
33240
+ input: process.stdin,
33241
+ output: process.stdout
33242
+ });
33243
+ try {
33244
+ return await new Promise(
33245
+ (resolve15) => rl.question(question, resolve15)
33246
+ );
33247
+ } finally {
33248
+ rl.close();
33249
+ }
33250
+ }
33251
+ async askHidden(question) {
33252
+ let muted = false;
33253
+ const output = new Writable({
33254
+ write(chunk, _enc, done) {
33255
+ if (!muted) process.stdout.write(chunk);
33256
+ done();
33257
+ }
33258
+ });
33259
+ const rl = createInterface2({
33260
+ input: process.stdin,
33261
+ output,
33262
+ terminal: true
33263
+ });
33264
+ try {
33265
+ const answer = new Promise(
33266
+ (resolve15) => rl.question(question, resolve15)
33267
+ );
33268
+ muted = true;
33269
+ const value = await answer;
33270
+ process.stdout.write("\n");
33271
+ return value;
33272
+ } finally {
33273
+ rl.close();
33274
+ }
33275
+ }
33276
+ async confirm(question, options = {}) {
33277
+ const defaultYes = options.defaultYes ?? false;
33278
+ for (; ; ) {
33279
+ const read = readConfirmAnswer(
33280
+ await this.ask(`${question} ${confirmHint(defaultYes)} `),
33281
+ defaultYes
33282
+ );
33283
+ if (read !== null) return read;
33284
+ }
33285
+ }
33286
+ async select(question, options) {
33287
+ process.stdout.write(`${question}
33288
+ `);
33289
+ options.forEach((option, index) => {
33290
+ process.stdout.write(` ${String(index + 1)}. ${option}
33291
+ `);
33292
+ });
33293
+ for (; ; ) {
33294
+ const answer = Number.parseInt(
33295
+ (await this.ask(`Choose 1-${String(options.length)}: `)).trim(),
33296
+ 10
33297
+ );
33298
+ if (Number.isInteger(answer) && answer >= 1 && answer <= options.length)
33299
+ return answer - 1;
33300
+ }
33301
+ }
33302
+ };
33303
+ var NonInteractivePrompter = class {
33304
+ constructor(assumeYes) {
33305
+ this.assumeYes = assumeYes;
33306
+ }
33307
+ refuse(question) {
33308
+ throw new NaroError(
33309
+ "VALIDATION_FAILED",
33310
+ `Interactive input required (${question.trim()}). Pass the value via flags/stdin or run without --json in a terminal.`
33311
+ );
33312
+ }
33313
+ async ask(question) {
33314
+ this.refuse(question);
33315
+ }
33316
+ async askHidden(question) {
33317
+ this.refuse(question);
33318
+ }
33319
+ async confirm() {
33320
+ return this.assumeYes;
33321
+ }
33322
+ async select(question) {
33323
+ this.refuse(question);
33324
+ }
33325
+ };
33326
+ function createPrompter(options) {
33327
+ if (options.yes || options.json || !isInteractive())
33328
+ return new NonInteractivePrompter(options.yes);
33329
+ return new TtyPrompter();
33330
+ }
33331
+
33332
+ // src/commands/confirm.ts
33333
+ function cannotAsk(context) {
33334
+ return context.flags.json || !isInteractive();
33335
+ }
33336
+ async function confirmOrAbort(context, question) {
33337
+ if (context.flags.yes) return;
33338
+ if (cannotAsk(context)) {
33339
+ throw new NaroError(
33340
+ "CONFIRMATION_REQUIRED",
33341
+ `${question} Re-run with --yes to confirm.`
33342
+ );
33343
+ }
33344
+ const confirmed = await context.prompter.confirm(question);
33345
+ if (!confirmed) throw new NaroError("CANCELED", "Canceled.");
33346
+ }
33347
+
33348
+ // src/commands/migration.ts
33349
+ var processStdin = {
33350
+ stdinIsTty: () => process.stdin.isTTY === true,
33351
+ readStdin
33352
+ };
33353
+ function migrationsDir(context) {
33354
+ return resolveMigrationsDir({
33355
+ baseDir: context.cwd,
33356
+ rootDirectory: context.flags.root,
33357
+ dir: context.flags.dir
33358
+ });
33359
+ }
33360
+ function relativeToCwd(context, path) {
33361
+ const rel = relative7(context.cwd, path);
33362
+ return rel === "" ? "." : rel;
33363
+ }
33364
+ async function appliedMigrations(client, projectId) {
33365
+ try {
33366
+ return await client.db.migrations(projectId);
33367
+ } catch (error51) {
33368
+ if (isNaroError(error51) && error51.code === "DATABASE_NOT_READY") return [];
33369
+ throw error51;
33370
+ }
33371
+ }
33372
+ var UNPRINTABLE = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
33373
+ function printable(text3) {
33374
+ return text3.replace(UNPRINTABLE, "?");
33375
+ }
33376
+ function isPrintable(text3) {
33377
+ return printable(text3) === text3;
33378
+ }
33379
+ var UNSHOWN_WARNING_LINE = "(a line of this warning has characters the terminal cannot show; run with --json to see it exactly)";
33380
+ function warningLines(message) {
33381
+ return message.split("\n").map(
33382
+ (line, index) => index === 0 ? `\u26A0 ${printable(line)}` : ` ${isPrintable(line) ? line : UNSHOWN_WARNING_LINE}`
33383
+ );
33384
+ }
33385
+ function policyProblems(dir, names) {
33386
+ return names.flatMap((name) => {
33387
+ const split2 = splitStatements(readFileSync7(join27(dir, name), "utf8"));
33388
+ if (!split2.ok) return [];
33389
+ const planned = planPolicyMigration(
33390
+ split2.statements.map((statement2) => statement2.text)
33391
+ );
33392
+ return planned.ok ? [] : [{ migration: name, message: planned.message }];
33393
+ });
33394
+ }
33395
+ function warningLine(warning) {
33396
+ if (warning === "out-of-order")
33397
+ return " \u26A0 older than an applied migration \u2014 db push will still apply it";
33398
+ if (warning === "ledger-name-missing-extension")
33399
+ return " \u26A0 recorded without .sql in the ledger";
33400
+ return null;
33401
+ }
33402
+ async function runMigrationNew(context, deps = processStdin) {
33403
+ const [name] = context.positionalOrThrow(
33404
+ 1,
33405
+ "Usage: naro migration new <name>"
33406
+ );
33407
+ const dir = migrationsDir(context);
33408
+ const fileName = nextMigrationFileName(
33409
+ listMigrationFiles(dir),
33410
+ name
33411
+ );
33412
+ mkdirSync3(dir, { recursive: true });
33413
+ const piped = !deps.stdinIsTty() ? await deps.readStdin() : "";
33414
+ const fromStdin = piped.length > 0;
33415
+ const body = fromStdin ? piped.endsWith("\n") ? piped : `${piped}
33416
+ ` : `-- Migration: ${fileName}
33417
+ `;
33418
+ const path = join27(dir, fileName);
33419
+ writeFileSync3(path, body, { flag: "wx" });
33420
+ const relPath = relativeToCwd(context, path);
33421
+ context.output.result({ file: fileName, path: relPath, fromStdin }, [
33422
+ `\u2705 Created ${relPath}`
33423
+ ]);
33424
+ }
33425
+ async function runMigrationList(context) {
33426
+ const client = await dataClient(context);
33427
+ const projectId = await context.resolveProjectId();
33428
+ const dir = migrationsDir(context);
33429
+ const local = listMigrationFiles(dir);
33430
+ const ledger = await appliedMigrations(client, projectId);
33431
+ const migrations = compareMigrations(local, ledger);
33432
+ const relDir = relativeToCwd(context, dir);
33433
+ const lines = migrations.length === 0 ? ["No migrations yet. Create one with `naro migration new <name>`."] : [
33434
+ `Migrations in ${relDir} (${String(migrations.length)}):`,
33435
+ ...migrations.flatMap((row) => {
33436
+ const line = ` ${row.status.padEnd(12)}${printable(row.name)}${row.appliedAt ? ` ${printable(row.appliedAt)}` : ""}`;
33437
+ const warning = warningLine(row.warning);
33438
+ return warning ? [line, warning] : [line];
33439
+ })
33440
+ ];
33441
+ context.output.result({ projectId, dir: relDir, migrations }, lines);
33442
+ }
33443
+ async function applyMigrationFiles(context, client, projectId, dir, names) {
33444
+ const applied = [];
33445
+ const warnings = [];
33446
+ for (const name of names) {
33447
+ const sql = readFileSync7(join27(dir, name), "utf8");
33448
+ let answer;
33449
+ try {
33450
+ answer = await client.db.applyMigration(projectId, { name, sql });
33451
+ } catch (error51) {
33452
+ if (!isNaroError(error51)) throw error51;
33453
+ for (const message of serverWarnings(error51.details))
33454
+ for (const line of warningLines(message)) context.output.warn(line);
33455
+ const shown = context.output.json ? name : printable(name);
33456
+ throw new NaroError(error51.code, `${shown}: ${error51.message}`, {
33457
+ ...error51.status === void 0 ? {} : { status: error51.status },
33458
+ details: {
33459
+ ...error51.details,
33460
+ migration: name,
33461
+ applied,
33462
+ appliedWarnings: warnings
33463
+ },
33464
+ cause: error51
33465
+ });
33466
+ }
33467
+ context.output.log(`\u2713 ${printable(name)}`);
33468
+ applied.push(name);
33469
+ for (const message of answer.warnings) {
33470
+ warnings.push({ migration: name, message });
33471
+ for (const line of warningLines(message)) context.output.warn(line);
33472
+ }
33473
+ }
33474
+ return { applied, warnings };
33475
+ }
33476
+ function serverWarnings(details) {
33477
+ const warnings = details?.warnings;
33478
+ return Array.isArray(warnings) ? warnings.filter(
33479
+ (warning) => typeof warning === "string"
33480
+ ) : [];
33481
+ }
33482
+ async function runMigrationRepair(context) {
33483
+ const [name] = context.positionalOrThrow(
33484
+ 1,
33485
+ "Usage: naro migration repair <name> --status applied|reverted"
33486
+ );
33487
+ const key = name;
33488
+ if (!MIGRATION_NAME_RE.test(key))
33489
+ throw new NaroError("VALIDATION_FAILED", MIGRATION_NAME_MESSAGE);
33490
+ const status = context.flags.status;
33491
+ if (status !== "applied" && status !== "reverted")
33492
+ throw new NaroError(
33493
+ "VALIDATION_FAILED",
33494
+ "Pass --status applied or --status reverted."
33495
+ );
33496
+ const client = await dataClient(context);
33497
+ const projectId = await context.resolveProjectId();
33498
+ const ledger = await appliedMigrations(client, projectId);
33499
+ const nothingToDo = status === "applied" ? `${key} was already applied; nothing to do.` : `${key} was not in the ledger; nothing to do.`;
33500
+ if (repairIsNoop(ledger, key, status)) {
33501
+ context.output.warn(nothingToDo);
33502
+ context.output.result({ name: key, status, changed: false });
33503
+ return;
33504
+ }
33505
+ await confirmOrAbort(
33506
+ context,
33507
+ status === "applied" ? `Mark ${key} as applied in ${databaseName(dataEnvironment(context.flags))}'s ledger? Its SQL is not run.` : `Remove ${key} from ${databaseName(dataEnvironment(context.flags))}'s ledger? Its SQL is not rolled back.`
33508
+ );
33509
+ const changed = await repairLedger(
33510
+ client,
33511
+ projectId,
33512
+ key,
33513
+ status,
33514
+ ledger.length === 0
33515
+ );
33516
+ if (!changed) {
33517
+ context.output.warn(nothingToDo);
33518
+ context.output.result({ name: key, status, changed: false });
33519
+ return;
33520
+ }
33521
+ context.output.result({ name: key, status, changed: true }, [
33522
+ status === "applied" ? `\u2713 Marked ${key} as applied.` : `\u2713 Removed ${key} from the ledger.`
33523
+ ]);
33524
+ }
33525
+ async function runMigrationUp(context) {
33526
+ const client = await dataClient(context);
33527
+ const projectId = await context.resolveProjectId();
33528
+ const dir = migrationsDir(context);
33529
+ const local = listMigrationFiles(dir);
33530
+ const ledger = await appliedMigrations(client, projectId);
33531
+ const pending = compareMigrations(local, ledger).filter((row) => row.status === "pending").map((row) => row.name);
33532
+ if (pending.length === 0) {
33533
+ context.output.result({ projectId, applied: [], pending: [] }, [
33534
+ "No pending migrations."
33535
+ ]);
33536
+ return;
33537
+ }
33538
+ if (context.flags.dryRun) {
33539
+ const problems2 = policyProblems(dir, pending);
33540
+ context.output.result({ projectId, dryRun: true, pending, problems: problems2 }, [
33541
+ `Pending migrations (${String(pending.length)}):`,
33542
+ ...pending.map((name) => ` ${printable(name)}`),
33543
+ ...problems2.map(
33544
+ (problem) => `${printable(problem.migration)}: ${printable(problem.message)}`
33545
+ )
33546
+ ]);
33547
+ if (problems2.length > 0) process.exitCode = 1;
33548
+ return;
33549
+ }
33550
+ context.output.log(`Pending migrations (${String(pending.length)}):`);
33551
+ for (const name of pending) context.output.log(` ${printable(name)}`);
33552
+ await confirmOrAbort(
33553
+ context,
33554
+ `Apply ${String(pending.length)} migration(s) to ${databaseName(dataEnvironment(context.flags))}? Each file is applied all-or-nothing; a failure stops the run.`
33555
+ );
33556
+ const { applied, warnings } = await applyMigrationFiles(
33557
+ context,
33558
+ client,
33559
+ projectId,
33560
+ dir,
33561
+ pending
33562
+ );
33563
+ context.output.result({ projectId, applied, pending: [], warnings }, [
33564
+ `\u2705 Applied ${String(applied.length)} migration(s).`
33565
+ ]);
33566
+ }
33567
+
33568
+ // src/commands/db-lint.ts
33569
+ var TRUNCATED_NOTICE = "\u26A0 The diagnostics were cut at 10,000 rows \u2014 there may be more broken rows than these.";
33570
+ function issueLines(issues, tables) {
33571
+ const errors = issues.filter((issue2) => issue2.severity === "error").length;
33572
+ if (issues.length === 0)
33573
+ return [`\u2713 No issues found in ${String(tables)} table(s).`];
33574
+ return [
33575
+ ...issues.map(
33576
+ (issue2) => printable(
33577
+ `${issue2.severity.padEnd(5)} ${issue2.rule} ${issue2.object}: ${issue2.message}`
33578
+ )
33579
+ ),
33580
+ `${String(errors)} error(s), ${String(issues.length - errors)} warning(s) in ${String(tables)} table(s).`
33581
+ ];
33582
+ }
33583
+ async function runDbLint(context) {
33584
+ const client = await dataClient(context);
33585
+ const projectId = await context.resolveProjectId();
33586
+ const linted = await lintDatabase(
33587
+ client,
33588
+ projectId,
33589
+ (message) => context.output.warn(message)
33590
+ );
33591
+ const { issues, errors, warnings, tables: counted } = linted;
33592
+ if (linted.truncated) context.output.warn(TRUNCATED_NOTICE);
33593
+ context.output.result(
33594
+ { issues, errors, warnings, tables: counted },
33595
+ issueLines(issues, counted)
33596
+ );
33597
+ if (errors > 0 || context.flags.strict === true && warnings > 0)
33598
+ process.exitCode = 1;
33599
+ }
33600
+
32510
33601
  // src/functions/deploy.ts
32511
33602
  import { relative as relative11, resolve as resolve13, sep as sep10 } from "path";
32512
33603
 
@@ -32564,8 +33655,8 @@ var BUNDLE_MESSAGES = {
32564
33655
  };
32565
33656
 
32566
33657
  // ../../packages/functions-bundler/src/discover.ts
32567
- import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
32568
- import { join as join27, relative as relative7, resolve as resolve11, sep as sep6 } from "path";
33658
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync2 } from "fs";
33659
+ import { join as join28, relative as relative8, resolve as resolve11, sep as sep6 } from "path";
32569
33660
 
32570
33661
  // ../../packages/functions-bundler/src/limits.ts
32571
33662
  var FUNCTION_BUNDLE_MAX_BYTES = 67108864;
@@ -32589,8 +33680,8 @@ function isEntrypoint(value) {
32589
33680
  function functionsFolder(options) {
32590
33681
  const root = resolve11(options.cwd, options.root ?? ".");
32591
33682
  const explicit = options.dir !== void 0;
32592
- const dir = explicit ? resolve11(options.cwd, options.dir) : join27(root, "naro", "functions");
32593
- const label2 = relative7(options.cwd, dir).split(sep6).join("/") || ".";
33683
+ const dir = explicit ? resolve11(options.cwd, options.dir) : join28(root, "naro", "functions");
33684
+ const label2 = relative8(options.cwd, dir).split(sep6).join("/") || ".";
32594
33685
  return { dir, label: label2, root, explicit };
32595
33686
  }
32596
33687
  function listFunctionNames(folder) {
@@ -32598,7 +33689,7 @@ function listFunctionNames(folder) {
32598
33689
  try {
32599
33690
  entries = readdirSync3(folder.dir, { withFileTypes: true });
32600
33691
  } catch {
32601
- if (!folder.explicit && existsSync7(join27(folder.root, "supabase", "functions")))
33692
+ if (!folder.explicit && existsSync7(join28(folder.root, "supabase", "functions")))
32602
33693
  throw new FunctionBundleError(
32603
33694
  "VALIDATION_FAILED",
32604
33695
  BUNDLE_MESSAGES.supabaseFound
@@ -32619,11 +33710,11 @@ function listFunctionNames(folder) {
32619
33710
  return names;
32620
33711
  }
32621
33712
  function readFunctionJson(dir, name) {
32622
- const file2 = join27(dir, "function.json");
33713
+ const file2 = join28(dir, "function.json");
32623
33714
  if (!existsSync7(file2)) return { verifyJwt: void 0, entrypoint: void 0 };
32624
33715
  let parsed;
32625
33716
  try {
32626
- parsed = JSON.parse(readFileSync7(file2, "utf8"));
33717
+ parsed = JSON.parse(readFileSync8(file2, "utf8"));
32627
33718
  } catch {
32628
33719
  throw new FunctionBundleError(
32629
33720
  "VALIDATION_FAILED",
@@ -32663,7 +33754,7 @@ function readFunctionSource(folder, name) {
32663
33754
  "VALIDATION_FAILED",
32664
33755
  BUNDLE_MESSAGES.badName(name)
32665
33756
  );
32666
- const dir = join27(folder.dir, name);
33757
+ const dir = join28(folder.dir, name);
32667
33758
  if (!statSync2(dir, { throwIfNoEntry: false })?.isDirectory())
32668
33759
  throw new FunctionBundleError(
32669
33760
  "VALIDATION_FAILED",
@@ -32672,14 +33763,14 @@ function readFunctionSource(folder, name) {
32672
33763
  const config2 = readFunctionJson(dir, name);
32673
33764
  let entrypoint;
32674
33765
  if (config2.entrypoint !== void 0) {
32675
- if (!existsSync7(join27(dir, ...config2.entrypoint.split("/"))))
33766
+ if (!existsSync7(join28(dir, ...config2.entrypoint.split("/"))))
32676
33767
  throw new FunctionBundleError(
32677
33768
  "FUNCTION_BUNDLE_FAILED",
32678
33769
  BUNDLE_MESSAGES.entryMissing(name, config2.entrypoint)
32679
33770
  );
32680
33771
  entrypoint = config2.entrypoint;
32681
33772
  } else {
32682
- const found = ENTRY_FILES.find((file2) => existsSync7(join27(dir, file2)));
33773
+ const found = ENTRY_FILES.find((file2) => existsSync7(join28(dir, file2)));
32683
33774
  if (found === void 0)
32684
33775
  throw new FunctionBundleError(
32685
33776
  "FUNCTION_BUNDLE_FAILED",
@@ -32690,16 +33781,16 @@ function readFunctionSource(folder, name) {
32690
33781
  return {
32691
33782
  name,
32692
33783
  dir,
32693
- entry: join27(dir, ...entrypoint.split("/")),
33784
+ entry: join28(dir, ...entrypoint.split("/")),
32694
33785
  entrypoint,
32695
33786
  verifyJwt: config2.verifyJwt
32696
33787
  };
32697
33788
  }
32698
33789
  function configTomlWarning(folder) {
32699
- if (!folder.dir.endsWith(join27("supabase", "functions"))) return null;
32700
- const file2 = join27(folder.dir, "..", "config.toml");
33790
+ if (!folder.dir.endsWith(join28("supabase", "functions"))) return null;
33791
+ const file2 = join28(folder.dir, "..", "config.toml");
32701
33792
  if (!existsSync7(file2)) return null;
32702
- return /^\s*verify_jwt\s*=/m.test(readFileSync7(file2, "utf8")) ? BUNDLE_MESSAGES.supabaseVerifyJwt(folder.label) : null;
33793
+ return /^\s*verify_jwt\s*=/m.test(readFileSync8(file2, "utf8")) ? BUNDLE_MESSAGES.supabaseVerifyJwt(folder.label) : null;
32703
33794
  }
32704
33795
 
32705
33796
  // ../../packages/functions-bundler/src/jsonc.ts
@@ -32902,22 +33993,22 @@ ${injects.map(
32902
33993
  }
32903
33994
 
32904
33995
  // ../../packages/functions-bundler/src/deno-imports.ts
32905
- import { existsSync as existsSync9, readFileSync as readFileSync9, realpathSync as realpathSync2 } from "fs";
32906
- import { dirname as dirname9, isAbsolute as isAbsolute5, join as join29, relative as relative8, sep as sep8 } from "path";
33996
+ import { existsSync as existsSync9, readFileSync as readFileSync10, realpathSync as realpathSync2 } from "fs";
33997
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join30, relative as relative9, sep as sep8 } from "path";
32907
33998
 
32908
33999
  // ../../packages/functions-bundler/src/import-map.ts
32909
- import { existsSync as existsSync8, readFileSync as readFileSync8, realpathSync } from "fs";
32910
- import { basename as basename4, dirname as dirname8, join as join28, resolve as resolve12, sep as sep7 } from "path";
34000
+ import { existsSync as existsSync8, readFileSync as readFileSync9, realpathSync } from "fs";
34001
+ import { basename as basename4, dirname as dirname8, join as join29, resolve as resolve12, sep as sep7 } from "path";
32911
34002
  import { fileURLToPath as fileURLToPath3 } from "url";
32912
34003
 
32913
34004
  // ../../packages/functions-bundler/src/printable.ts
32914
- var UNPRINTABLE = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
32915
- function printable(text3) {
32916
- return text3.replace(UNPRINTABLE, "?");
34005
+ var UNPRINTABLE2 = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
34006
+ function printable2(text3) {
34007
+ return text3.replace(UNPRINTABLE2, "?");
32917
34008
  }
32918
34009
  function causeLine(error51) {
32919
34010
  const text3 = error51 instanceof Error ? error51.message : String(error51);
32920
- return printable(
34011
+ return printable2(
32921
34012
  text3.split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "unknown error"
32922
34013
  );
32923
34014
  }
@@ -32941,7 +34032,7 @@ function expandBareKeys(imports) {
32941
34032
  function load(file2, label2) {
32942
34033
  let parsed;
32943
34034
  try {
32944
- parsed = parseJsonc2(readFileSync8(file2, "utf8"));
34035
+ parsed = parseJsonc2(readFileSync9(file2, "utf8"));
32945
34036
  } catch {
32946
34037
  throw new FunctionBundleError(
32947
34038
  "VALIDATION_FAILED",
@@ -32961,7 +34052,7 @@ function load(file2, label2) {
32961
34052
  if (key.endsWith("/") && !value.endsWith("/"))
32962
34053
  throw new FunctionBundleError(
32963
34054
  "VALIDATION_FAILED",
32964
- printable(BUNDLE_MESSAGES.importMapPrefix(label2, key, value))
34055
+ printable2(BUNDLE_MESSAGES.importMapPrefix(label2, key, value))
32965
34056
  );
32966
34057
  return {
32967
34058
  imports: DENO_CONFIGS.has(basename4(file2)) ? expandBareKeys(written) : written,
@@ -32979,7 +34070,7 @@ function readImportMapFile(file2, label2) {
32979
34070
  }
32980
34071
  function readImportMap(functionDir, fallback, labelOf) {
32981
34072
  for (const name of FILES) {
32982
- const file2 = join28(functionDir, name);
34073
+ const file2 = join29(functionDir, name);
32983
34074
  if (!existsSync8(file2)) continue;
32984
34075
  const map2 = load(file2, labelOf(file2));
32985
34076
  if (map2 !== null) return map2;
@@ -33104,7 +34195,7 @@ function parseNpm(text3) {
33104
34195
  }
33105
34196
  var afterScheme = (specifier) => specifier.slice(4).replace(/^\//, "");
33106
34197
  var isPackageName = (pkg) => pkg.startsWith("@") ? /^@[^/]+\/[^/]+$/.test(pkg) : /^[^/]+$/.test(pkg);
33107
- var refuse2 = (text3) => ({ errors: [{ text: printable(text3) }] });
34198
+ var refuse2 = (text3) => ({ errors: [{ text: printable2(text3) }] });
33108
34199
  var installOf = (pkg, range) => range ? `${pkg}@${range}` : pkg;
33109
34200
  function realPath2(path) {
33110
34201
  try {
@@ -33113,16 +34204,16 @@ function realPath2(path) {
33113
34204
  return path;
33114
34205
  }
33115
34206
  }
33116
- var fromRoot = (projectRoot, path) => relative8(projectRoot, path).split(sep8).join("/");
34207
+ var fromRoot = (projectRoot, path) => relative9(projectRoot, path).split(sep8).join("/");
33117
34208
  function isInside(path, projectRoot) {
33118
- const rel = relative8(projectRoot, path);
34209
+ const rel = relative9(projectRoot, path);
33119
34210
  return rel === "" || rel !== ".." && !rel.startsWith(`..${sep8}`) && !isAbsolute5(rel);
33120
34211
  }
33121
34212
  function packageDir(from, projectRoot) {
33122
34213
  let current = from;
33123
34214
  while (isInside(current, projectRoot)) {
33124
34215
  const inNodeModules = fromRoot(projectRoot, current).split("/").includes("node_modules");
33125
- if (!inNodeModules && existsSync9(join29(current, "package.json")))
34216
+ if (!inNodeModules && existsSync9(join30(current, "package.json")))
33126
34217
  return fromRoot(projectRoot, current) || ".";
33127
34218
  const up = dirname9(current);
33128
34219
  if (up === current) break;
@@ -33134,7 +34225,7 @@ function isInstalled(from, pkg) {
33134
34225
  if (pkg === "") return false;
33135
34226
  let current = from;
33136
34227
  for (; ; ) {
33137
- if (existsSync9(join29(current, "node_modules", pkg))) return true;
34228
+ if (existsSync9(join30(current, "node_modules", pkg))) return true;
33138
34229
  const up = dirname9(current);
33139
34230
  if (up === current) return false;
33140
34231
  current = up;
@@ -33144,10 +34235,10 @@ var NODE_MODULES = /[\\/]node_modules[\\/]/;
33144
34235
  function installedVersion(resolved, pkg) {
33145
34236
  let current = dirname9(resolved);
33146
34237
  for (; ; ) {
33147
- const file2 = join29(current, "package.json");
34238
+ const file2 = join30(current, "package.json");
33148
34239
  if (existsSync9(file2)) {
33149
34240
  try {
33150
- const manifest2 = JSON.parse(readFileSync9(file2, "utf8"));
34241
+ const manifest2 = JSON.parse(readFileSync10(file2, "utf8"));
33151
34242
  if (manifest2.name === pkg && typeof manifest2.version === "string")
33152
34243
  return manifest2.version;
33153
34244
  } catch {
@@ -33160,7 +34251,7 @@ function installedVersion(resolved, pkg) {
33160
34251
  }
33161
34252
  var byCodePoint = (a, b) => a < b ? -1 : a > b ? 1 : 0;
33162
34253
  function missingPackagesMessage(unsorted) {
33163
- return printable(installSentence(unsorted));
34254
+ return printable2(installSentence(unsorted));
33164
34255
  }
33165
34256
  function installSentence(unsorted) {
33166
34257
  const missing = [...unsorted].sort(
@@ -33241,7 +34332,7 @@ function denoImportsPlugin(options) {
33241
34332
  if (spec.range) {
33242
34333
  const version3 = installedVersion(result.path, pkgName);
33243
34334
  if (version3 !== null && inRange(version3, spec.range) === false) {
33244
- const warning = printable(
34335
+ const warning = printable2(
33245
34336
  BUNDLE_MESSAGES.outOfRange(
33246
34337
  pkgName,
33247
34338
  version3,
@@ -33328,7 +34419,7 @@ function denoImportsPlugin(options) {
33328
34419
 
33329
34420
  // ../../packages/functions-bundler/src/bundle.ts
33330
34421
  import { createHash as createHash4 } from "crypto";
33331
- import { join as join30, relative as relative9, sep as sep9 } from "path";
34422
+ import { join as join31, relative as relative10, sep as sep9 } from "path";
33332
34423
  import { gzipSync } from "zlib";
33333
34424
 
33334
34425
  // ../../packages/functions-runtime/dist/index.js
@@ -33349,7 +34440,7 @@ function runtimeCompressedBytes() {
33349
34440
  return runtimeGzip;
33350
34441
  }
33351
34442
  function relativeLabel(root, file2) {
33352
- return relative9(root, file2).split(sep9).join("/");
34443
+ return relative10(root, file2).split(sep9).join("/");
33353
34444
  }
33354
34445
  function location(message) {
33355
34446
  return message.location ? `${message.location.file}:${String(message.location.line)}` : null;
@@ -33404,7 +34495,7 @@ async function bundleFunction(source, folder, options = {}) {
33404
34495
  result = await build({
33405
34496
  absWorkingDir: folder.root,
33406
34497
  entryPoints: [source.entry],
33407
- outfile: join30(source.dir, NARO_USER_MODULE),
34498
+ outfile: join31(source.dir, NARO_USER_MODULE),
33408
34499
  bundle: true,
33409
34500
  write: false,
33410
34501
  format: "esm",
@@ -33496,7 +34587,7 @@ function bootErrorLine(logs, error51) {
33496
34587
  const logged = logs.find((line2) => line2.includes("Uncaught")) ?? logs.find((line2) => line2.includes("Error"));
33497
34588
  const raw = logged ?? (error51 instanceof Error ? error51.message : String(error51));
33498
34589
  const line = raw.split("\n").map((part) => part.trim()).find((part) => part.length > 0) ?? "unknown error";
33499
- return printable(line.replace(WORKERD_SERVICE_HEAD, ""));
34590
+ return printable2(line.replace(WORKERD_SERVICE_HEAD, ""));
33500
34591
  }
33501
34592
  async function bootCheck(name, code, options = {}) {
33502
34593
  const timeoutMs = options.timeoutMs ?? BOOT_TIMEOUT_MS;
@@ -33577,407 +34668,6 @@ async function bootCheck(name, code, options = {}) {
33577
34668
  }
33578
34669
  }
33579
34670
 
33580
- // src/commands/migration.ts
33581
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "fs";
33582
- import { join as join31, relative as relative10 } from "path";
33583
-
33584
- // src/data-environment.ts
33585
- var DATA_ENV_MESSAGES = {
33586
- name: "--env must be production or an environment name.",
33587
- preview: "--env preview has no data of its own: preview deployments use production's. Drop --env, or name an environment.",
33588
- projectWide: (command) => `--env is not an option of ${command}: turning the backend on or off applies to every environment.`
33589
- };
33590
- function dataEnvironment(flags) {
33591
- const value = flags.env;
33592
- if (value === void 0 || value === "production") return null;
33593
- if (value === "preview")
33594
- throw new NaroError("VALIDATION_FAILED", DATA_ENV_MESSAGES.preview);
33595
- if (environmentSlugProblem(value) !== null)
33596
- throw new NaroError("VALIDATION_FAILED", DATA_ENV_MESSAGES.name);
33597
- return value;
33598
- }
33599
- async function dataClient(context) {
33600
- return (await context.client()).inEnvironment(dataEnvironment(context.flags));
33601
- }
33602
- function databaseName(environment) {
33603
- return environment === null ? "the project database" : `the ${environment} environment's database`;
33604
- }
33605
- function enableBackendCommand(environment) {
33606
- return environment === null ? "naro backend enable" : `naro environments provision ${environment}`;
33607
- }
33608
- function envFlag(environment) {
33609
- return environment === null ? "" : ` --env ${environment}`;
33610
- }
33611
- function forEnvironment(text3, environment) {
33612
- return environmentGuidance(text3, environment);
33613
- }
33614
-
33615
- // src/prompt.ts
33616
- import { createInterface as createInterface2 } from "readline";
33617
- import { Writable } from "stream";
33618
- function confirmHint(defaultYes) {
33619
- return defaultYes ? "(Y/n)" : "(y/N)";
33620
- }
33621
- function readConfirmAnswer(answer, defaultYes) {
33622
- const normalized2 = answer.trim().toLowerCase();
33623
- if (normalized2 === "") return defaultYes;
33624
- if (normalized2 === "y" || normalized2 === "yes") return true;
33625
- if (!defaultYes) return false;
33626
- if (normalized2 === "n" || normalized2 === "no") return false;
33627
- return null;
33628
- }
33629
- function isInteractive() {
33630
- return process.stdin.isTTY === true && process.stdout.isTTY === true;
33631
- }
33632
- async function readStdin() {
33633
- let data = "";
33634
- for await (const chunk of process.stdin) data += String(chunk);
33635
- return data.replace(/\r?\n$/, "");
33636
- }
33637
- var TtyPrompter = class {
33638
- async ask(question) {
33639
- const rl = createInterface2({
33640
- input: process.stdin,
33641
- output: process.stdout
33642
- });
33643
- try {
33644
- return await new Promise(
33645
- (resolve15) => rl.question(question, resolve15)
33646
- );
33647
- } finally {
33648
- rl.close();
33649
- }
33650
- }
33651
- async askHidden(question) {
33652
- let muted = false;
33653
- const output = new Writable({
33654
- write(chunk, _enc, done) {
33655
- if (!muted) process.stdout.write(chunk);
33656
- done();
33657
- }
33658
- });
33659
- const rl = createInterface2({
33660
- input: process.stdin,
33661
- output,
33662
- terminal: true
33663
- });
33664
- try {
33665
- const answer = new Promise(
33666
- (resolve15) => rl.question(question, resolve15)
33667
- );
33668
- muted = true;
33669
- const value = await answer;
33670
- process.stdout.write("\n");
33671
- return value;
33672
- } finally {
33673
- rl.close();
33674
- }
33675
- }
33676
- async confirm(question, options = {}) {
33677
- const defaultYes = options.defaultYes ?? false;
33678
- for (; ; ) {
33679
- const read = readConfirmAnswer(
33680
- await this.ask(`${question} ${confirmHint(defaultYes)} `),
33681
- defaultYes
33682
- );
33683
- if (read !== null) return read;
33684
- }
33685
- }
33686
- async select(question, options) {
33687
- process.stdout.write(`${question}
33688
- `);
33689
- options.forEach((option, index) => {
33690
- process.stdout.write(` ${String(index + 1)}. ${option}
33691
- `);
33692
- });
33693
- for (; ; ) {
33694
- const answer = Number.parseInt(
33695
- (await this.ask(`Choose 1-${String(options.length)}: `)).trim(),
33696
- 10
33697
- );
33698
- if (Number.isInteger(answer) && answer >= 1 && answer <= options.length)
33699
- return answer - 1;
33700
- }
33701
- }
33702
- };
33703
- var NonInteractivePrompter = class {
33704
- constructor(assumeYes) {
33705
- this.assumeYes = assumeYes;
33706
- }
33707
- refuse(question) {
33708
- throw new NaroError(
33709
- "VALIDATION_FAILED",
33710
- `Interactive input required (${question.trim()}). Pass the value via flags/stdin or run without --json in a terminal.`
33711
- );
33712
- }
33713
- async ask(question) {
33714
- this.refuse(question);
33715
- }
33716
- async askHidden(question) {
33717
- this.refuse(question);
33718
- }
33719
- async confirm() {
33720
- return this.assumeYes;
33721
- }
33722
- async select(question) {
33723
- this.refuse(question);
33724
- }
33725
- };
33726
- function createPrompter(options) {
33727
- if (options.yes || options.json || !isInteractive())
33728
- return new NonInteractivePrompter(options.yes);
33729
- return new TtyPrompter();
33730
- }
33731
-
33732
- // src/commands/confirm.ts
33733
- function cannotAsk(context) {
33734
- return context.flags.json || !isInteractive();
33735
- }
33736
- async function confirmOrAbort(context, question) {
33737
- if (context.flags.yes) return;
33738
- if (cannotAsk(context)) {
33739
- throw new NaroError(
33740
- "CONFIRMATION_REQUIRED",
33741
- `${question} Re-run with --yes to confirm.`
33742
- );
33743
- }
33744
- const confirmed = await context.prompter.confirm(question);
33745
- if (!confirmed) throw new NaroError("CANCELED", "Canceled.");
33746
- }
33747
-
33748
- // src/commands/migration.ts
33749
- var processStdin = {
33750
- stdinIsTty: () => process.stdin.isTTY === true,
33751
- readStdin
33752
- };
33753
- function migrationsDir(context) {
33754
- return resolveMigrationsDir({
33755
- baseDir: context.cwd,
33756
- rootDirectory: context.flags.root,
33757
- dir: context.flags.dir
33758
- });
33759
- }
33760
- function relativeToCwd(context, path) {
33761
- const rel = relative10(context.cwd, path);
33762
- return rel === "" ? "." : rel;
33763
- }
33764
- async function appliedMigrations(client, projectId) {
33765
- try {
33766
- return await client.db.migrations(projectId);
33767
- } catch (error51) {
33768
- if (isNaroError(error51) && error51.code === "DATABASE_NOT_READY") return [];
33769
- throw error51;
33770
- }
33771
- }
33772
- var UNPRINTABLE2 = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
33773
- function printable2(text3) {
33774
- return text3.replace(UNPRINTABLE2, "?");
33775
- }
33776
- function isPrintable(text3) {
33777
- return printable2(text3) === text3;
33778
- }
33779
- var UNSHOWN_WARNING_LINE = "(a line of this warning has characters the terminal cannot show; run with --json to see it exactly)";
33780
- function warningLines(message) {
33781
- return message.split("\n").map(
33782
- (line, index) => index === 0 ? `\u26A0 ${printable2(line)}` : ` ${isPrintable(line) ? line : UNSHOWN_WARNING_LINE}`
33783
- );
33784
- }
33785
- function policyProblems(dir, names) {
33786
- return names.flatMap((name) => {
33787
- const split2 = splitStatements(readFileSync10(join31(dir, name), "utf8"));
33788
- if (!split2.ok) return [];
33789
- const planned = planPolicyMigration(
33790
- split2.statements.map((statement2) => statement2.text)
33791
- );
33792
- return planned.ok ? [] : [{ migration: name, message: planned.message }];
33793
- });
33794
- }
33795
- function warningLine(warning) {
33796
- if (warning === "out-of-order")
33797
- return " \u26A0 older than an applied migration \u2014 db push will still apply it";
33798
- if (warning === "ledger-name-missing-extension")
33799
- return " \u26A0 recorded without .sql in the ledger";
33800
- return null;
33801
- }
33802
- async function runMigrationNew(context, deps = processStdin) {
33803
- const [name] = context.positionalOrThrow(
33804
- 1,
33805
- "Usage: naro migration new <name>"
33806
- );
33807
- const dir = migrationsDir(context);
33808
- const fileName = nextMigrationFileName(
33809
- listMigrationFiles(dir),
33810
- name
33811
- );
33812
- mkdirSync3(dir, { recursive: true });
33813
- const piped = !deps.stdinIsTty() ? await deps.readStdin() : "";
33814
- const fromStdin = piped.length > 0;
33815
- const body = fromStdin ? piped.endsWith("\n") ? piped : `${piped}
33816
- ` : `-- Migration: ${fileName}
33817
- `;
33818
- const path = join31(dir, fileName);
33819
- writeFileSync3(path, body, { flag: "wx" });
33820
- const relPath = relativeToCwd(context, path);
33821
- context.output.result({ file: fileName, path: relPath, fromStdin }, [
33822
- `\u2705 Created ${relPath}`
33823
- ]);
33824
- }
33825
- async function runMigrationList(context) {
33826
- const client = await dataClient(context);
33827
- const projectId = await context.resolveProjectId();
33828
- const dir = migrationsDir(context);
33829
- const local = listMigrationFiles(dir);
33830
- const ledger = await appliedMigrations(client, projectId);
33831
- const migrations = compareMigrations(local, ledger);
33832
- const relDir = relativeToCwd(context, dir);
33833
- const lines = migrations.length === 0 ? ["No migrations yet. Create one with `naro migration new <name>`."] : [
33834
- `Migrations in ${relDir} (${String(migrations.length)}):`,
33835
- ...migrations.flatMap((row) => {
33836
- const line = ` ${row.status.padEnd(12)}${printable2(row.name)}${row.appliedAt ? ` ${printable2(row.appliedAt)}` : ""}`;
33837
- const warning = warningLine(row.warning);
33838
- return warning ? [line, warning] : [line];
33839
- })
33840
- ];
33841
- context.output.result({ projectId, dir: relDir, migrations }, lines);
33842
- }
33843
- async function applyMigrationFiles(context, client, projectId, dir, names) {
33844
- const applied = [];
33845
- const warnings = [];
33846
- for (const name of names) {
33847
- const sql = readFileSync10(join31(dir, name), "utf8");
33848
- let answer;
33849
- try {
33850
- answer = await client.db.applyMigration(projectId, { name, sql });
33851
- } catch (error51) {
33852
- if (!isNaroError(error51)) throw error51;
33853
- for (const message of serverWarnings(error51.details))
33854
- for (const line of warningLines(message)) context.output.warn(line);
33855
- const shown = context.output.json ? name : printable2(name);
33856
- throw new NaroError(error51.code, `${shown}: ${error51.message}`, {
33857
- ...error51.status === void 0 ? {} : { status: error51.status },
33858
- details: {
33859
- ...error51.details,
33860
- migration: name,
33861
- applied,
33862
- appliedWarnings: warnings
33863
- },
33864
- cause: error51
33865
- });
33866
- }
33867
- context.output.log(`\u2713 ${printable2(name)}`);
33868
- applied.push(name);
33869
- for (const message of answer.warnings) {
33870
- warnings.push({ migration: name, message });
33871
- for (const line of warningLines(message)) context.output.warn(line);
33872
- }
33873
- }
33874
- return { applied, warnings };
33875
- }
33876
- function serverWarnings(details) {
33877
- const warnings = details?.warnings;
33878
- return Array.isArray(warnings) ? warnings.filter(
33879
- (warning) => typeof warning === "string"
33880
- ) : [];
33881
- }
33882
- var MIGRATION_NAME_RE = /^[A-Za-z0-9_.-]{1,200}\.sql$/;
33883
- var LEDGER_DDL = "CREATE TABLE IF NOT EXISTS d1_migrations (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)";
33884
- var LEDGER_INSERT = "INSERT INTO d1_migrations (name) SELECT ? WHERE NOT EXISTS (SELECT 1 FROM d1_migrations WHERE name = ?)";
33885
- var LEDGER_DELETE = "DELETE FROM d1_migrations WHERE name IN (?, ?)";
33886
- function withoutSuffix(key) {
33887
- return key.slice(0, -".sql".length);
33888
- }
33889
- async function runMigrationRepair(context) {
33890
- const [name] = context.positionalOrThrow(
33891
- 1,
33892
- "Usage: naro migration repair <name> --status applied|reverted"
33893
- );
33894
- const key = name;
33895
- if (!MIGRATION_NAME_RE.test(key))
33896
- throw new NaroError(
33897
- "VALIDATION_FAILED",
33898
- "name must end with .sql and contain only letters, digits, _ . -"
33899
- );
33900
- const status = context.flags.status;
33901
- if (status !== "applied" && status !== "reverted")
33902
- throw new NaroError(
33903
- "VALIDATION_FAILED",
33904
- "Pass --status applied or --status reverted."
33905
- );
33906
- const client = await dataClient(context);
33907
- const projectId = await context.resolveProjectId();
33908
- const ledger = await appliedMigrations(client, projectId);
33909
- const recorded = ledger.some(
33910
- (row) => row.name === key || row.name === withoutSuffix(key)
33911
- );
33912
- const nothingToDo = status === "applied" ? `${key} was already applied; nothing to do.` : `${key} was not in the ledger; nothing to do.`;
33913
- if (status === "applied" ? recorded : !recorded) {
33914
- context.output.warn(nothingToDo);
33915
- context.output.result({ name: key, status, changed: false });
33916
- return;
33917
- }
33918
- await confirmOrAbort(
33919
- context,
33920
- status === "applied" ? `Mark ${key} as applied in ${databaseName(dataEnvironment(context.flags))}'s ledger? Its SQL is not run.` : `Remove ${key} from ${databaseName(dataEnvironment(context.flags))}'s ledger? Its SQL is not rolled back.`
33921
- );
33922
- if (status === "applied" && ledger.length === 0)
33923
- await client.db.query(projectId, { sql: LEDGER_DDL });
33924
- const answer = await client.db.query(projectId, {
33925
- sql: status === "applied" ? LEDGER_INSERT : LEDGER_DELETE,
33926
- params: status === "applied" ? [key, key] : [key, withoutSuffix(key)]
33927
- });
33928
- const changed = (answer.statements[0]?.meta.changes ?? 0) > 0;
33929
- if (!changed) {
33930
- context.output.warn(nothingToDo);
33931
- context.output.result({ name: key, status, changed: false });
33932
- return;
33933
- }
33934
- context.output.result({ name: key, status, changed: true }, [
33935
- status === "applied" ? `\u2713 Marked ${key} as applied.` : `\u2713 Removed ${key} from the ledger.`
33936
- ]);
33937
- }
33938
- async function runMigrationUp(context) {
33939
- const client = await dataClient(context);
33940
- const projectId = await context.resolveProjectId();
33941
- const dir = migrationsDir(context);
33942
- const local = listMigrationFiles(dir);
33943
- const ledger = await appliedMigrations(client, projectId);
33944
- const pending = compareMigrations(local, ledger).filter((row) => row.status === "pending").map((row) => row.name);
33945
- if (pending.length === 0) {
33946
- context.output.result({ projectId, applied: [], pending: [] }, [
33947
- "No pending migrations."
33948
- ]);
33949
- return;
33950
- }
33951
- if (context.flags.dryRun) {
33952
- const problems2 = policyProblems(dir, pending);
33953
- context.output.result({ projectId, dryRun: true, pending, problems: problems2 }, [
33954
- `Pending migrations (${String(pending.length)}):`,
33955
- ...pending.map((name) => ` ${printable2(name)}`),
33956
- ...problems2.map(
33957
- (problem) => `${printable2(problem.migration)}: ${printable2(problem.message)}`
33958
- )
33959
- ]);
33960
- if (problems2.length > 0) process.exitCode = 1;
33961
- return;
33962
- }
33963
- context.output.log(`Pending migrations (${String(pending.length)}):`);
33964
- for (const name of pending) context.output.log(` ${printable2(name)}`);
33965
- await confirmOrAbort(
33966
- context,
33967
- `Apply ${String(pending.length)} migration(s) to ${databaseName(dataEnvironment(context.flags))}? Each file is applied all-or-nothing; a failure stops the run.`
33968
- );
33969
- const { applied, warnings } = await applyMigrationFiles(
33970
- context,
33971
- client,
33972
- projectId,
33973
- dir,
33974
- pending
33975
- );
33976
- context.output.result({ projectId, applied, pending: [], warnings }, [
33977
- `\u2705 Applied ${String(applied.length)} migration(s).`
33978
- ]);
33979
- }
33980
-
33981
34671
  // src/functions/retry.ts
33982
34672
  var FUNCTION_RETRIES = 3;
33983
34673
  var FUNCTION_BUSY_WAIT_MS = 5e3;
@@ -34262,7 +34952,7 @@ async function deployFunctions(options) {
34262
34952
  if (!PER_FUNCTION_CODES.has(error51.code))
34263
34953
  throw withProgress(error51, deployed, failures);
34264
34954
  failures.push({ name, code: error51.code, message: error51.message });
34265
- io.log(`\u2717 ${name}: ${printable2(error51.message)}`);
34955
+ io.log(`\u2717 ${name}: ${printable(error51.message)}`);
34266
34956
  }
34267
34957
  }
34268
34958
  if (failures.length > 0)
@@ -34557,7 +35247,7 @@ async function deployDirectory(sourceDir, options, log2) {
34557
35247
  const set2 = missing.filter((key) => isNaroSystemName(key));
34558
35248
  missing = missing.filter((key) => !isNaroSystemName(key));
34559
35249
  log2(
34560
- `\u25B8 naro sets ${set2.join(", ")} for ${environment} to its own backend's: not asked for`
35250
+ `\u25B8 Naro sets ${set2.join(", ")} for ${environment} to its own backend's: not asked for`
34561
35251
  );
34562
35252
  }
34563
35253
  }
@@ -34708,10 +35398,14 @@ function deploymentUrl(detail, ctx, production) {
34708
35398
 
34709
35399
  export {
34710
35400
  parseDotenvDetailed,
35401
+ FUNCTION_INVOKE_METHODS,
35402
+ FUNCTION_INVOKE_MAX_RESPONSE_BYTES,
35403
+ FUNCTION_INVOKE_TIMEOUT_MS,
34711
35404
  isFunctionName,
34712
35405
  parseGitHubRemote,
34713
35406
  schemaMigrationTitle,
34714
35407
  projectPath,
35408
+ readCapped,
34715
35409
  ANALYTICS_FILTER_KEYS,
34716
35410
  NaroError,
34717
35411
  promoteTargetUnsupported,
@@ -34753,18 +35447,12 @@ export {
34753
35447
  parseCacheControl,
34754
35448
  STORAGE_OBJECTS_TABLE,
34755
35449
  dataError,
34756
- SCHEMA_SQL,
34757
- tableNames,
34758
- snapshotFromRows,
34759
35450
  quoteIdent,
34760
35451
  commentText,
34761
35452
  quotePolicyName,
34762
35453
  NAMED_POLICY_TABLE,
34763
35454
  NAMED_POLICY_SQL,
34764
- loadPolicy,
34765
35455
  parsePolicyNode,
34766
- INDEX_SQL,
34767
- indexesFromRows,
34768
35456
  parsePolicyStatement,
34769
35457
  splitPolicyStatements,
34770
35458
  STORAGE_SUBQUERIES_SQL,
@@ -34777,8 +35465,7 @@ export {
34777
35465
  writeProjectLink,
34778
35466
  removeProjectLink,
34779
35467
  ensureGitignoreHasNaro,
34780
- countLintedTables,
34781
- lintSchema,
35468
+ lintDatabase,
34782
35469
  listMigrationFiles,
34783
35470
  nextMigrationFileName,
34784
35471
  pendingMigrationNames,
@@ -34800,9 +35487,6 @@ export {
34800
35487
  pullEnv,
34801
35488
  detectGitInfo,
34802
35489
  version2 as version,
34803
- BUNDLE_MESSAGES,
34804
- functionsFolder,
34805
- parseJsonc2 as parseJsonc,
34806
35490
  DATA_ENV_MESSAGES,
34807
35491
  dataEnvironment,
34808
35492
  dataClient,
@@ -34818,7 +35502,7 @@ export {
34818
35502
  migrationsDir,
34819
35503
  relativeToCwd,
34820
35504
  appliedMigrations,
34821
- printable2 as printable,
35505
+ printable,
34822
35506
  isPrintable,
34823
35507
  UNSHOWN_WARNING_LINE,
34824
35508
  warningLines,
@@ -34828,6 +35512,10 @@ export {
34828
35512
  applyMigrationFiles,
34829
35513
  runMigrationRepair,
34830
35514
  runMigrationUp,
35515
+ runDbLint,
35516
+ BUNDLE_MESSAGES,
35517
+ functionsFolder,
35518
+ parseJsonc2 as parseJsonc,
34831
35519
  defaultSleep,
34832
35520
  retryAfterSeconds,
34833
35521
  withFunctionRetries,