naro-cli 0.23.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",
@@ -320,6 +327,33 @@ var PHASES = [
320
327
  ];
321
328
  var ACTIVE_STATUSES = new Set(PHASES);
322
329
 
330
+ // ../../packages/core/src/dotenv.ts
331
+ var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/dgm;
332
+ var QUOTES = /* @__PURE__ */ new Set(["'", '"', "`"]);
333
+ function parseDotenvDetailed(text3) {
334
+ const vars = {};
335
+ const unclosedQuotes = [];
336
+ const lines = text3.replace(/\r\n?/g, "\n");
337
+ let line = 1;
338
+ let counted = 0;
339
+ for (const match of lines.matchAll(LINE)) {
340
+ const key = match[1];
341
+ const raw = match[2] ?? "";
342
+ let value = raw.trim();
343
+ const maybeQuote = value[0];
344
+ if (maybeQuote !== void 0 && QUOTES.has(maybeQuote) && !value.includes(maybeQuote, 1)) {
345
+ const at = (match.indices?.[2]?.[0] ?? match.index) + (raw.length - raw.trimStart().length);
346
+ for (; counted < at; counted += 1) if (lines[counted] === "\n") line += 1;
347
+ unclosedQuotes.push(line);
348
+ }
349
+ value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
350
+ if (maybeQuote === '"')
351
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
352
+ vars[key] = value;
353
+ }
354
+ return { vars, unclosedQuotes };
355
+ }
356
+
323
357
  // ../../packages/core/src/deployment-stall.ts
324
358
  var STALL_WINDOW_MS = 10 * 6e4;
325
359
 
@@ -426,7 +460,7 @@ function quoted(span, slug, switchContext) {
426
460
  }
427
461
  }
428
462
  var QUOTED = /`(naro [^`\n]+)`/g;
429
- 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;
430
464
  function bare(match, command, rest, after, slug, switchContext) {
431
465
  const vocabulary = SUBCOMMANDS[command];
432
466
  if (vocabulary === void 0) return match;
@@ -436,7 +470,7 @@ function bare(match, command, rest, after, slug, switchContext) {
436
470
  if (count2 === 0) return match;
437
471
  const head = ["naro", command, ...words.slice(0, count2)];
438
472
  const tail = words.slice(count2);
439
- const following = after.split(/\n|[.;,)](?:\s|$)/, 1)[0] ?? "";
473
+ const following = after.split(/\n|[.;,)](?:\s|$)|\uE000/, 1)[0] ?? "";
440
474
  if (/(?:^|\s)--env[\s=]/.test(following) || after.startsWith(` \u2014 ${PRODUCTION_SWITCH_NOTE}`))
441
475
  return match;
442
476
  const shown = head.join(" ");
@@ -606,7 +640,13 @@ var DEPLOYMENT_ERROR_CODES = [
606
640
  /** A deployment failure: the environment was deleted before its deployment finished. */
607
641
  "ENVIRONMENT_DELETED",
608
642
  /** The edge refused the environment's protection change; the database change is rolled back. */
609
- "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"
610
650
  ];
611
651
  var DeploymentError = class extends Error {
612
652
  code;
@@ -623,6 +663,15 @@ var DeploymentError = class extends Error {
623
663
  // ../../packages/core/src/functions.ts
624
664
  var FUNCTION_NAME_RE = /^[a-z][a-z0-9_-]{0,63}$/;
625
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;
626
675
  function isFunctionName(name) {
627
676
  return FUNCTION_NAME_RE.test(name);
628
677
  }
@@ -683,6 +732,33 @@ function projectPath(teamSlug, projectSlug) {
683
732
  // ../../packages/core/src/platform-mail.ts
684
733
  var encoder = new TextEncoder();
685
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
+
686
762
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
687
763
  var external_exports = {};
688
764
  __export(external_exports, {
@@ -15223,13 +15299,22 @@ var RESERVED_SUBDOMAINS = [
15223
15299
  "www"
15224
15300
  ];
15225
15301
  var BACKEND_WORKER_NAME_PREFIX = "naro-backend-";
15302
+ var AUTH_WORKER_NAME_PREFIX = "naro-auth-";
15303
+ var DATA_WORKER_NAME_PREFIX = "naro-data-";
15304
+ var NARO_WORKER_NAME_PREFIXES = [
15305
+ BACKEND_WORKER_NAME_PREFIX,
15306
+ AUTH_WORKER_NAME_PREFIX,
15307
+ DATA_WORKER_NAME_PREFIX
15308
+ ];
15226
15309
  var SUBDOMAIN_PATTERN = /^[a-z0-9](-?[a-z0-9])*$/;
15227
15310
  var SUBDOMAIN_MAX_LENGTH = 54;
15228
15311
  function subdomainProblem(value) {
15229
15312
  if (value.length < 1 || value.length > SUBDOMAIN_MAX_LENGTH) return "invalid";
15230
15313
  if (!SUBDOMAIN_PATTERN.test(value)) return "invalid";
15231
15314
  if (value.endsWith("-preview")) return "reserved";
15232
- if (value === BACKEND_WORKER_NAME_PREFIX.slice(0, -1) || value.startsWith(BACKEND_WORKER_NAME_PREFIX))
15315
+ if (NARO_WORKER_NAME_PREFIXES.some(
15316
+ (prefix) => value === prefix.slice(0, -1) || value.startsWith(prefix)
15317
+ ))
15233
15318
  return "reserved";
15234
15319
  if (value === FUNCTION_WORKER_NAME_PREFIX.slice(0, -1) || value.startsWith(FUNCTION_WORKER_NAME_PREFIX))
15235
15320
  return "reserved";
@@ -15578,7 +15663,7 @@ var REQUESTS = {
15578
15663
  blocked: "Sites on this team answer 503 until the month ends (UTC).",
15579
15664
  running: "Sites keep running for now.",
15580
15665
  button: "View usage",
15581
- 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.`
15582
15667
  },
