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