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.
- package/README.md +83 -18
- package/dist/args.d.ts +49 -0
- package/dist/{chunk-ALK4VTL2.js → chunk-R4XGSZQL.js} +1268 -513
- package/dist/chunk-X5TUBMEU.js +35536 -0
- package/dist/commands/confirm.d.ts +10 -0
- package/dist/commands/db-lint.d.ts +16 -0
- package/dist/deploy.d.ts +2 -0
- package/dist/deploy.js +5 -1
- package/dist/env-services.d.ts +44 -0
- package/dist/functions/read-capped.d.ts +1 -0
- package/dist/index.js +8901 -7750
- package/package.json +6 -6
|
@@ -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 (
|
|
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
|
|
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) => `
|
|
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) => `
|
|
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}.
|
|
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
|
|
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
|
|
16814
|
+
const project2 = Object.values(workspace?.projects ?? {}).find(
|
|
16730
16815
|
(entry) => entry.projectType === "application"
|
|
16731
16816
|
);
|
|
16732
|
-
const 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 ===
|
|
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
|
|
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>
|
|
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,
|
|
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 (
|
|
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
|
|
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 && !
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
"
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
`
|
|
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
|
|
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 = "
|
|
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 `
|
|
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
|
|
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
|
|
32502
|
-
import { join as
|
|
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) :
|
|
32527
|
-
const label2 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
32634
|
-
const file2 =
|
|
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(
|
|
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
|
|
32840
|
-
import { dirname as dirname9, isAbsolute as isAbsolute5, join as
|
|
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
|
|
32844
|
-
import { basename as basename4, dirname as dirname8, join as
|
|
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
|
|
32849
|
-
function
|
|
32850
|
-
return text3.replace(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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) =>
|
|
34207
|
+
var fromRoot = (projectRoot, path) => relative9(projectRoot, path).split(sep8).join("/");
|
|
33051
34208
|
function isInside(path, projectRoot) {
|
|
33052
|
-
const rel =
|
|
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(
|
|
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(
|
|
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 =
|
|
34238
|
+
const file2 = join30(current, "package.json");
|
|
33082
34239
|
if (existsSync9(file2)) {
|
|
33083
34240
|
try {
|
|
33084
|
-
const manifest2 = JSON.parse(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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}: ${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|