15583
15668
  ko: {
15584
15669
  subject80: (team) => `${team} \uD300\uC774 \uC774\uBC88 \uB2EC \uC694\uCCAD \uD55C\uB3C4\uC758 80%\uB97C \uC37C\uC2B5\uB2C8\uB2E4`,
@@ -15588,7 +15673,7 @@ var REQUESTS = {
15588
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.",
15589
15674
  running: "\uC9C0\uAE08\uC740 \uC0AC\uC774\uD2B8\uAC00 \uACC4\uC18D \uB3D9\uC791\uD569\uB2C8\uB2E4.",
15590
15675
  button: "\uC0AC\uC6A9\uB7C9 \uBCF4\uAE30",
15591
- 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.`
15592
15677
  },
15593
15678
  ja: {
15594
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`,
@@ -15598,7 +15683,7 @@ var REQUESTS = {
15598
15683
  blocked: "\u6708\u672B(UTC)\u307E\u3067\u3001\u3053\u306E\u30C1\u30FC\u30E0\u306E\u30B5\u30A4\u30C8\u306F503\u3092\u8FD4\u3057\u307E\u3059\u3002",
15599
15684
  running: "\u73FE\u5728\u3001\u30B5\u30A4\u30C8\u306F\u5F15\u304D\u7D9A\u304D\u52D5\u4F5C\u3057\u3066\u3044\u307E\u3059\u3002",
15600
15685
  button: "\u4F7F\u7528\u91CF\u3092\u898B\u308B",
15601
- 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`
15602
15687
  }
15603
15688
  };
15604
15689
  var BUILDS = {
@@ -16306,7 +16391,7 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
16306
16391
  if (await exists3(workerEntryPath)) {
16307
16392
  throw new DeploymentError(
16308
16393
  "ARTIFACT_INVALID",
16309
- `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.`
16310
16395
  );
16311
16396
  }
16312
16397
  const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
@@ -16359,7 +16444,7 @@ function parseVinextCheckReport(output) {
16359
16444
  const plain = output.replace(ANSI_ESCAPE, "");
16360
16445
  const match = SUMMARY_LINE.exec(plain);
16361
16446
  if (!match) return null;
16362
- const issueLines = [
16447
+ const issueLines2 = [
16363
16448
  ...new Set(
16364
16449
  plain.split("\n").map((line) => ISSUE_LINE.exec(line)?.[1]?.trim()).filter((line) => Boolean(line))
16365
16450
  )
@@ -16374,7 +16459,7 @@ function parseVinextCheckReport(output) {
16374
16459
  supported: Number(match[2]),
16375
16460
  partial: Number(match[3]),
16376
16461
  issues: Number(match[4]),
16377
- issueLines,
16462
+ issueLines: issueLines2,
16378
16463
  partialLines
16379
16464
  };
16380
16465
  }
@@ -16726,10 +16811,10 @@ async function angularOutput(root) {
16726
16811
  } catch {
16727
16812
  workspace = null;
16728
16813
  }
16729
- const project = Object.values(workspace?.projects ?? {}).find(
16814
+ const project2 = Object.values(workspace?.projects ?? {}).find(
16730
16815
  (entry) => entry.projectType === "application"
16731
16816
  );
16732
- const build = project?.architect?.build;
16817
+ const build = project2?.architect?.build;
16733
16818
  const outputPath = build?.options?.outputPath;
16734
16819
  const legacy = build?.builder?.endsWith(":browser") ?? false;
16735
16820
  const clean = (path) => path.replace(/^\.?\/+/, "").replace(/\/+$/, "");
@@ -16741,7 +16826,7 @@ async function angularOutput(root) {
16741
16826
  ""
16742
16827
  );
16743
16828
  const name = Object.entries(workspace?.projects ?? {}).find(
16744
- ([, entry]) => entry === project
16829
+ ([, entry]) => entry === project2
16745
16830
  )?.[0];
16746
16831
  if (name) return `dist/${name}/browser`;
16747
16832
  throw new DeploymentError(
@@ -19645,13 +19730,13 @@ function page(options) {
19645
19730
  <head>
19646
19731
  <meta charset="utf-8">
19647
19732
  <meta name="viewport" content="width=device-width,initial-scale=1">
19648
- <title>${title} \xB7 naro</title>
19733
+ <title>${title} \xB7 Naro</title>
19649
19734
  <link rel="icon" href="${FAVICON}">
19650
19735
  <style>:root{--tone:${tone.color};--tone-soft:${tone.soft};--tone-edge:${tone.edge};--glow:${tone.glow}}${STYLES}</style>
19651
19736
  </head>
19652
19737
  <body>
19653
19738
  <main class="card">
19654
- <span class="brand"><span class="brand-mark">n</span>naro</span>
19739
+ <span class="brand"><span class="brand-mark">n</span>Naro</span>
19655
19740
  <div class="glyph">${tone.icon}</div>
19656
19741
  <h1>${title}</h1>
19657
19742
  <p>${escapeHtml(options.message)}</p>
@@ -19940,6 +20025,271 @@ function openBrowser(url2) {
19940
20025
  child.unref();
19941
20026
  }
19942
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
+
19943
20293
  // ../../packages/sdk/src/environments.ts
19944
20294
  function isReservedEnvTarget(value) {
19945
20295
  return RESERVED_ENVIRONMENT_SLUGS.includes(value);
@@ -20074,6 +20424,13 @@ var NaroClient = class _NaroClient {
20074
20424
  deployments;
20075
20425
  env;
20076
20426
  domains;
20427
+ dns;
20428
+ firewall;
20429
+ flags;
20430
+ bindings;
20431
+ billing;
20432
+ observability;
20433
+ supabase;
20077
20434
  logs;
20078
20435
  db;
20079
20436
  backend;
@@ -20097,6 +20454,13 @@ var NaroClient = class _NaroClient {
20097
20454
  this.deployments = new DeploymentsApi(this);
20098
20455
  this.env = new EnvApi(this);
20099
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);
20100
20464
  this.logs = new LogsApi(this);
20101
20465
  this.db = new DbApi(this);
20102
20466
  this.backend = new BackendApi(this);
@@ -20181,7 +20545,93 @@ var TeamsApi = class {
20181
20545
  { context: "Read team usage" }
20182
20546
  );
20183
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
+ }
20184
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
+ }
20185
20635
  var ProjectsApi = class {
20186
20636
  constructor(client) {
20187
20637
  this.client = client;
@@ -20200,6 +20650,28 @@ var ProjectsApi = class {
20200
20650
  );
20201
20651
  return projectInfo(body.project);
20202
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
+ }
20203
20675
  /**
20204
20676
  * Connects a project to a GitHub repository, turning `git push` into a
20205
20677
  * deployment.
@@ -20233,6 +20705,52 @@ var ProjectsApi = class {
20233
20705
  );
20234
20706
  return projectInfo(body.project);
20235
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
+ }
20236
20754
  };
20237
20755
  var GithubApi = class {
20238
20756
  constructor(client) {
@@ -20474,7 +20992,7 @@ var EnvApi = class {
20474
20992
  */
20475
20993
  async takenBindingNames(projectId) {
20476
20994
  const base2 = `/api/projects/${encodeURIComponent(projectId)}`;
20477
- const [storage, project] = await Promise.all([
20995
+ const [storage, project2] = await Promise.all([
20478
20996
  this.client.request(
20479
20997
  `${base2}/storage`,
20480
20998
  { context: "List storage" }
@@ -20488,7 +21006,7 @@ var EnvApi = class {
20488
21006
  STATIC_ASSETS_BINDING,
20489
21007
  ...storage.storage.map((row) => row.bindingName)
20490
21008
  ]);
20491
- if (project.project.database_provider === "d1")
21009
+ if (project2.project.database_provider === "d1")
20492
21010
  names.add(MANAGED_DATABASE_BINDING);
20493
21011
  return [...names];
20494
21012
  }
@@ -21316,6 +21834,23 @@ var BackendApi = class {
21316
21834
  );
21317
21835
  return { backend, key };
21318
21836
  }
21837
+ /**
21838
+ * Replaces the access JWT's signing key (design 2026-11-01). Signing moves
21839
+ * to the new key at once; tokens the old keys signed keep working until
21840
+ * `previousKeysExpireAt` (their own 15 minutes), so nobody is signed out.
21841
+ * The API keys and the sessions are untouched. A worker uploaded before
21842
+ * this answers `BACKEND_OUTDATED` — `enable` updates it.
21843
+ */
21844
+ async rotateSigningKey(projectId) {
21845
+ return this.client.request(
21846
+ this.path(projectId, "/signing-key/rotate"),
21847
+ {
21848
+ method: "POST",
21849
+ body: {},
21850
+ context: "Rotate signing key"
21851
+ }
21852
+ );
21853
+ }
21319
21854
  /**
21320
21855
  * The sign-in settings (spec P2-6 §12): email, Google and GitHub, the
21321
21856
  * allowed redirect origins, whether sign-in waits for a confirmed email,
@@ -21763,6 +22298,41 @@ var StorageApi = class {
21763
22298
  );
21764
22299
  return body.objects;
21765
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
+ }
21766
22336
  /**
21767
22337
  * A link that downloads one object without a key, for `expiresIn` seconds
21768
22338
  * (1–3600 through the control plane; `VALIDATION_FAILED` past it), on the
@@ -22162,31 +22732,7 @@ function summarizeStatement(statement2) {
22162
22732
  // ../../packages/sdk/src/dotenv.ts
22163
22733
  import { existsSync, readFileSync as readFileSync2 } from "fs";
22164
22734
  import { join as join20 } from "path";
22165
- var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/dgm;
22166
- var QUOTES = /* @__PURE__ */ new Set(["'", '"', "`"]);
22167
- function parseDotenvDetailed(text3) {
22168
- const vars = {};
22169
- const unclosedQuotes = [];
22170
- const lines = text3.replace(/\r\n?/g, "\n");
22171
- let line = 1;
22172
- let counted = 0;
22173
- for (const match of lines.matchAll(LINE)) {
22174
- const key = match[1];
22175
- const raw = match[2] ?? "";
22176
- let value = raw.trim();
22177
- const maybeQuote = value[0];
22178
- if (maybeQuote !== void 0 && QUOTES.has(maybeQuote) && !value.includes(maybeQuote, 1)) {
22179
- const at = (match.indices?.[2]?.[0] ?? match.index) + (raw.length - raw.trimStart().length);
22180
- for (; counted < at; counted += 1) if (lines[counted] === "\n") line += 1;
22181
- unclosedQuotes.push(line);
22182
- }
22183
- value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
22184
- if (maybeQuote === '"')
22185
- value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
22186
- vars[key] = value;
22187
- }
22188
- return { vars, unclosedQuotes };
22189
- }
22735
+ var QUOTES2 = /* @__PURE__ */ new Set(["'", '"', "`"]);
22190
22736
  function parseDotenv(text3) {
22191
22737
  return parseDotenvDetailed(text3).vars;
22192
22738
  }
@@ -22199,7 +22745,7 @@ function readProjectEnv(dir) {
22199
22745
  }
22200
22746
  var PLAIN = /^[^\s"'#$`\\]*$/;
22201
22747
  function bareReadsBack(value) {
22202
- return !/[#\r\n\u2028\u2029]/.test(value) && value.trim() === value && !QUOTES.has(value[0] ?? "");
22748
+ return !/[#\r\n\u2028\u2029]/.test(value) && value.trim() === value && !QUOTES2.has(value[0] ?? "");
22203
22749
  }
22204
22750
  function dotenvValue(value) {
22205
22751
  if (PLAIN.test(value)) return value;
@@ -23371,10 +23917,10 @@ var REALTIME_COMMAND_PROBLEM = "realtime.messages takes SELECT, INSERT and ALL p
23371
23917
  var REALTIME_COLUMNS_PROBLEM = "realtime.messages policies are checked once per join, so they can read topic, extension and private only";
23372
23918
  var REALTIME_TOPIC_PROBLEM = "realtime.topic() is available only in policies of realtime.messages";
23373
23919
  function quotedRealtimeMessagesProblem(written) {
23374
- 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`;
23375
23921
  }
23376
23922
  function quotedRealtimeAliasProblem(written) {
23377
- 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`;
23378
23924
  }
23379
23925
  function realtimeFunctionProblem(name) {
23380
23926
  return `function realtime.${name} is not available in policies: use realtime.topic()`;
@@ -23859,7 +24405,7 @@ function storageHelperColumnProblem(fn) {
23859
24405
  return `storage.${fn}() takes the name column of storage.objects`;
23860
24406
  }
23861
24407
  function quotedStorageObjectsProblem(written) {
23862
- 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`;
23863
24409
  }
23864
24410
  function count(state, token) {
23865
24411
  if (++state.nodes > MAX_EXPRESSION_NODES)
@@ -24317,7 +24863,7 @@ function readTableName(stream) {
24317
24863
  if (stream.acceptOperator(".")) {
24318
24864
  if (first.quoted ? name !== "public" : asciiLower(name) !== "public")
24319
24865
  throw new PolicySyntaxError(
24320
- `schema ${name} does not exist: naro's tables are in public`,
24866
+ `schema ${name} does not exist: Naro's tables are in public`,
24321
24867
  first.position
24322
24868
  );
24323
24869
  token = stream.peek();
@@ -24428,7 +24974,7 @@ function readJwtAccess(stream) {
24428
24974
  const next = stream.peek();
24429
24975
  if (isOperator(next, "->") || isOperator(next, "->>"))
24430
24976
  throw new PolicySyntaxError(
24431
- "naro's auth.jwt() claims are not nested",
24977
+ "Naro's auth.jwt() claims are not nested",
24432
24978
  next.position
24433
24979
  );
24434
24980
  return {
@@ -24527,7 +25073,7 @@ function readPrimary(state) {
24527
25073
  stream.next();
24528
25074
  if (isOperator(stream.peek(), "(") || isOperator(stream.peek(), "."))
24529
25075
  throw new PolicySyntaxError(
24530
- `${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`,
24531
25077
  token.position
24532
25078
  );
24533
25079
  return { kind: "column", qualifier: token.value, name: name.value };
@@ -24580,7 +25126,7 @@ function readRealtimeColumn(state) {
24580
25126
  const next = stream.peek();
24581
25127
  if (isOperator(next, "(") || isOperator(next, "."))
24582
25128
  throw new PolicySyntaxError(
24583
- `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`,
24584
25130
  next.position
24585
25131
  );
24586
25132
  return { kind: "column", qualifier: REALTIME_MESSAGES, name };
@@ -26502,7 +27048,7 @@ function textTruthProblem(snapshot, table, expr, names = [asciiLower(table.name)
26502
27048
  function selectCheckRefusal(path, column2, problem) {
26503
27049
  const reason = {
26504
27050
  generated: `is not available: the select policy reads the generated column ${column2}, which a new row cannot be checked against`,
26505
- 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`,
26506
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`,
26507
27053
  textual: `is not available: the select policy turns the number column ${column2} into text, which a new row cannot be checked against`,
26508
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`,
@@ -26900,7 +27446,7 @@ function checkProblemReason(column2, problem) {
26900
27446
  case "generated":
26901
27447
  return `${column2} is a generated column, which a WITH CHECK cannot read (an update policy without WITH CHECK checks its USING)`;
26902
27448
  case "collation":
26903
- 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`;
26904
27450
  case "pattern":
26905
27451
  return `${column2} is a number column, which a WITH CHECK cannot compare with LIKE or ILIKE`;
26906
27452
  case "textual":
@@ -26916,7 +27462,7 @@ function usingProblemReason(name, problem) {
26916
27462
  case "generated":
26917
27463
  return `${name} is a generated column, which a USING judged on a row's values cannot read`;
26918
27464
  case "collation":
26919
- 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`;
26920
27466
  case "pattern":
26921
27467
  return `${name} is a number column, which a USING judged on a row's values cannot compare with LIKE or ILIKE`;
26922
27468
  case "textual":
@@ -27314,7 +27860,7 @@ function checkCase(table, terms, guard, pass, action, nonce) {
27314
27860
  fail(
27315
27861
  invalidQuery(
27316
27862
  action === "insert" ? "returning" : "values",
27317
- `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`
27318
27864
  )
27319
27865
  );
27320
27866
  fail(policyInvalid(first.policy, action));
@@ -27476,7 +28022,7 @@ function checkFragment(target2, req, snapshot, command, row, nonce, stored, with
27476
28022
  const first = terms.find((term) => term.policy !== void 0)?.policy;
27477
28023
  return fail(
27478
28024
  first ? policyInvalid(first, command) : dataError("INTERNAL_ERROR"),
27479
- `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`
27480
28026
  );
27481
28027
  }
27482
28028
  return [schemaGuard(name, sql, command, checked), ...guards, ...terms];
@@ -28657,7 +29203,7 @@ var Reader = class {
28657
29203
  }
28658
29204
  if (this.takeWord("WHERE")) top.resolved.push(this.where());
28659
29205
  this.returning(top.resolved);
28660
- } else throw new Unreadable("not a statement naro compiles");
29206
+ } else throw new Unreadable("not a statement Naro compiles");
28661
29207
  return top;
28662
29208
  }
28663
29209
  };
@@ -30797,9 +31343,9 @@ var SYSTEM_MIGRATIONS = [
30797
31343
  ];
30798
31344
 
30799
31345
  // ../../packages/data-core/src/policy-migration.ts
30800
- 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).";
30801
31347
  function disableRefusal(table) {
30802
- 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);`;
30803
31349
  }
30804
31350
  function isStorageObjects(table) {
30805
31351
  return asciiLower(table) === STORAGE_OBJECTS;
@@ -31520,6 +32066,159 @@ function lintSchema(input) {
31520
32066
  ];
31521
32067
  }
31522
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
+
31523
32222
  // ../../packages/sdk/src/migrations.ts
31524
32223
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
31525
32224
  import { resolve as resolve9 } from "path";
@@ -31601,7 +32300,7 @@ function migrationsPendingError(pending) {
31601
32300
  );
31602
32301
  }
31603
32302
  function migrationNameMismatch(sent, recorded) {
31604
- 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.`;
31605
32304
  }
31606
32305
 
31607
32306
  // ../../packages/sdk/src/reset-plan.ts
@@ -32031,6 +32730,43 @@ async function promptForMissingEnv(ctx, keys) {
32031
32730
  return collected;
32032
32731
  }
32033
32732
 
32733
+ // src/env-services.ts
32734
+ var PUBLIC_PREFIXES = [
32735
+ "NEXT_PUBLIC_",
32736
+ "NUXT_PUBLIC_",
32737
+ "EXPO_PUBLIC_",
32738
+ "REACT_APP_",
32739
+ "PUBLIC_",
32740
+ "GATSBY_",
32741
+ "VITE_"
32742
+ ];
32743
+ function bareEnvKey(key) {
32744
+ let longest = "";
32745
+ for (const prefix of PUBLIC_PREFIXES)
32746
+ if (key.startsWith(prefix) && prefix.length > longest.length)
32747
+ longest = prefix;
32748
+ return key.slice(longest.length);
32749
+ }
32750
+ function supabaseKeys(keys) {
32751
+ return keys.filter((key) => bareEnvKey(key).startsWith("SUPABASE_"));
32752
+ }
32753
+ function envServiceHints(keys) {
32754
+ const own2 = supabaseKeys(keys);
32755
+ const isUrl = (key) => bareEnvKey(key).endsWith("_URL");
32756
+ const isKey = (key) => bareEnvKey(key).endsWith("_KEY");
32757
+ const placed = own2.filter((key) => isUrl(key) || isKey(key));
32758
+ if (placed.length === 0) return [];
32759
+ const lines = [
32760
+ placed.length === keys.length ? " Supabase \u2014 in that project's dashboard:" : ` Supabase (${placed.join(", ")}) \u2014 in that project's dashboard:`
32761
+ ];
32762
+ if (placed.some(isUrl))
32763
+ lines.push(
32764
+ " the project URL \u2014 the Connect dialog at the top of the page"
32765
+ );
32766
+ if (placed.some(isKey)) lines.push(" the keys \u2014 Settings > API Keys");
32767
+ return lines;
32768
+ }
32769
+
32034
32770
  // src/env-surface.ts
32035
32771
  function pullEnv(rows, surface) {
32036
32772
  const chosen = /* @__PURE__ */ new Map();
@@ -32441,6 +33177,427 @@ async function copyWorkingTree(source, checkout) {
32441
33177
  };
32442
33178
  }
32443
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
+
32444
33601
  // src/functions/deploy.ts
32445
33602
  import { relative as relative11, resolve as resolve13, sep as sep10 } from "path";
32446
33603
 
@@ -32498,8 +33655,8 @@ var BUNDLE_MESSAGES = {
32498
33655
  };
32499
33656
 
32500
33657
  // ../../packages/functions-bundler/src/discover.ts
32501
- import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
32502
- 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";
32503
33660
 
32504
33661
  // ../../packages/functions-bundler/src/limits.ts
32505
33662
  var FUNCTION_BUNDLE_MAX_BYTES = 67108864;
@@ -32523,8 +33680,8 @@ function isEntrypoint(value) {
32523
33680
  function functionsFolder(options) {
32524
33681
  const root = resolve11(options.cwd, options.root ?? ".");
32525
33682
  const explicit = options.dir !== void 0;
32526
- const dir = explicit ? resolve11(options.cwd, options.dir) : join27(root, "naro", "functions");
32527
- 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("/") || ".";
32528
33685
  return { dir, label: label2, root, explicit };
32529
33686
  }
32530
33687
  function listFunctionNames(folder) {
@@ -32532,7 +33689,7 @@ function listFunctionNames(folder) {
32532
33689
  try {
32533
33690
  entries = readdirSync3(folder.dir, { withFileTypes: true });
32534
33691
  } catch {
32535
- if (!folder.explicit && existsSync7(join27(folder.root, "supabase", "functions")))
33692
+ if (!folder.explicit && existsSync7(join28(folder.root, "supabase", "functions")))
32536
33693
  throw new FunctionBundleError(
32537
33694
  "VALIDATION_FAILED",
32538
33695
  BUNDLE_MESSAGES.supabaseFound
@@ -32553,11 +33710,11 @@ function listFunctionNames(folder) {
32553
33710
  return names;
32554
33711
  }
32555
33712
  function readFunctionJson(dir, name) {
32556
- const file2 = join27(dir, "function.json");
33713
+ const file2 = join28(dir, "function.json");
32557
33714
  if (!existsSync7(file2)) return { verifyJwt: void 0, entrypoint: void 0 };
32558
33715
  let parsed;
32559
33716
  try {
32560
- parsed = JSON.parse(readFileSync7(file2, "utf8"));
33717
+ parsed = JSON.parse(readFileSync8(file2, "utf8"));
32561
33718
  } catch {
32562
33719
  throw new FunctionBundleError(
32563
33720
  "VALIDATION_FAILED",
@@ -32597,7 +33754,7 @@ function readFunctionSource(folder, name) {
32597
33754
  "VALIDATION_FAILED",
32598
33755
  BUNDLE_MESSAGES.badName(name)
32599
33756
  );
32600
- const dir = join27(folder.dir, name);
33757
+ const dir = join28(folder.dir, name);
32601
33758
  if (!statSync2(dir, { throwIfNoEntry: false })?.isDirectory())
32602
33759
  throw new FunctionBundleError(
32603
33760
  "VALIDATION_FAILED",
@@ -32606,14 +33763,14 @@ function readFunctionSource(folder, name) {
32606
33763
  const config2 = readFunctionJson(dir, name);
32607
33764
  let entrypoint;
32608
33765
  if (config2.entrypoint !== void 0) {
32609
- if (!existsSync7(join27(dir, ...config2.entrypoint.split("/"))))
33766
+ if (!existsSync7(join28(dir, ...config2.entrypoint.split("/"))))
32610
33767
  throw new FunctionBundleError(
32611
33768
  "FUNCTION_BUNDLE_FAILED",
32612
33769
  BUNDLE_MESSAGES.entryMissing(name, config2.entrypoint)
32613
33770
  );
32614
33771
  entrypoint = config2.entrypoint;
32615
33772
  } else {
32616
- const found = ENTRY_FILES.find((file2) => existsSync7(join27(dir, file2)));
33773
+ const found = ENTRY_FILES.find((file2) => existsSync7(join28(dir, file2)));
32617
33774
  if (found === void 0)
32618
33775
  throw new FunctionBundleError(
32619
33776
  "FUNCTION_BUNDLE_FAILED",
@@ -32624,16 +33781,16 @@ function readFunctionSource(folder, name) {
32624
33781
  return {
32625
33782
  name,
32626
33783
  dir,
32627
- entry: join27(dir, ...entrypoint.split("/")),
33784
+ entry: join28(dir, ...entrypoint.split("/")),
32628
33785
  entrypoint,
32629
33786
  verifyJwt: config2.verifyJwt
32630
33787
  };
32631
33788
  }
32632
33789
  function configTomlWarning(folder) {
32633
- if (!folder.dir.endsWith(join27("supabase", "functions"))) return null;
32634
- 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");
32635
33792
  if (!existsSync7(file2)) return null;
32636
- 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;
32637
33794
  }
32638
33795
 
32639
33796
  // ../../packages/functions-bundler/src/jsonc.ts
@@ -32836,22 +33993,22 @@ ${injects.map(
32836
33993
  }
32837
33994
 
32838
33995
  // ../../packages/functions-bundler/src/deno-imports.ts
32839
- import { existsSync as existsSync9, readFileSync as readFileSync9, realpathSync as realpathSync2 } from "fs";
32840
- 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";
32841
33998
 
32842
33999
  // ../../packages/functions-bundler/src/import-map.ts
32843
- import { existsSync as existsSync8, readFileSync as readFileSync8, realpathSync } from "fs";
32844
- 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";
32845
34002
  import { fileURLToPath as fileURLToPath3 } from "url";
32846
34003
 
32847
34004
  // ../../packages/functions-bundler/src/printable.ts
32848
- var UNPRINTABLE = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
32849
- function printable(text3) {
32850
- 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, "?");
32851
34008
  }
32852
34009
  function causeLine(error51) {
32853
34010
  const text3 = error51 instanceof Error ? error51.message : String(error51);
32854
- return printable(
34011
+ return printable2(
32855
34012
  text3.split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "unknown error"
32856
34013
  );
32857
34014
  }
@@ -32875,7 +34032,7 @@ function expandBareKeys(imports) {
32875
34032
  function load(file2, label2) {
32876
34033
  let parsed;
32877
34034
  try {
32878
- parsed = parseJsonc2(readFileSync8(file2, "utf8"));
34035
+ parsed = parseJsonc2(readFileSync9(file2, "utf8"));
32879
34036
  } catch {
32880
34037
  throw new FunctionBundleError(
32881
34038
  "VALIDATION_FAILED",
@@ -32895,7 +34052,7 @@ function load(file2, label2) {
32895
34052
  if (key.endsWith("/") && !value.endsWith("/"))
32896
34053
  throw new FunctionBundleError(
32897
34054
  "VALIDATION_FAILED",
32898
- printable(BUNDLE_MESSAGES.importMapPrefix(label2, key, value))
34055
+ printable2(BUNDLE_MESSAGES.importMapPrefix(label2, key, value))
32899
34056
  );
32900
34057
  return {
32901
34058
  imports: DENO_CONFIGS.has(basename4(file2)) ? expandBareKeys(written) : written,
@@ -32913,7 +34070,7 @@ function readImportMapFile(file2, label2) {
32913
34070
  }
32914
34071
  function readImportMap(functionDir, fallback, labelOf) {
32915
34072
  for (const name of FILES) {
32916
- const file2 = join28(functionDir, name);
34073
+ const file2 = join29(functionDir, name);
32917
34074
  if (!existsSync8(file2)) continue;
32918
34075
  const map2 = load(file2, labelOf(file2));
32919
34076
  if (map2 !== null) return map2;
@@ -33038,7 +34195,7 @@ function parseNpm(text3) {
33038
34195
  }
33039
34196
  var afterScheme = (specifier) => specifier.slice(4).replace(/^\//, "");
33040
34197
  var isPackageName = (pkg) => pkg.startsWith("@") ? /^@[^/]+\/[^/]+$/.test(pkg) : /^[^/]+$/.test(pkg);
33041
- var refuse2 = (text3) => ({ errors: [{ text: printable(text3) }] });
34198
+ var refuse2 = (text3) => ({ errors: [{ text: printable2(text3) }] });
33042
34199
  var installOf = (pkg, range) => range ? `${pkg}@${range}` : pkg;
33043
34200
  function realPath2(path) {
33044
34201
  try {
@@ -33047,16 +34204,16 @@ function realPath2(path) {
33047
34204
  return path;
33048
34205
  }
33049
34206
  }
33050
- var fromRoot = (projectRoot, path) => relative8(projectRoot, path).split(sep8).join("/");
34207
+ var fromRoot = (projectRoot, path) => relative9(projectRoot, path).split(sep8).join("/");
33051
34208
  function isInside(path, projectRoot) {
33052
- const rel = relative8(projectRoot, path);
34209
+ const rel = relative9(projectRoot, path);
33053
34210
  return rel === "" || rel !== ".." && !rel.startsWith(`..${sep8}`) && !isAbsolute5(rel);
33054
34211
  }
33055
34212
  function packageDir(from, projectRoot) {
33056
34213
  let current = from;
33057
34214
  while (isInside(current, projectRoot)) {
33058
34215
  const inNodeModules = fromRoot(projectRoot, current).split("/").includes("node_modules");
33059
- if (!inNodeModules && existsSync9(join29(current, "package.json")))
34216
+ if (!inNodeModules && existsSync9(join30(current, "package.json")))
33060
34217
  return fromRoot(projectRoot, current) || ".";
33061
34218
  const up = dirname9(current);
33062
34219
  if (up === current) break;
@@ -33068,7 +34225,7 @@ function isInstalled(from, pkg) {
33068
34225
  if (pkg === "") return false;
33069
34226
  let current = from;
33070
34227
  for (; ; ) {
33071
- if (existsSync9(join29(current, "node_modules", pkg))) return true;
34228
+ if (existsSync9(join30(current, "node_modules", pkg))) return true;
33072
34229
  const up = dirname9(current);
33073
34230
  if (up === current) return false;
33074
34231
  current = up;
@@ -33078,10 +34235,10 @@ var NODE_MODULES = /[\\/]node_modules[\\/]/;
33078
34235
  function installedVersion(resolved, pkg) {
33079
34236
  let current = dirname9(resolved);
33080
34237
  for (; ; ) {
33081
- const file2 = join29(current, "package.json");
34238
+ const file2 = join30(current, "package.json");
33082
34239
  if (existsSync9(file2)) {
33083
34240
  try {
33084
- const manifest2 = JSON.parse(readFileSync9(file2, "utf8"));
34241
+ const manifest2 = JSON.parse(readFileSync10(file2, "utf8"));
33085
34242
  if (manifest2.name === pkg && typeof manifest2.version === "string")
33086
34243
  return manifest2.version;
33087
34244
  } catch {
@@ -33094,7 +34251,7 @@ function installedVersion(resolved, pkg) {
33094
34251
  }
33095
34252
  var byCodePoint = (a, b) => a < b ? -1 : a > b ? 1 : 0;
33096
34253
  function missingPackagesMessage(unsorted) {
33097
- return printable(installSentence(unsorted));
34254
+ return printable2(installSentence(unsorted));
33098
34255
  }
33099
34256
  function installSentence(unsorted) {
33100
34257
  const missing = [...unsorted].sort(
@@ -33175,7 +34332,7 @@ function denoImportsPlugin(options) {
33175
34332
  if (spec.range) {
33176
34333
  const version3 = installedVersion(result.path, pkgName);
33177
34334
  if (version3 !== null && inRange(version3, spec.range) === false) {
33178
- const warning = printable(
34335
+ const warning = printable2(
33179
34336
  BUNDLE_MESSAGES.outOfRange(
33180
34337
  pkgName,
33181
34338
  version3,
@@ -33262,7 +34419,7 @@ function denoImportsPlugin(options) {
33262
34419
 
33263
34420
  // ../../packages/functions-bundler/src/bundle.ts
33264
34421
  import { createHash as createHash4 } from "crypto";
33265
- 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";
33266
34423
  import { gzipSync } from "zlib";
33267
34424
 
33268
34425
  // ../../packages/functions-runtime/dist/index.js
@@ -33283,7 +34440,7 @@ function runtimeCompressedBytes() {
33283
34440
  return runtimeGzip;
33284
34441
  }
33285
34442
  function relativeLabel(root, file2) {
33286
- return relative9(root, file2).split(sep9).join("/");
34443
+ return relative10(root, file2).split(sep9).join("/");
33287
34444
  }
33288
34445
  function location(message) {
33289
34446
  return message.location ? `${message.location.file}:${String(message.location.line)}` : null;
@@ -33338,7 +34495,7 @@ async function bundleFunction(source, folder, options = {}) {
33338
34495
  result = await build({
33339
34496
  absWorkingDir: folder.root,
33340
34497
  entryPoints: [source.entry],
33341
- outfile: join30(source.dir, NARO_USER_MODULE),
34498
+ outfile: join31(source.dir, NARO_USER_MODULE),
33342
34499
  bundle: true,
33343
34500
  write: false,
33344
34501
  format: "esm",
@@ -33430,7 +34587,7 @@ function bootErrorLine(logs, error51) {
33430
34587
  const logged = logs.find((line2) => line2.includes("Uncaught")) ?? logs.find((line2) => line2.includes("Error"));
33431
34588
  const raw = logged ?? (error51 instanceof Error ? error51.message : String(error51));
33432
34589
  const line = raw.split("\n").map((part) => part.trim()).find((part) => part.length > 0) ?? "unknown error";
33433
- return printable(line.replace(WORKERD_SERVICE_HEAD, ""));
34590
+ return printable2(line.replace(WORKERD_SERVICE_HEAD, ""));
33434
34591
  }
33435
34592
  async function bootCheck(name, code, options = {}) {
33436
34593
  const timeoutMs = options.timeoutMs ?? BOOT_TIMEOUT_MS;
@@ -33511,407 +34668,6 @@ async function bootCheck(name, code, options = {}) {
33511
34668
  }
33512
34669
  }
33513
34670
 
33514
- // src/commands/migration.ts
33515
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "fs";
33516
- import { join as join31, relative as relative10 } from "path";
33517
-
33518
- // src/data-environment.ts
33519
- var DATA_ENV_MESSAGES = {
33520
- name: "--env must be production or an environment name.",
33521
- preview: "--env preview has no data of its own: preview deployments use production's. Drop --env, or name an environment.",
33522
- projectWide: (command) => `--env is not an option of ${command}: turning the backend on or off applies to every environment.`
33523
- };
33524
- function dataEnvironment(flags) {
33525
- const value = flags.env;
33526
- if (value === void 0 || value === "production") return null;
33527
- if (value === "preview")
33528
- throw new NaroError("VALIDATION_FAILED", DATA_ENV_MESSAGES.preview);
33529
- if (environmentSlugProblem(value) !== null)
33530
- throw new NaroError("VALIDATION_FAILED", DATA_ENV_MESSAGES.name);
33531
- return value;
33532
- }
33533
- async function dataClient(context) {
33534
- return (await context.client()).inEnvironment(dataEnvironment(context.flags));
33535
- }
33536
- function databaseName(environment) {
33537
- return environment === null ? "the project database" : `the ${environment} environment's database`;
33538
- }
33539
- function enableBackendCommand(environment) {
33540
- return environment === null ? "naro backend enable" : `naro environments provision ${environment}`;
33541
- }
33542
- function envFlag(environment) {
33543
- return environment === null ? "" : ` --env ${environment}`;
33544
- }
33545
- function forEnvironment(text3, environment) {
33546
- return environmentGuidance(text3, environment);
33547
- }
33548
-
33549
- // src/prompt.ts
33550
- import { createInterface as createInterface2 } from "readline";
33551
- import { Writable } from "stream";
33552
- function confirmHint(defaultYes) {
33553
- return defaultYes ? "(Y/n)" : "(y/N)";
33554
- }
33555
- function readConfirmAnswer(answer, defaultYes) {
33556
- const normalized2 = answer.trim().toLowerCase();
33557
- if (normalized2 === "") return defaultYes;
33558
- if (normalized2 === "y" || normalized2 === "yes") return true;
33559
- if (!defaultYes) return false;
33560
- if (normalized2 === "n" || normalized2 === "no") return false;
33561
- return null;
33562
- }
33563
- function isInteractive() {
33564
- return process.stdin.isTTY === true && process.stdout.isTTY === true;
33565
- }
33566
- async function readStdin() {
33567
- let data = "";
33568
- for await (const chunk of process.stdin) data += String(chunk);
33569
- return data.replace(/\r?\n$/, "");
33570
- }
33571
- var TtyPrompter = class {
33572
- async ask(question) {
33573
- const rl = createInterface2({
33574
- input: process.stdin,
33575
- output: process.stdout
33576
- });
33577
- try {
33578
- return await new Promise(
33579
- (resolve15) => rl.question(question, resolve15)
33580
- );
33581
- } finally {
33582
- rl.close();
33583
- }
33584
- }
33585
- async askHidden(question) {
33586
- let muted = false;
33587
- const output = new Writable({
33588
- write(chunk, _enc, done) {
33589
- if (!muted) process.stdout.write(chunk);
33590
- done();
33591
- }
33592
- });
33593
- const rl = createInterface2({
33594
- input: process.stdin,
33595
- output,
33596
- terminal: true
33597
- });
33598
- try {
33599
- const answer = new Promise(
33600
- (resolve15) => rl.question(question, resolve15)
33601
- );
33602
- muted = true;
33603
- const value = await answer;
33604
- process.stdout.write("\n");
33605
- return value;
33606
- } finally {
33607
- rl.close();
33608
- }
33609
- }
33610
- async confirm(question, options = {}) {
33611
- const defaultYes = options.defaultYes ?? false;
33612
- for (; ; ) {
33613
- const read = readConfirmAnswer(
33614
- await this.ask(`${question} ${confirmHint(defaultYes)} `),
33615
- defaultYes
33616
- );
33617
- if (read !== null) return read;
33618
- }
33619
- }
33620
- async select(question, options) {
33621
- process.stdout.write(`${question}
33622
- `);
33623
- options.forEach((option, index) => {
33624
- process.stdout.write(` ${String(index + 1)}. ${option}
33625
- `);
33626
- });
33627
- for (; ; ) {
33628
- const answer = Number.parseInt(
33629
- (await this.ask(`Choose 1-${String(options.length)}: `)).trim(),
33630
- 10
33631
- );
33632
- if (Number.isInteger(answer) && answer >= 1 && answer <= options.length)
33633
- return answer - 1;
33634
- }
33635
- }
33636
- };
33637
- var NonInteractivePrompter = class {
33638
- constructor(assumeYes) {
33639
- this.assumeYes = assumeYes;
33640
- }
33641
- refuse(question) {
33642
- throw new NaroError(
33643
- "VALIDATION_FAILED",
33644
- `Interactive input required (${question.trim()}). Pass the value via flags/stdin or run without --json in a terminal.`
33645
- );
33646
- }
33647
- async ask(question) {
33648
- this.refuse(question);
33649
- }
33650
- async askHidden(question) {
33651
- this.refuse(question);
33652
- }
33653
- async confirm() {
33654
- return this.assumeYes;
33655
- }
33656
- async select(question) {
33657
- this.refuse(question);
33658
- }
33659
- };
33660
- function createPrompter(options) {
33661
- if (options.yes || options.json || !isInteractive())
33662
- return new NonInteractivePrompter(options.yes);
33663
- return new TtyPrompter();
33664
- }
33665
-
33666
- // src/commands/confirm.ts
33667
- function cannotAsk(context) {
33668
- return context.flags.json || !isInteractive();
33669
- }
33670
- async function confirmOrAbort(context, question) {
33671
- if (context.flags.yes) return;
33672
- if (cannotAsk(context)) {
33673
- throw new NaroError(
33674
- "CONFIRMATION_REQUIRED",
33675
- `${question} Re-run with --yes to confirm.`
33676
- );
33677
- }
33678
- const confirmed = await context.prompter.confirm(question);
33679
- if (!confirmed) throw new NaroError("CANCELED", "Canceled.");
33680
- }
33681
-
33682
- // src/commands/migration.ts
33683
- var processStdin = {
33684
- stdinIsTty: () => process.stdin.isTTY === true,
33685
- readStdin
33686
- };
33687
- function migrationsDir(context) {
33688
- return resolveMigrationsDir({
33689
- baseDir: context.cwd,
33690
- rootDirectory: context.flags.root,
33691
- dir: context.flags.dir
33692
- });
33693
- }
33694
- function relativeToCwd(context, path) {
33695
- const rel = relative10(context.cwd, path);
33696
- return rel === "" ? "." : rel;
33697
- }
33698
- async function appliedMigrations(client, projectId) {
33699
- try {
33700
- return await client.db.migrations(projectId);
33701
- } catch (error51) {
33702
- if (isNaroError(error51) && error51.code === "DATABASE_NOT_READY") return [];
33703
- throw error51;
33704
- }
33705
- }
33706
- var UNPRINTABLE2 = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
33707
- function printable2(text3) {
33708
- return text3.replace(UNPRINTABLE2, "?");
33709
- }
33710
- function isPrintable(text3) {
33711
- return printable2(text3) === text3;
33712
- }
33713
- var UNSHOWN_WARNING_LINE = "(a line of this warning has characters the terminal cannot show; run with --json to see it exactly)";
33714
- function warningLines(message) {
33715
- return message.split("\n").map(
33716
- (line, index) => index === 0 ? `\u26A0 ${printable2(line)}` : ` ${isPrintable(line) ? line : UNSHOWN_WARNING_LINE}`
33717
- );
33718
- }
33719
- function policyProblems(dir, names) {
33720
- return names.flatMap((name) => {
33721
- const split2 = splitStatements(readFileSync10(join31(dir, name), "utf8"));
33722
- if (!split2.ok) return [];
33723
- const planned = planPolicyMigration(
33724
- split2.statements.map((statement2) => statement2.text)
33725
- );
33726
- return planned.ok ? [] : [{ migration: name, message: planned.message }];
33727
- });
33728
- }
33729
- function warningLine(warning) {
33730
- if (warning === "out-of-order")
33731
- return " \u26A0 older than an applied migration \u2014 db push will still apply it";
33732
- if (warning === "ledger-name-missing-extension")
33733
- return " \u26A0 recorded without .sql in the ledger";
33734
- return null;
33735
- }
33736
- async function runMigrationNew(context, deps = processStdin) {
33737
- const [name] = context.positionalOrThrow(
33738
- 1,
33739
- "Usage: naro migration new <name>"
33740
- );
33741
- const dir = migrationsDir(context);
33742
- const fileName = nextMigrationFileName(
33743
- listMigrationFiles(dir),
33744
- name
33745
- );
33746
- mkdirSync3(dir, { recursive: true });
33747
- const piped = !deps.stdinIsTty() ? await deps.readStdin() : "";
33748
- const fromStdin = piped.length > 0;
33749
- const body = fromStdin ? piped.endsWith("\n") ? piped : `${piped}
33750
- ` : `-- Migration: ${fileName}
33751
- `;
33752
- const path = join31(dir, fileName);
33753
- writeFileSync3(path, body, { flag: "wx" });
33754
- const relPath = relativeToCwd(context, path);
33755
- context.output.result({ file: fileName, path: relPath, fromStdin }, [
33756
- `\u2705 Created ${relPath}`
33757
- ]);
33758
- }
33759
- async function runMigrationList(context) {
33760
- const client = await dataClient(context);
33761
- const projectId = await context.resolveProjectId();
33762
- const dir = migrationsDir(context);
33763
- const local = listMigrationFiles(dir);
33764
- const ledger = await appliedMigrations(client, projectId);
33765
- const migrations = compareMigrations(local, ledger);
33766
- const relDir = relativeToCwd(context, dir);
33767
- const lines = migrations.length === 0 ? ["No migrations yet. Create one with `naro migration new <name>`."] : [
33768
- `Migrations in ${relDir} (${String(migrations.length)}):`,
33769
- ...migrations.flatMap((row) => {
33770
- const line = ` ${row.status.padEnd(12)}${printable2(row.name)}${row.appliedAt ? ` ${printable2(row.appliedAt)}` : ""}`;
33771
- const warning = warningLine(row.warning);
33772
- return warning ? [line, warning] : [line];
33773
- })
33774
- ];
33775
- context.output.result({ projectId, dir: relDir, migrations }, lines);
33776
- }
33777
- async function applyMigrationFiles(context, client, projectId, dir, names) {
33778
- const applied = [];
33779
- const warnings = [];
33780
- for (const name of names) {
33781
- const sql = readFileSync10(join31(dir, name), "utf8");
33782
- let answer;
33783
- try {
33784
- answer = await client.db.applyMigration(projectId, { name, sql });
33785
- } catch (error51) {
33786
- if (!isNaroError(error51)) throw error51;
33787
- for (const message of serverWarnings(error51.details))
33788
- for (const line of warningLines(message)) context.output.warn(line);
33789
- const shown = context.output.json ? name : printable2(name);
33790
- throw new NaroError(error51.code, `${shown}: ${error51.message}`, {
33791
- ...error51.status === void 0 ? {} : { status: error51.status },
33792
- details: {
33793
- ...error51.details,
33794
- migration: name,
33795
- applied,
33796
- appliedWarnings: warnings
33797
- },
33798
- cause: error51
33799
- });
33800
- }
33801
- context.output.log(`\u2713 ${printable2(name)}`);
33802
- applied.push(name);
33803
- for (const message of answer.warnings) {
33804
- warnings.push({ migration: name, message });
33805
- for (const line of warningLines(message)) context.output.warn(line);
33806
- }
33807
- }
33808
- return { applied, warnings };
33809
- }
33810
- function serverWarnings(details) {
33811
- const warnings = details?.warnings;
33812
- return Array.isArray(warnings) ? warnings.filter(
33813
- (warning) => typeof warning === "string"
33814
- ) : [];
33815
- }
33816
- var MIGRATION_NAME_RE = /^[A-Za-z0-9_.-]{1,200}\.sql$/;
33817
- 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)";
33818
- var LEDGER_INSERT = "INSERT INTO d1_migrations (name) SELECT ? WHERE NOT EXISTS (SELECT 1 FROM d1_migrations WHERE name = ?)";
33819
- var LEDGER_DELETE = "DELETE FROM d1_migrations WHERE name IN (?, ?)";
33820
- function withoutSuffix(key) {
33821
- return key.slice(0, -".sql".length);
33822
- }
33823
- async function runMigrationRepair(context) {
33824
- const [name] = context.positionalOrThrow(
33825
- 1,
33826
- "Usage: naro migration repair <name> --status applied|reverted"
33827
- );
33828
- const key = name;
33829
- if (!MIGRATION_NAME_RE.test(key))
33830
- throw new NaroError(
33831
- "VALIDATION_FAILED",
33832
- "name must end with .sql and contain only letters, digits, _ . -"
33833
- );
33834
- const status = context.flags.status;
33835
- if (status !== "applied" && status !== "reverted")
33836
- throw new NaroError(
33837
- "VALIDATION_FAILED",
33838
- "Pass --status applied or --status reverted."
33839
- );
33840
- const client = await dataClient(context);
33841
- const projectId = await context.resolveProjectId();
33842
- const ledger = await appliedMigrations(client, projectId);
33843
- const recorded = ledger.some(
33844
- (row) => row.name === key || row.name === withoutSuffix(key)
33845
- );
33846
- const nothingToDo = status === "applied" ? `${key} was already applied; nothing to do.` : `${key} was not in the ledger; nothing to do.`;
33847
- if (status === "applied" ? recorded : !recorded) {
33848
- context.output.warn(nothingToDo);
33849
- context.output.result({ name: key, status, changed: false });
33850
- return;
33851
- }
33852
- await confirmOrAbort(
33853
- context,
33854
- 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.`
33855
- );
33856
- if (status === "applied" && ledger.length === 0)
33857
- await client.db.query(projectId, { sql: LEDGER_DDL });
33858
- const answer = await client.db.query(projectId, {
33859
- sql: status === "applied" ? LEDGER_INSERT : LEDGER_DELETE,
33860
- params: status === "applied" ? [key, key] : [key, withoutSuffix(key)]
33861
- });
33862
- const changed = (answer.statements[0]?.meta.changes ?? 0) > 0;
33863
- if (!changed) {
33864
- context.output.warn(nothingToDo);
33865
- context.output.result({ name: key, status, changed: false });
33866
- return;
33867
- }
33868
- context.output.result({ name: key, status, changed: true }, [
33869
- status === "applied" ? `\u2713 Marked ${key} as applied.` : `\u2713 Removed ${key} from the ledger.`
33870
- ]);
33871
- }
33872
- async function runMigrationUp(context) {
33873
- const client = await dataClient(context);
33874
- const projectId = await context.resolveProjectId();
33875
- const dir = migrationsDir(context);
33876
- const local = listMigrationFiles(dir);
33877
- const ledger = await appliedMigrations(client, projectId);
33878
- const pending = compareMigrations(local, ledger).filter((row) => row.status === "pending").map((row) => row.name);
33879
- if (pending.length === 0) {
33880
- context.output.result({ projectId, applied: [], pending: [] }, [
33881
- "No pending migrations."
33882
- ]);
33883
- return;
33884
- }
33885
- if (context.flags.dryRun) {
33886
- const problems2 = policyProblems(dir, pending);
33887
- context.output.result({ projectId, dryRun: true, pending, problems: problems2 }, [
33888
- `Pending migrations (${String(pending.length)}):`,
33889
- ...pending.map((name) => ` ${printable2(name)}`),
33890
- ...problems2.map(
33891
- (problem) => `${printable2(problem.migration)}: ${printable2(problem.message)}`
33892
- )
33893
- ]);
33894
- if (problems2.length > 0) process.exitCode = 1;
33895
- return;
33896
- }
33897
- context.output.log(`Pending migrations (${String(pending.length)}):`);
33898
- for (const name of pending) context.output.log(` ${printable2(name)}`);
33899
- await confirmOrAbort(
33900
- context,
33901
- `Apply ${String(pending.length)} migration(s) to ${databaseName(dataEnvironment(context.flags))}? Each file is applied all-or-nothing; a failure stops the run.`
33902
- );
33903
- const { applied, warnings } = await applyMigrationFiles(
33904
- context,
33905
- client,
33906
- projectId,
33907
- dir,
33908
- pending
33909
- );
33910
- context.output.result({ projectId, applied, pending: [], warnings }, [
33911
- `\u2705 Applied ${String(applied.length)} migration(s).`
33912
- ]);
33913
- }
33914
-
33915
34671
  // src/functions/retry.ts
33916
34672
  var FUNCTION_RETRIES = 3;
33917
34673
  var FUNCTION_BUSY_WAIT_MS = 5e3;
@@ -34196,7 +34952,7 @@ async function deployFunctions(options) {
34196
34952
  if (!PER_FUNCTION_CODES.has(error51.code))
34197
34953
  throw withProgress(error51, deployed, failures);
34198
34954
  failures.push({ name, code: error51.code, message: error51.message });
34199
- io.log(`\u2717 ${name}: ${printable2(error51.message)}`);
34955
+ io.log(`\u2717 ${name}: ${printable(error51.message)}`);
34200
34956
  }
34201
34957
  }
34202
34958
  if (failures.length > 0)
@@ -34491,7 +35247,7 @@ async function deployDirectory(sourceDir, options, log2) {
34491
35247
  const set2 = missing.filter((key) => isNaroSystemName(key));
34492
35248
  missing = missing.filter((key) => !isNaroSystemName(key));
34493
35249
  log2(
34494
- `\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`
34495
35251
  );
34496
35252
  }
34497
35253
  }
@@ -34500,6 +35256,7 @@ async function deployDirectory(sourceDir, options, log2) {
34500
35256
  log2(
34501
35257
  `\u25B8 ${String(missing.length)} env var(s) needed, not set on this project: ${missing.join(", ")}`
34502
35258
  );
35259
+ for (const line of envServiceHints(missing)) log2(line);
34503
35260
  const canCollect = environment === void 0 || options.projectId;
34504
35261
  if (options.onMissingEnv && canCollect)
34505
35262
  collected = await options.onMissingEnv(missing);
@@ -34640,10 +35397,15 @@ function deploymentUrl(detail, ctx, production) {
34640
35397
  }
34641
35398
 
34642
35399
  export {
35400
+ parseDotenvDetailed,
35401
+ FUNCTION_INVOKE_METHODS,
35402
+ FUNCTION_INVOKE_MAX_RESPONSE_BYTES,
35403
+ FUNCTION_INVOKE_TIMEOUT_MS,
34643
35404
  isFunctionName,
34644
35405
  parseGitHubRemote,
34645
35406
  schemaMigrationTitle,
34646
35407
  projectPath,
35408
+ readCapped,
34647
35409
  ANALYTICS_FILTER_KEYS,
34648
35410
  NaroError,
34649
35411
  promoteTargetUnsupported,
@@ -34678,7 +35440,6 @@ export {
34678
35440
  cellText,
34679
35441
  renderRows,
34680
35442
  summarizeStatement,
34681
- parseDotenvDetailed,
34682
35443
  serializeDotenv,
34683
35444
  systemTablePredicate,
34684
35445
  MAX_SINGLE_UPLOAD_BYTES,
@@ -34686,18 +35447,12 @@ export {
34686
35447
  parseCacheControl,
34687
35448
  STORAGE_OBJECTS_TABLE,
34688
35449
  dataError,
34689
- SCHEMA_SQL,
34690
- tableNames,
34691
- snapshotFromRows,
34692
35450
  quoteIdent,
34693
35451
  commentText,
34694
35452
  quotePolicyName,
34695
35453
  NAMED_POLICY_TABLE,
34696
35454
  NAMED_POLICY_SQL,
34697
- loadPolicy,
34698
35455
  parsePolicyNode,
34699
- INDEX_SQL,
34700
- indexesFromRows,
34701
35456
  parsePolicyStatement,
34702
35457
  splitPolicyStatements,
34703
35458
  STORAGE_SUBQUERIES_SQL,
@@ -34710,8 +35465,7 @@ export {
34710
35465
  writeProjectLink,
34711
35466
  removeProjectLink,
34712
35467
  ensureGitignoreHasNaro,
34713
- countLintedTables,
34714
- lintSchema,
35468
+ lintDatabase,
34715
35469
  listMigrationFiles,
34716
35470
  nextMigrationFileName,
34717
35471
  pendingMigrationNames,
@@ -34733,9 +35487,6 @@ export {
34733
35487
  pullEnv,
34734
35488
  detectGitInfo,
34735
35489
  version2 as version,
34736
- BUNDLE_MESSAGES,
34737
- functionsFolder,
34738
- parseJsonc2 as parseJsonc,
34739
35490
  DATA_ENV_MESSAGES,
34740
35491
  dataEnvironment,
34741
35492
  dataClient,
@@ -34751,7 +35502,7 @@ export {
34751
35502
  migrationsDir,
34752
35503
  relativeToCwd,
34753
35504
  appliedMigrations,
34754
- printable2 as printable,
35505
+ printable,
34755
35506
  isPrintable,
34756
35507
  UNSHOWN_WARNING_LINE,
34757
35508
  warningLines,
@@ -34761,6 +35512,10 @@ export {
34761
35512
  applyMigrationFiles,
34762
35513
  runMigrationRepair,
34763
35514
  runMigrationUp,
35515
+ runDbLint,
35516
+ BUNDLE_MESSAGES,
35517
+ functionsFolder,
35518
+ parseJsonc2 as parseJsonc,
34764
35519
  defaultSleep,
34765
35520
  retryAfterSeconds,
34766
35521
  withFunctionRetries,