naro-cli 0.7.0 → 0.9.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 +312 -3
- package/dist/artifact-types.d.ts +1 -0
- package/dist/chunk-K3NQKI34.js +10 -0
- package/dist/{chunk-E2M6CQL7.js → chunk-Z757IOF3.js} +1615 -121
- package/dist/deploy.js +2 -1
- package/dist/index.js +2778 -296
- package/dist/stack-PDV3NPWC.js +117 -0
- package/package.json +6 -3
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
-
};
|
|
2
|
+
import {
|
|
3
|
+
__export
|
|
4
|
+
} from "./chunk-K3NQKI34.js";
|
|
7
5
|
|
|
8
6
|
// src/deploy.ts
|
|
9
7
|
import { existsSync as existsSync7 } from "fs";
|
|
@@ -284,6 +282,33 @@ function pathnameOf(url2) {
|
|
|
284
282
|
// ../../packages/core/src/internal-auth.ts
|
|
285
283
|
import { Buffer as Buffer2 } from "buffer";
|
|
286
284
|
|
|
285
|
+
// ../../packages/core/src/migration-names.ts
|
|
286
|
+
function normalizeMigrationName(name) {
|
|
287
|
+
return name.trim().toLowerCase().replace(/\.sql$/, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
288
|
+
}
|
|
289
|
+
function leadingMigrationNumber(name) {
|
|
290
|
+
const match = /^(\d+)/.exec(name);
|
|
291
|
+
return match ? BigInt(match[1] ?? "0") : null;
|
|
292
|
+
}
|
|
293
|
+
function highestMigrationNumber(names) {
|
|
294
|
+
let max = 0n;
|
|
295
|
+
for (const name of names) {
|
|
296
|
+
const number4 = leadingMigrationNumber(name);
|
|
297
|
+
if (number4 !== null && number4 > max) max = number4;
|
|
298
|
+
}
|
|
299
|
+
return max;
|
|
300
|
+
}
|
|
301
|
+
function numberedMigrationName(existing, title) {
|
|
302
|
+
const number4 = (highestMigrationNumber(existing) + 1n).toString().padStart(4, "0");
|
|
303
|
+
return `${number4}_${title}.sql`;
|
|
304
|
+
}
|
|
305
|
+
var MAX_SCHEMA_TITLE_LENGTH = 180;
|
|
306
|
+
function schemaMigrationTitle(operation) {
|
|
307
|
+
const op = typeof operation.op === "string" ? operation.op.replace(/[A-Z]/g, (letter) => `_${letter}`) : "";
|
|
308
|
+
const target = typeof operation.table === "string" ? operation.table : typeof operation.name === "string" ? operation.name : "";
|
|
309
|
+
return normalizeMigrationName(`schema ${op} ${target}`).slice(0, MAX_SCHEMA_TITLE_LENGTH).replace(/_+$/, "");
|
|
310
|
+
}
|
|
311
|
+
|
|
287
312
|
// ../../packages/core/src/paths.ts
|
|
288
313
|
function projectPath(teamSlug, projectSlug) {
|
|
289
314
|
return `/${teamSlug}/projects/${projectSlug}`;
|
|
@@ -2474,9 +2499,9 @@ var $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst,
|
|
|
2474
2499
|
});
|
|
2475
2500
|
};
|
|
2476
2501
|
});
|
|
2477
|
-
function handleCheckPropertyResult(result, payload,
|
|
2502
|
+
function handleCheckPropertyResult(result, payload, property) {
|
|
2478
2503
|
if (result.issues.length) {
|
|
2479
|
-
payload.issues.push(...prefixIssues(
|
|
2504
|
+
payload.issues.push(...prefixIssues(property, result.issues));
|
|
2480
2505
|
}
|
|
2481
2506
|
}
|
|
2482
2507
|
var $ZodCheckProperty = /* @__PURE__ */ $constructor("$ZodCheckProperty", (inst, def) => {
|
|
@@ -11303,10 +11328,10 @@ function _endsWith(suffix, params) {
|
|
|
11303
11328
|
});
|
|
11304
11329
|
}
|
|
11305
11330
|
// @__NO_SIDE_EFFECTS__
|
|
11306
|
-
function _property(
|
|
11331
|
+
function _property(property, schema, params) {
|
|
11307
11332
|
return new $ZodCheckProperty({
|
|
11308
11333
|
check: "property",
|
|
11309
|
-
property
|
|
11334
|
+
property,
|
|
11310
11335
|
schema,
|
|
11311
11336
|
...normalizeParams(params)
|
|
11312
11337
|
});
|
|
@@ -11403,28 +11428,28 @@ function _tuple(Class2, items, _paramsOrRest, _params) {
|
|
|
11403
11428
|
});
|
|
11404
11429
|
}
|
|
11405
11430
|
// @__NO_SIDE_EFFECTS__
|
|
11406
|
-
function _record(Class2, keyType,
|
|
11431
|
+
function _record(Class2, keyType, valueType, params) {
|
|
11407
11432
|
return new Class2({
|
|
11408
11433
|
type: "record",
|
|
11409
11434
|
keyType,
|
|
11410
|
-
valueType
|
|
11435
|
+
valueType,
|
|
11411
11436
|
...normalizeParams(params)
|
|
11412
11437
|
});
|
|
11413
11438
|
}
|
|
11414
11439
|
// @__NO_SIDE_EFFECTS__
|
|
11415
|
-
function _map(Class2, keyType,
|
|
11440
|
+
function _map(Class2, keyType, valueType, params) {
|
|
11416
11441
|
return new Class2({
|
|
11417
11442
|
type: "map",
|
|
11418
11443
|
keyType,
|
|
11419
|
-
valueType
|
|
11444
|
+
valueType,
|
|
11420
11445
|
...normalizeParams(params)
|
|
11421
11446
|
});
|
|
11422
11447
|
}
|
|
11423
11448
|
// @__NO_SIDE_EFFECTS__
|
|
11424
|
-
function _set(Class2,
|
|
11449
|
+
function _set(Class2, valueType, params) {
|
|
11425
11450
|
return new Class2({
|
|
11426
11451
|
type: "set",
|
|
11427
|
-
valueType
|
|
11452
|
+
valueType,
|
|
11428
11453
|
...normalizeParams(params)
|
|
11429
11454
|
});
|
|
11430
11455
|
}
|
|
@@ -13790,37 +13815,37 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
|
|
|
13790
13815
|
inst.keyType = def.keyType;
|
|
13791
13816
|
inst.valueType = def.valueType;
|
|
13792
13817
|
});
|
|
13793
|
-
function record(keyType,
|
|
13794
|
-
if (!
|
|
13818
|
+
function record(keyType, valueType, params) {
|
|
13819
|
+
if (!valueType || !valueType._zod) {
|
|
13795
13820
|
return new ZodRecord({
|
|
13796
13821
|
type: "record",
|
|
13797
13822
|
keyType: string2(),
|
|
13798
13823
|
valueType: keyType,
|
|
13799
|
-
...util_exports.normalizeParams(
|
|
13824
|
+
...util_exports.normalizeParams(valueType)
|
|
13800
13825
|
});
|
|
13801
13826
|
}
|
|
13802
13827
|
return new ZodRecord({
|
|
13803
13828
|
type: "record",
|
|
13804
13829
|
keyType,
|
|
13805
|
-
valueType
|
|
13830
|
+
valueType,
|
|
13806
13831
|
...util_exports.normalizeParams(params)
|
|
13807
13832
|
});
|
|
13808
13833
|
}
|
|
13809
|
-
function partialRecord(keyType,
|
|
13834
|
+
function partialRecord(keyType, valueType, params) {
|
|
13810
13835
|
const k = clone(keyType);
|
|
13811
13836
|
k._zod.values = void 0;
|
|
13812
13837
|
return new ZodRecord({
|
|
13813
13838
|
type: "record",
|
|
13814
13839
|
keyType: k,
|
|
13815
|
-
valueType
|
|
13840
|
+
valueType,
|
|
13816
13841
|
...util_exports.normalizeParams(params)
|
|
13817
13842
|
});
|
|
13818
13843
|
}
|
|
13819
|
-
function looseRecord(keyType,
|
|
13844
|
+
function looseRecord(keyType, valueType, params) {
|
|
13820
13845
|
return new ZodRecord({
|
|
13821
13846
|
type: "record",
|
|
13822
13847
|
keyType,
|
|
13823
|
-
valueType
|
|
13848
|
+
valueType,
|
|
13824
13849
|
mode: "loose",
|
|
13825
13850
|
...util_exports.normalizeParams(params)
|
|
13826
13851
|
});
|
|
@@ -13836,11 +13861,11 @@ var ZodMap = /* @__PURE__ */ $constructor("ZodMap", (inst, def) => {
|
|
|
13836
13861
|
inst.max = (...args) => inst.check(_maxSize(...args));
|
|
13837
13862
|
inst.size = (...args) => inst.check(_size(...args));
|
|
13838
13863
|
});
|
|
13839
|
-
function map(keyType,
|
|
13864
|
+
function map(keyType, valueType, params) {
|
|
13840
13865
|
return new ZodMap({
|
|
13841
13866
|
type: "map",
|
|
13842
13867
|
keyType,
|
|
13843
|
-
valueType
|
|
13868
|
+
valueType,
|
|
13844
13869
|
...util_exports.normalizeParams(params)
|
|
13845
13870
|
});
|
|
13846
13871
|
}
|
|
@@ -13853,10 +13878,10 @@ var ZodSet = /* @__PURE__ */ $constructor("ZodSet", (inst, def) => {
|
|
|
13853
13878
|
inst.max = (...args) => inst.check(_maxSize(...args));
|
|
13854
13879
|
inst.size = (...args) => inst.check(_size(...args));
|
|
13855
13880
|
});
|
|
13856
|
-
function set(
|
|
13881
|
+
function set(valueType, params) {
|
|
13857
13882
|
return new ZodSet({
|
|
13858
13883
|
type: "set",
|
|
13859
|
-
valueType
|
|
13884
|
+
valueType,
|
|
13860
13885
|
...util_exports.normalizeParams(params)
|
|
13861
13886
|
});
|
|
13862
13887
|
}
|
|
@@ -14828,12 +14853,15 @@ var RESERVED_SUBDOMAINS = [
|
|
|
14828
14853
|
"support",
|
|
14829
14854
|
"www"
|
|
14830
14855
|
];
|
|
14856
|
+
var BACKEND_WORKER_NAME_PREFIX = "naro-backend-";
|
|
14831
14857
|
var SUBDOMAIN_PATTERN = /^[a-z0-9](-?[a-z0-9])*$/;
|
|
14832
14858
|
var SUBDOMAIN_MAX_LENGTH = 54;
|
|
14833
14859
|
function subdomainProblem(value) {
|
|
14834
14860
|
if (value.length < 1 || value.length > SUBDOMAIN_MAX_LENGTH) return "invalid";
|
|
14835
14861
|
if (!SUBDOMAIN_PATTERN.test(value)) return "invalid";
|
|
14836
14862
|
if (value.endsWith("-preview")) return "reserved";
|
|
14863
|
+
if (value === BACKEND_WORKER_NAME_PREFIX.slice(0, -1) || value.startsWith(BACKEND_WORKER_NAME_PREFIX))
|
|
14864
|
+
return "reserved";
|
|
14837
14865
|
if (RESERVED_SUBDOMAINS.includes(value))
|
|
14838
14866
|
return "reserved";
|
|
14839
14867
|
return null;
|
|
@@ -15663,6 +15691,67 @@ var FRAMEWORK_PRESETS = {
|
|
|
15663
15691
|
spaFallback: false,
|
|
15664
15692
|
cachePath: ".next/cache"
|
|
15665
15693
|
},
|
|
15694
|
+
// The presets below build a static site. Each framework can also render on
|
|
15695
|
+
// a server; detectFramework refuses those setups by name instead of
|
|
15696
|
+
// shipping half an app (see SERVER_ONLY and staticBuildProblem).
|
|
15697
|
+
astro: {
|
|
15698
|
+
id: "astro",
|
|
15699
|
+
detectDependency: "astro",
|
|
15700
|
+
defaultBuildScript: "build",
|
|
15701
|
+
defaultOutputDirectory: "dist",
|
|
15702
|
+
needsServerRuntime: false,
|
|
15703
|
+
spaFallback: false,
|
|
15704
|
+
cachePath: "node_modules/.astro"
|
|
15705
|
+
},
|
|
15706
|
+
sveltekit: {
|
|
15707
|
+
id: "sveltekit",
|
|
15708
|
+
detectDependency: "@sveltejs/kit",
|
|
15709
|
+
defaultBuildScript: "build",
|
|
15710
|
+
// adapter-static's default `pages` directory.
|
|
15711
|
+
defaultOutputDirectory: "build",
|
|
15712
|
+
needsServerRuntime: false,
|
|
15713
|
+
spaFallback: false,
|
|
15714
|
+
cachePath: null
|
|
15715
|
+
},
|
|
15716
|
+
nuxt: {
|
|
15717
|
+
id: "nuxt",
|
|
15718
|
+
detectDependency: "nuxt",
|
|
15719
|
+
// `nuxt generate` prerenders every route; `nuxt build` would need Nitro.
|
|
15720
|
+
defaultBuildScript: "generate",
|
|
15721
|
+
defaultOutputDirectory: ".output/public",
|
|
15722
|
+
needsServerRuntime: false,
|
|
15723
|
+
spaFallback: false,
|
|
15724
|
+
cachePath: "node_modules/.cache"
|
|
15725
|
+
},
|
|
15726
|
+
angular: {
|
|
15727
|
+
id: "angular",
|
|
15728
|
+
detectDependency: "@angular/core",
|
|
15729
|
+
defaultBuildScript: "build",
|
|
15730
|
+
// Documentation only: the real path is read from angular.json by
|
|
15731
|
+
// angularOutput, which never falls back to this string.
|
|
15732
|
+
defaultOutputDirectory: "dist/<project>/browser",
|
|
15733
|
+
needsServerRuntime: false,
|
|
15734
|
+
spaFallback: true,
|
|
15735
|
+
cachePath: ".angular/cache"
|
|
15736
|
+
},
|
|
15737
|
+
docusaurus: {
|
|
15738
|
+
id: "docusaurus",
|
|
15739
|
+
detectDependency: "@docusaurus/core",
|
|
15740
|
+
defaultBuildScript: "build",
|
|
15741
|
+
defaultOutputDirectory: "build",
|
|
15742
|
+
needsServerRuntime: false,
|
|
15743
|
+
spaFallback: false,
|
|
15744
|
+
cachePath: "node_modules/.cache"
|
|
15745
|
+
},
|
|
15746
|
+
gatsby: {
|
|
15747
|
+
id: "gatsby",
|
|
15748
|
+
detectDependency: "gatsby",
|
|
15749
|
+
defaultBuildScript: "build",
|
|
15750
|
+
defaultOutputDirectory: "public",
|
|
15751
|
+
needsServerRuntime: false,
|
|
15752
|
+
spaFallback: false,
|
|
15753
|
+
cachePath: ".cache"
|
|
15754
|
+
},
|
|
15666
15755
|
vite: {
|
|
15667
15756
|
id: "vite",
|
|
15668
15757
|
detectDependency: "vite",
|
|
@@ -15691,10 +15780,36 @@ var FRAMEWORK_PRESETS = {
|
|
|
15691
15780
|
cachePath: null
|
|
15692
15781
|
}
|
|
15693
15782
|
};
|
|
15694
|
-
var DETECTION_ORDER = [
|
|
15783
|
+
var DETECTION_ORDER = [
|
|
15784
|
+
"next",
|
|
15785
|
+
"nuxt",
|
|
15786
|
+
"astro",
|
|
15787
|
+
"sveltekit",
|
|
15788
|
+
"angular",
|
|
15789
|
+
"docusaurus",
|
|
15790
|
+
"gatsby",
|
|
15791
|
+
"vite",
|
|
15792
|
+
"cra"
|
|
15793
|
+
];
|
|
15794
|
+
var SERVER_ONLY = [
|
|
15795
|
+
{ dependency: "@tanstack/react-start", name: "TanStack Start" },
|
|
15796
|
+
{ dependency: "@tanstack/solid-start", name: "TanStack Start" },
|
|
15797
|
+
{ dependency: "@remix-run/dev", name: "Remix" },
|
|
15798
|
+
{ dependency: "@react-router/dev", name: "React Router (framework mode)" },
|
|
15799
|
+
{ dependency: "@solidjs/start", name: "SolidStart" },
|
|
15800
|
+
{ dependency: "@builder.io/qwik-city", name: "Qwik City" },
|
|
15801
|
+
{ dependency: "@analogjs/platform", name: "Analog" }
|
|
15802
|
+
];
|
|
15803
|
+
var SERVER_RUNTIME_HINT = "Naro deploys Next.js, static sites and single-page apps; server rendering for other frameworks is not supported yet.";
|
|
15695
15804
|
var FRAMEWORK_LABELS = {
|
|
15696
15805
|
next: "Next.js",
|
|
15697
15806
|
"next-export": "Next.js static export",
|
|
15807
|
+
astro: "Astro",
|
|
15808
|
+
sveltekit: "SvelteKit",
|
|
15809
|
+
nuxt: "Nuxt",
|
|
15810
|
+
angular: "Angular",
|
|
15811
|
+
docusaurus: "Docusaurus",
|
|
15812
|
+
gatsby: "Gatsby",
|
|
15698
15813
|
vite: "Vite",
|
|
15699
15814
|
cra: "Create React App",
|
|
15700
15815
|
static: "static files"
|
|
@@ -15705,8 +15820,7 @@ function dependencyVersion(packageJson, name) {
|
|
|
15705
15820
|
}
|
|
15706
15821
|
var STATIC_EXPORT = /(?:^|[\s,{;])output\s*:\s*(["'`])export\1/;
|
|
15707
15822
|
function declaresStaticExport(source) {
|
|
15708
|
-
|
|
15709
|
-
return STATIC_EXPORT.test(code);
|
|
15823
|
+
return STATIC_EXPORT.test(stripComments(source));
|
|
15710
15824
|
}
|
|
15711
15825
|
async function usesStaticExport(root) {
|
|
15712
15826
|
for (const name of NEXT_CONFIG_FILES) {
|
|
@@ -15718,6 +15832,102 @@ async function usesStaticExport(root) {
|
|
|
15718
15832
|
}
|
|
15719
15833
|
return false;
|
|
15720
15834
|
}
|
|
15835
|
+
var ASTRO_CONFIG_FILES = [
|
|
15836
|
+
"astro.config.mjs",
|
|
15837
|
+
"astro.config.js",
|
|
15838
|
+
"astro.config.ts",
|
|
15839
|
+
"astro.config.mts",
|
|
15840
|
+
"astro.config.cjs"
|
|
15841
|
+
];
|
|
15842
|
+
var SVELTE_CONFIG_FILES = [
|
|
15843
|
+
"svelte.config.js",
|
|
15844
|
+
"svelte.config.mjs",
|
|
15845
|
+
"svelte.config.ts"
|
|
15846
|
+
];
|
|
15847
|
+
var VITE_CONFIG_FILES = [
|
|
15848
|
+
"vite.config.ts",
|
|
15849
|
+
"vite.config.js",
|
|
15850
|
+
"vite.config.mjs",
|
|
15851
|
+
"vite.config.mts"
|
|
15852
|
+
];
|
|
15853
|
+
var ASTRO_ADAPTER = /^@astrojs\/(cloudflare|node|vercel|netlify|deno)$/;
|
|
15854
|
+
async function readFirst(root, names) {
|
|
15855
|
+
for (const name of names) {
|
|
15856
|
+
try {
|
|
15857
|
+
return await readFile6(join7(root, name), "utf8");
|
|
15858
|
+
} catch {
|
|
15859
|
+
continue;
|
|
15860
|
+
}
|
|
15861
|
+
}
|
|
15862
|
+
return null;
|
|
15863
|
+
}
|
|
15864
|
+
function stripComments(source) {
|
|
15865
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
15866
|
+
}
|
|
15867
|
+
async function staticBuildProblem(root, id, packageJson) {
|
|
15868
|
+
const has = (name) => dependencyVersion(packageJson, name) !== null;
|
|
15869
|
+
const dependencies = [
|
|
15870
|
+
...Object.keys(packageJson.dependencies ?? {}),
|
|
15871
|
+
...Object.keys(packageJson.devDependencies ?? {})
|
|
15872
|
+
];
|
|
15873
|
+
switch (id) {
|
|
15874
|
+
case "sveltekit": {
|
|
15875
|
+
const configs = await Promise.all(
|
|
15876
|
+
[...SVELTE_CONFIG_FILES, ...VITE_CONFIG_FILES].map(
|
|
15877
|
+
(name) => readFirst(root, [name])
|
|
15878
|
+
)
|
|
15879
|
+
);
|
|
15880
|
+
const usesStatic = configs.some(
|
|
15881
|
+
(source) => source !== null && stripComments(source).includes("@sveltejs/adapter-static")
|
|
15882
|
+
);
|
|
15883
|
+
return usesStatic ? null : "SvelteKit deploys on Naro with @sveltejs/adapter-static only (install it, use it as the adapter in svelte.config.js or vite.config.ts, and prerender your pages).";
|
|
15884
|
+
}
|
|
15885
|
+
case "astro": {
|
|
15886
|
+
const adapter = dependencies.find((name) => ASTRO_ADAPTER.test(name));
|
|
15887
|
+
if (adapter)
|
|
15888
|
+
return `This Astro project uses the ${adapter} adapter, which renders on a server. Remove it to deploy the static build.`;
|
|
15889
|
+
const config2 = await readFirst(root, ASTRO_CONFIG_FILES);
|
|
15890
|
+
if (config2 && /(?:^|[\s,{;])output\s*:\s*(["'`])server\1/.test(stripComments(config2)))
|
|
15891
|
+
return 'This Astro project sets output: "server". Use the default static output to deploy on Naro.';
|
|
15892
|
+
return null;
|
|
15893
|
+
}
|
|
15894
|
+
case "angular":
|
|
15895
|
+
return has("@angular/ssr") ? "This Angular project uses @angular/ssr (server rendering). Remove it to deploy the browser build." : null;
|
|
15896
|
+
default:
|
|
15897
|
+
return null;
|
|
15898
|
+
}
|
|
15899
|
+
}
|
|
15900
|
+
async function angularOutput(root) {
|
|
15901
|
+
const source = await readFirst(root, ["angular.json"]);
|
|
15902
|
+
let workspace = null;
|
|
15903
|
+
try {
|
|
15904
|
+
workspace = source ? JSON.parse(source) : null;
|
|
15905
|
+
} catch {
|
|
15906
|
+
workspace = null;
|
|
15907
|
+
}
|
|
15908
|
+
const project = Object.values(workspace?.projects ?? {}).find(
|
|
15909
|
+
(entry) => entry.projectType === "application"
|
|
15910
|
+
);
|
|
15911
|
+
const build = project?.architect?.build;
|
|
15912
|
+
const outputPath = build?.options?.outputPath;
|
|
15913
|
+
const legacy = build?.builder?.endsWith(":browser") ?? false;
|
|
15914
|
+
const clean = (path) => path.replace(/^\.?\/+/, "").replace(/\/+$/, "");
|
|
15915
|
+
if (typeof outputPath === "string")
|
|
15916
|
+
return legacy ? clean(outputPath) : `${clean(outputPath)}/browser`;
|
|
15917
|
+
if (outputPath && typeof outputPath.base === "string")
|
|
15918
|
+
return `${clean(outputPath.base)}/${outputPath.browser ?? "browser"}`.replace(
|
|
15919
|
+
/\/$/,
|
|
15920
|
+
""
|
|
15921
|
+
);
|
|
15922
|
+
const name = Object.entries(workspace?.projects ?? {}).find(
|
|
15923
|
+
([, entry]) => entry === project
|
|
15924
|
+
)?.[0];
|
|
15925
|
+
if (name) return `dist/${name}/browser`;
|
|
15926
|
+
throw new DeploymentError(
|
|
15927
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15928
|
+
"Could not find the Angular application's output path in angular.json. Set the output directory in the project settings."
|
|
15929
|
+
);
|
|
15930
|
+
}
|
|
15721
15931
|
async function readPackageJson(root) {
|
|
15722
15932
|
let source;
|
|
15723
15933
|
try {
|
|
@@ -15754,6 +15964,14 @@ async function detectFramework(root, overrides = {}) {
|
|
|
15754
15964
|
} else if (!packageJson) {
|
|
15755
15965
|
preset = FRAMEWORK_PRESETS.static;
|
|
15756
15966
|
} else {
|
|
15967
|
+
const serverOnly = SERVER_ONLY.find(
|
|
15968
|
+
({ dependency }) => dependencyVersion(packageJson, dependency) !== null
|
|
15969
|
+
);
|
|
15970
|
+
if (serverOnly)
|
|
15971
|
+
throw new DeploymentError(
|
|
15972
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15973
|
+
`${serverOnly.name} needs a server runtime. ${SERVER_RUNTIME_HINT}`
|
|
15974
|
+
);
|
|
15757
15975
|
const detected = DETECTION_ORDER.map((id) => FRAMEWORK_PRESETS[id]).find(
|
|
15758
15976
|
(candidate) => candidate.detectDependency !== null && dependencyVersion(packageJson, candidate.detectDependency) !== null
|
|
15759
15977
|
);
|
|
@@ -15766,6 +15984,14 @@ async function detectFramework(root, overrides = {}) {
|
|
|
15766
15984
|
}
|
|
15767
15985
|
if (preset.id === "next" && await usesStaticExport(root))
|
|
15768
15986
|
preset = FRAMEWORK_PRESETS["next-export"];
|
|
15987
|
+
if (!overrides.framework && packageJson) {
|
|
15988
|
+
const problem = await staticBuildProblem(root, preset.id, packageJson);
|
|
15989
|
+
if (problem)
|
|
15990
|
+
throw new DeploymentError(
|
|
15991
|
+
"FRAMEWORK_DETECTION_FAILED",
|
|
15992
|
+
`${problem} ${SERVER_RUNTIME_HINT}`
|
|
15993
|
+
);
|
|
15994
|
+
}
|
|
15769
15995
|
const buildScript = overrides.buildCommand ?? preset.defaultBuildScript;
|
|
15770
15996
|
if (buildScript !== null) {
|
|
15771
15997
|
if (!packageJson)
|
|
@@ -15783,7 +16009,7 @@ async function detectFramework(root, overrides = {}) {
|
|
|
15783
16009
|
return {
|
|
15784
16010
|
preset,
|
|
15785
16011
|
buildScript,
|
|
15786
|
-
outputDirectory: overrides.outputDirectory ?? preset.defaultOutputDirectory,
|
|
16012
|
+
outputDirectory: overrides.outputDirectory ?? (preset.id === "angular" ? await angularOutput(root) : preset.defaultOutputDirectory),
|
|
15787
16013
|
description: version2 ? `${FRAMEWORK_LABELS[preset.id]} ${version2}` : FRAMEWORK_LABELS[preset.id],
|
|
15788
16014
|
dependencyRange: version2
|
|
15789
16015
|
};
|
|
@@ -17573,12 +17799,21 @@ var LocalBuildExecutor = class {
|
|
|
17573
17799
|
checkout,
|
|
17574
17800
|
input.rootDirectory
|
|
17575
17801
|
);
|
|
17802
|
+
const stacks = await import("./stack-PDV3NPWC.js");
|
|
17803
|
+
const stack = await (this.dependencies.detectStack ?? stacks.detectStack)(
|
|
17804
|
+
appRoot
|
|
17805
|
+
).catch(() => null);
|
|
17806
|
+
if (stack) await observer.onMetadata?.({ framework: stack });
|
|
17576
17807
|
const resolved = await this.dependencies.detectFramework(appRoot, {
|
|
17577
17808
|
framework: input.framework ?? null,
|
|
17578
17809
|
buildCommand: input.buildCommand ?? null,
|
|
17579
17810
|
outputDirectory: input.outputDirectory ?? null
|
|
17580
17811
|
});
|
|
17581
17812
|
await info2(observer, "CLONING", `Detected ${resolved.description}`);
|
|
17813
|
+
if (!stack)
|
|
17814
|
+
await observer.onMetadata?.({
|
|
17815
|
+
framework: stacks.stackForPreset(resolved.preset.id)
|
|
17816
|
+
});
|
|
17582
17817
|
const isNext = resolved.preset.id === "next";
|
|
17583
17818
|
if (isNext && resolved.dependencyRange) {
|
|
17584
17819
|
const declared = declaredNextMajor(resolved.dependencyRange);
|
|
@@ -17934,6 +18169,12 @@ async function errorFromResponse(response, context) {
|
|
|
17934
18169
|
const serverMessage = typeof body?.error?.message === "string" ? body.error.message : null;
|
|
17935
18170
|
const code = serverCode ?? (response.status === 401 ? "UNAUTHORIZED" : response.status === 404 ? "NOT_FOUND" : response.status === 429 ? "RATE_LIMITED" : "INTERNAL_ERROR");
|
|
17936
18171
|
const details = {};
|
|
18172
|
+
const serverDetails = body?.error?.details;
|
|
18173
|
+
if (typeof serverDetails === "object" && serverDetails !== null && !Array.isArray(serverDetails))
|
|
18174
|
+
Object.assign(details, serverDetails);
|
|
18175
|
+
const retryAfter = response.headers.get("retry-after")?.trim() ?? "";
|
|
18176
|
+
if (response.status === 429 && /^\d{1,9}$/.test(retryAfter))
|
|
18177
|
+
details.retryAfter = Number(retryAfter);
|
|
17937
18178
|
if (body?.error?.fields !== void 0) details.fields = body.error.fields;
|
|
17938
18179
|
if (typeof body?.error?.installUrl === "string")
|
|
17939
18180
|
details.installUrl = body.error.installUrl;
|
|
@@ -18357,6 +18598,21 @@ var TERMINAL_DEPLOYMENT_STATUSES = [
|
|
|
18357
18598
|
function isTerminalStatus(status) {
|
|
18358
18599
|
return TERMINAL_DEPLOYMENT_STATUSES.includes(status);
|
|
18359
18600
|
}
|
|
18601
|
+
var STALLED_GRACE_MS = 2 * 6e4;
|
|
18602
|
+
function isStalledBackend(info3, now = Date.now()) {
|
|
18603
|
+
if (typeof info3.stalled === "boolean") return info3.stalled;
|
|
18604
|
+
if (info3.status !== "provisioning") return false;
|
|
18605
|
+
if (info3.leaseExpiresAt === null) return true;
|
|
18606
|
+
const until = Date.parse(info3.leaseExpiresAt);
|
|
18607
|
+
return Number.isNaN(until) || until + STALLED_GRACE_MS <= now;
|
|
18608
|
+
}
|
|
18609
|
+
var POLICY_RULES = [
|
|
18610
|
+
"none",
|
|
18611
|
+
"public",
|
|
18612
|
+
"authenticated",
|
|
18613
|
+
"owner"
|
|
18614
|
+
];
|
|
18615
|
+
var POLICY_ACTIONS = ["select", "insert", "update", "delete"];
|
|
18360
18616
|
function projectInfo(row) {
|
|
18361
18617
|
return {
|
|
18362
18618
|
id: row.id,
|
|
@@ -18414,6 +18670,7 @@ var NaroClient = class {
|
|
|
18414
18670
|
domains;
|
|
18415
18671
|
logs;
|
|
18416
18672
|
db;
|
|
18673
|
+
backend;
|
|
18417
18674
|
constructor(options) {
|
|
18418
18675
|
this.baseUrl = (options.baseUrl ?? controlPlaneUrl()).replace(/\/$/, "");
|
|
18419
18676
|
this.getAccessToken = options.getAccessToken;
|
|
@@ -18427,9 +18684,11 @@ var NaroClient = class {
|
|
|
18427
18684
|
this.domains = new DomainsApi(this);
|
|
18428
18685
|
this.logs = new LogsApi(this);
|
|
18429
18686
|
this.db = new DbApi(this);
|
|
18687
|
+
this.backend = new BackendApi(this);
|
|
18430
18688
|
}
|
|
18431
18689
|
async request(path, options) {
|
|
18432
18690
|
const token = await this.getAccessToken();
|
|
18691
|
+
const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
|
|
18433
18692
|
let response;
|
|
18434
18693
|
try {
|
|
18435
18694
|
response = await this.doFetch(`${this.baseUrl}${path}`, {
|
|
@@ -18439,13 +18698,13 @@ var NaroClient = class {
|
|
|
18439
18698
|
...options.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
18440
18699
|
},
|
|
18441
18700
|
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
|
|
18442
|
-
signal: AbortSignal.timeout(
|
|
18701
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
18443
18702
|
});
|
|
18444
18703
|
} catch (error51) {
|
|
18445
18704
|
if (error51 instanceof Error && (error51.name === "TimeoutError" || error51.name === "AbortError")) {
|
|
18446
18705
|
throw new NaroError(
|
|
18447
18706
|
"TIMEOUT",
|
|
18448
|
-
`${options.context} timed out after ${String(Math.round(
|
|
18707
|
+
`${options.context} timed out after ${String(Math.round(timeoutMs / 1e3))}s.`,
|
|
18449
18708
|
{ cause: error51 }
|
|
18450
18709
|
);
|
|
18451
18710
|
}
|
|
@@ -18833,6 +19092,9 @@ var LogsApi = class {
|
|
|
18833
19092
|
return body.entries;
|
|
18834
19093
|
}
|
|
18835
19094
|
};
|
|
19095
|
+
var EXPORT_FIRST_POLL_MS = 1e3;
|
|
19096
|
+
var EXPORT_MAX_POLL_MS = 5e3;
|
|
19097
|
+
var EXPORT_TIMEOUT_MS = 15 * 6e4;
|
|
18836
19098
|
var DbApi = class {
|
|
18837
19099
|
constructor(client) {
|
|
18838
19100
|
this.client = client;
|
|
@@ -18903,9 +19165,381 @@ var DbApi = class {
|
|
|
18903
19165
|
}
|
|
18904
19166
|
);
|
|
18905
19167
|
}
|
|
19168
|
+
/**
|
|
19169
|
+
* The current Time Travel bookmark, or the one `timestamp` (RFC 3339 with a
|
|
19170
|
+
* timezone, or Unix seconds) names. The server checks the range - the last
|
|
19171
|
+
* 30 days, not before the database existed - before asking Cloudflare.
|
|
19172
|
+
*/
|
|
19173
|
+
async bookmark(projectId, options = {}) {
|
|
19174
|
+
const query = options.timestamp === void 0 ? "" : `?timestamp=${encodeURIComponent(options.timestamp)}`;
|
|
19175
|
+
const body = await this.client.request(
|
|
19176
|
+
this.path(projectId, `/bookmark${query}`),
|
|
19177
|
+
{ context: "Read bookmark" }
|
|
19178
|
+
);
|
|
19179
|
+
return { bookmark: body.bookmark, timestamp: body.timestamp };
|
|
19180
|
+
}
|
|
19181
|
+
/**
|
|
19182
|
+
* Rolls the database back to a bookmark or a timestamp - rows, schema and
|
|
19183
|
+
* the migration ledger alike - and cancels queries in flight. Restoring to
|
|
19184
|
+
* `previousBookmark` undoes it.
|
|
19185
|
+
*/
|
|
19186
|
+
async restore(projectId, target) {
|
|
19187
|
+
const given = target;
|
|
19188
|
+
const body = await this.client.request(
|
|
19189
|
+
this.path(projectId, "/restore"),
|
|
19190
|
+
{
|
|
19191
|
+
method: "POST",
|
|
19192
|
+
body: {
|
|
19193
|
+
...given.bookmark !== void 0 ? { bookmark: given.bookmark } : {},
|
|
19194
|
+
...given.timestamp !== void 0 ? { timestamp: given.timestamp } : {}
|
|
19195
|
+
},
|
|
19196
|
+
context: "Restore database"
|
|
19197
|
+
}
|
|
19198
|
+
);
|
|
19199
|
+
return { bookmark: body.bookmark, previousBookmark: body.previousBookmark };
|
|
19200
|
+
}
|
|
19201
|
+
/**
|
|
19202
|
+
* One step of an export: starts it, or - with the first step's
|
|
19203
|
+
* `atBookmark` as `currentBookmark` - polls it. See `export` for the loop.
|
|
19204
|
+
*/
|
|
19205
|
+
async exportStep(projectId, options = {}) {
|
|
19206
|
+
const body = await this.client.request(
|
|
19207
|
+
this.path(projectId, "/export"),
|
|
19208
|
+
{
|
|
19209
|
+
method: "POST",
|
|
19210
|
+
// Only the keys given: the server validates each one it sees.
|
|
19211
|
+
body: {
|
|
19212
|
+
...options.schemaOnly !== void 0 ? { schemaOnly: options.schemaOnly } : {},
|
|
19213
|
+
...options.dataOnly !== void 0 ? { dataOnly: options.dataOnly } : {},
|
|
19214
|
+
...options.tables !== void 0 ? { tables: options.tables } : {},
|
|
19215
|
+
...options.currentBookmark !== void 0 ? { currentBookmark: options.currentBookmark } : {}
|
|
19216
|
+
},
|
|
19217
|
+
context: "Export database"
|
|
19218
|
+
}
|
|
19219
|
+
);
|
|
19220
|
+
return body.export;
|
|
19221
|
+
}
|
|
19222
|
+
/**
|
|
19223
|
+
* Exports the database and polls until the dump is ready. The server takes
|
|
19224
|
+
* one Cloudflare step per call - a big database runs for minutes, longer
|
|
19225
|
+
* than a Worker should wait - so the loop is here: 1 s, then 1.5x per poll
|
|
19226
|
+
* up to 5 s. Cloudflare holds every other query to the database while the
|
|
19227
|
+
* export runs.
|
|
19228
|
+
*
|
|
19229
|
+
* On TIMEOUT the export is still running at Cloudflare; nothing was
|
|
19230
|
+
* cancelled.
|
|
19231
|
+
*/
|
|
19232
|
+
async export(projectId, options = {}, wait = {}) {
|
|
19233
|
+
const timeoutMs = wait.timeoutMs ?? EXPORT_TIMEOUT_MS;
|
|
19234
|
+
const sleep2 = wait.sleep ?? ((ms) => new Promise((resolve10) => setTimeout(resolve10, ms)));
|
|
19235
|
+
const given = wait.pollIntervalMs;
|
|
19236
|
+
const firstPollMs = given !== void 0 && Number.isFinite(given) && given > 0 ? given : EXPORT_FIRST_POLL_MS;
|
|
19237
|
+
const maxPollMs = Math.max(EXPORT_MAX_POLL_MS, firstPollMs);
|
|
19238
|
+
const startedAt = Date.now();
|
|
19239
|
+
let step = await this.exportStep(projectId, options);
|
|
19240
|
+
wait.onProgress?.(step);
|
|
19241
|
+
const currentBookmark = step.atBookmark;
|
|
19242
|
+
const polled = {
|
|
19243
|
+
...options,
|
|
19244
|
+
...step.tables !== void 0 ? { tables: step.tables } : {}
|
|
19245
|
+
};
|
|
19246
|
+
let intervalMs = firstPollMs;
|
|
19247
|
+
while (step.status !== "complete") {
|
|
19248
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
19249
|
+
throw new NaroError(
|
|
19250
|
+
"TIMEOUT",
|
|
19251
|
+
`The export was still running after ${String(Math.round(timeoutMs / 6e4))} minutes. Cloudflare keeps it going; run naro db dump again later.`,
|
|
19252
|
+
{ details: { atBookmark: currentBookmark } }
|
|
19253
|
+
);
|
|
19254
|
+
}
|
|
19255
|
+
await sleep2(intervalMs);
|
|
19256
|
+
intervalMs = Math.min(intervalMs * 1.5, maxPollMs);
|
|
19257
|
+
step = await this.exportStep(projectId, { ...polled, currentBookmark });
|
|
19258
|
+
wait.onProgress?.(step);
|
|
19259
|
+
}
|
|
19260
|
+
return step;
|
|
19261
|
+
}
|
|
19262
|
+
/**
|
|
19263
|
+
* Records the remote schema as the already-applied migration `name` and
|
|
19264
|
+
* returns its SQL - a baseline for a database that has none. Refused with
|
|
19265
|
+
* DATABASE_HAS_MIGRATIONS when the ledger has rows, DATABASE_EMPTY when
|
|
19266
|
+
* there is no schema. The caller writes the file.
|
|
19267
|
+
*/
|
|
19268
|
+
async pull(projectId, input) {
|
|
19269
|
+
return this.client.request(this.path(projectId, "/pull"), {
|
|
19270
|
+
method: "POST",
|
|
19271
|
+
body: { name: input.name },
|
|
19272
|
+
context: "Pull schema"
|
|
19273
|
+
});
|
|
19274
|
+
}
|
|
19275
|
+
/**
|
|
19276
|
+
* Changes the schema without SQL (spec 2026-09-24 P2-4): the server
|
|
19277
|
+
* renders the DDL, checks it and applies it as ONE migration recorded in
|
|
19278
|
+
* `d1_migrations` — all operations or none. `dryRun` returns the SQL and
|
|
19279
|
+
* the name without applying anything. Save `sql` as `migrations/<name>` so
|
|
19280
|
+
* `naro db reset` re-applies it; a refused operation's index is in
|
|
19281
|
+
* `details.operation`.
|
|
19282
|
+
*/
|
|
19283
|
+
async schema(projectId, operations, options = {}) {
|
|
19284
|
+
return this.client.request(
|
|
19285
|
+
this.path(projectId, "/schema"),
|
|
19286
|
+
{
|
|
19287
|
+
method: "POST",
|
|
19288
|
+
// Only the keys given: the server validates each one it sees.
|
|
19289
|
+
body: {
|
|
19290
|
+
operations,
|
|
19291
|
+
...options.dryRun !== void 0 ? { dryRun: options.dryRun } : {},
|
|
19292
|
+
...options.name !== void 0 ? { name: options.name } : {}
|
|
19293
|
+
},
|
|
19294
|
+
context: "Change schema"
|
|
19295
|
+
}
|
|
19296
|
+
);
|
|
19297
|
+
}
|
|
19298
|
+
};
|
|
19299
|
+
var PROVISION_TIMEOUT_MS = 12e4;
|
|
19300
|
+
function backendInfo(body) {
|
|
19301
|
+
return {
|
|
19302
|
+
status: body.status,
|
|
19303
|
+
step: body.step,
|
|
19304
|
+
error: body.error,
|
|
19305
|
+
url: body.url,
|
|
19306
|
+
anonKey: body.anonKey,
|
|
19307
|
+
serviceKeyPrefix: body.serviceKeyPrefix,
|
|
19308
|
+
leaseExpiresAt: body.leaseExpiresAt ?? null,
|
|
19309
|
+
// Only a real verdict: without it `isStalledBackend` judges locally.
|
|
19310
|
+
...typeof body.stalled === "boolean" ? { stalled: body.stalled } : {},
|
|
19311
|
+
// A control plane from before the toggle could not turn a backend off:
|
|
19312
|
+
// whatever it has is on.
|
|
19313
|
+
enabled: typeof body.enabled === "boolean" ? body.enabled : body.status !== "none",
|
|
19314
|
+
disabledAt: body.disabledAt ?? null,
|
|
19315
|
+
turningOff: body.turningOff === true
|
|
19316
|
+
};
|
|
19317
|
+
}
|
|
19318
|
+
var BackendApi = class {
|
|
19319
|
+
constructor(client) {
|
|
19320
|
+
this.client = client;
|
|
19321
|
+
}
|
|
19322
|
+
path(projectId, suffix = "") {
|
|
19323
|
+
return `/api/projects/${encodeURIComponent(projectId)}/backend${suffix}`;
|
|
19324
|
+
}
|
|
19325
|
+
/**
|
|
19326
|
+
* `status: "none"` until the backend is first turned on; `enabled` says
|
|
19327
|
+
* whether it is on now.
|
|
19328
|
+
*/
|
|
19329
|
+
async info(projectId) {
|
|
19330
|
+
const body = await this.client.request(this.path(projectId), {
|
|
19331
|
+
context: "Read backend"
|
|
19332
|
+
});
|
|
19333
|
+
return backendInfo(body);
|
|
19334
|
+
}
|
|
19335
|
+
/**
|
|
19336
|
+
* Turns the backend on (toggle spec §2.3): the first time it provisions
|
|
19337
|
+
* the D1, the keys and the Naro worker; a backend that was turned off
|
|
19338
|
+
* comes back with the same keys — its worker is uploaded again, nothing
|
|
19339
|
+
* else is made anew. Safe to repeat, and a repair: a ready backend that
|
|
19340
|
+
* is on has its routes written again and its worker checked (no upload),
|
|
19341
|
+
* a failed run resumes at its step. A step that fails is not an
|
|
19342
|
+
* HTTP error — the answer is 200 with `status: "error"`, `step` and
|
|
19343
|
+
* `error`. While another run holds the project's lease this answers the
|
|
19344
|
+
* row as it stands (`provisioning`).
|
|
19345
|
+
*/
|
|
19346
|
+
async enable(projectId) {
|
|
19347
|
+
const body = await this.client.request(
|
|
19348
|
+
this.path(projectId, "/enable"),
|
|
19349
|
+
{
|
|
19350
|
+
method: "POST",
|
|
19351
|
+
context: "Enable backend",
|
|
19352
|
+
timeoutMs: PROVISION_TIMEOUT_MS
|
|
19353
|
+
}
|
|
19354
|
+
);
|
|
19355
|
+
return backendInfo(body);
|
|
19356
|
+
}
|
|
19357
|
+
/**
|
|
19358
|
+
* Turns the backend off (toggle spec §2.2): the dispatcher's routes to
|
|
19359
|
+
* the Naro worker go, so the app's sign-ins and data requests answer 404
|
|
19360
|
+
* from `/_naro/*` — in up to about 90 seconds, as the dispatcher's cache
|
|
19361
|
+
* ages out. The D1 with its data and users, the keys, the sign-in
|
|
19362
|
+
* settings and the worker stay; `enable` brings it back as it was.
|
|
19363
|
+
* Safe to repeat, and a repair: a backend already off has its routes
|
|
19364
|
+
* removed again — what finishes a turn-off that stopped half-way
|
|
19365
|
+
* (`turningOff`) — and keeps its first `disabledAt`. No backend at all
|
|
19366
|
+
* answers as it stands. `BACKEND_BUSY` while another run holds the lease.
|
|
19367
|
+
*/
|
|
19368
|
+
async disable(projectId) {
|
|
19369
|
+
const body = await this.client.request(
|
|
19370
|
+
this.path(projectId, "/disable"),
|
|
19371
|
+
{ method: "POST", context: "Disable backend" }
|
|
19372
|
+
);
|
|
19373
|
+
return backendInfo(body);
|
|
19374
|
+
}
|
|
19375
|
+
/**
|
|
19376
|
+
* `enable` under its first name: the control plane keeps
|
|
19377
|
+
* `POST …/backend/provision` as the same call.
|
|
19378
|
+
*/
|
|
19379
|
+
async provision(projectId) {
|
|
19380
|
+
const body = await this.client.request(
|
|
19381
|
+
this.path(projectId, "/provision"),
|
|
19382
|
+
{
|
|
19383
|
+
method: "POST",
|
|
19384
|
+
context: "Provision backend",
|
|
19385
|
+
timeoutMs: PROVISION_TIMEOUT_MS
|
|
19386
|
+
}
|
|
19387
|
+
);
|
|
19388
|
+
return backendInfo(body);
|
|
19389
|
+
}
|
|
19390
|
+
/** The anon key and the service key's prefix; BACKEND_NOT_READY before provisioning. */
|
|
19391
|
+
async keys(projectId) {
|
|
19392
|
+
const body = await this.client.request(
|
|
19393
|
+
this.path(projectId, "/keys"),
|
|
19394
|
+
{ context: "Read API keys" }
|
|
19395
|
+
);
|
|
19396
|
+
return { anonKey: body.anonKey, serviceKeyPrefix: body.serviceKeyPrefix };
|
|
19397
|
+
}
|
|
19398
|
+
/**
|
|
19399
|
+
* The whole service key. It skips every policy — servers only. A role
|
|
19400
|
+
* without `database.write` gets `FORBIDDEN` "Your role cannot reveal the
|
|
19401
|
+
* service key."
|
|
19402
|
+
*/
|
|
19403
|
+
async revealServiceKey(projectId) {
|
|
19404
|
+
const body = await this.client.request(
|
|
19405
|
+
this.path(projectId, "/keys?reveal=service"),
|
|
19406
|
+
{ context: "Reveal service key" }
|
|
19407
|
+
);
|
|
19408
|
+
return body.serviceKey;
|
|
19409
|
+
}
|
|
19410
|
+
/**
|
|
19411
|
+
* Replaces the live key of one kind, in two phases (spec §6 "(P1-3 리뷰
|
|
19412
|
+
* 반영)" I3): the worker takes the new hash first, and only then does the
|
|
19413
|
+
* database swap keys. There is no overlap — the old key stops working once
|
|
19414
|
+
* both are done. Only a `ready` backend that is on rotates
|
|
19415
|
+
* (`BACKEND_NOT_READY`, `BACKEND_BUSY`, or `BACKEND_OFF` while it is
|
|
19416
|
+
* turned off).
|
|
19417
|
+
*
|
|
19418
|
+
* The database decides the outcome. Whenever the new key ends up live —
|
|
19419
|
+
* even if a step after the swap failed — this resolves with the key and
|
|
19420
|
+
* `backend.status` `error`. Otherwise it throws `BACKEND_ROTATION_FAILED`
|
|
19421
|
+
* with no key; that usually leaves the old key working, but a 5xx, a
|
|
19422
|
+
* timeout or a lost connection is not proof either way: `keys()` (and
|
|
19423
|
+
* `revealServiceKey()`) show which key is live, and `enable` puts the
|
|
19424
|
+
* stored keys back on the worker. For a few seconds during any rotation
|
|
19425
|
+
* the worker can already refuse the old key.
|
|
19426
|
+
*/
|
|
19427
|
+
async rotateKey(projectId, kind) {
|
|
19428
|
+
const body = await this.client.request(this.path(projectId, "/keys/rotate"), {
|
|
19429
|
+
method: "POST",
|
|
19430
|
+
body: { kind },
|
|
19431
|
+
context: "Rotate API key",
|
|
19432
|
+
timeoutMs: PROVISION_TIMEOUT_MS
|
|
19433
|
+
});
|
|
19434
|
+
const backend = backendInfo(body);
|
|
19435
|
+
const key = kind === "anon" ? backend.anonKey : body.serviceKey;
|
|
19436
|
+
if (typeof key !== "string" || key === "")
|
|
19437
|
+
throw new NaroError(
|
|
19438
|
+
"INTERNAL_ERROR",
|
|
19439
|
+
"The rotation answer carried no new key. `naro backend keys` shows the key stored now (--reveal for the service key).",
|
|
19440
|
+
{ details: { backend } }
|
|
19441
|
+
);
|
|
19442
|
+
return { backend, key };
|
|
19443
|
+
}
|
|
19444
|
+
/**
|
|
19445
|
+
* The sign-in settings (spec P2-6 §12): email, Google and GitHub, the
|
|
19446
|
+
* allowed redirect origins, whether sign-in waits for a confirmed email,
|
|
19447
|
+
* and the callback URLs to register with the providers. Never a secret.
|
|
19448
|
+
*/
|
|
19449
|
+
async auth(projectId) {
|
|
19450
|
+
return this.client.request(
|
|
19451
|
+
this.path(projectId, "/auth"),
|
|
19452
|
+
{
|
|
19453
|
+
context: "Read sign-in settings"
|
|
19454
|
+
}
|
|
19455
|
+
);
|
|
19456
|
+
}
|
|
19457
|
+
/**
|
|
19458
|
+
* Changes the given settings and uploads the Naro worker again with them,
|
|
19459
|
+
* under the provisioning lease: `BACKEND_BUSY` while another run holds it
|
|
19460
|
+
* (nothing is saved), `BACKEND_NOT_READY` before the first provision,
|
|
19461
|
+
* `BACKEND_OFF` while the backend is turned off (nothing is saved),
|
|
19462
|
+
* `VERIFICATION_NEEDS_EMAIL` for a state where nobody could sign in. A
|
|
19463
|
+
* failed upload is not an error — `backend.status` says `error` and the
|
|
19464
|
+
* settings are saved; `enable` finishes. Two minutes, like enable.
|
|
19465
|
+
*/
|
|
19466
|
+
async setAuth(projectId, patch) {
|
|
19467
|
+
const answer = await this.client.request(
|
|
19468
|
+
this.path(projectId, "/auth"),
|
|
19469
|
+
{
|
|
19470
|
+
method: "PUT",
|
|
19471
|
+
body: patch,
|
|
19472
|
+
context: "Change sign-in settings",
|
|
19473
|
+
timeoutMs: PROVISION_TIMEOUT_MS
|
|
19474
|
+
}
|
|
19475
|
+
);
|
|
19476
|
+
return { auth: answer.auth, backend: backendInfo(answer.backend) };
|
|
19477
|
+
}
|
|
19478
|
+
/** Removes email or one provider; the same answer as `setAuth`. */
|
|
19479
|
+
async removeAuth(projectId, target) {
|
|
19480
|
+
const answer = await this.client.request(
|
|
19481
|
+
this.path(projectId, `/auth?target=${encodeURIComponent(target)}`),
|
|
19482
|
+
{
|
|
19483
|
+
method: "DELETE",
|
|
19484
|
+
context: "Change sign-in settings",
|
|
19485
|
+
timeoutMs: PROVISION_TIMEOUT_MS
|
|
19486
|
+
}
|
|
19487
|
+
);
|
|
19488
|
+
return { auth: answer.auth, backend: backendInfo(answer.backend) };
|
|
19489
|
+
}
|
|
19490
|
+
/** Every stored policy, by table name. Tables without one deny anon and signed-in requests. */
|
|
19491
|
+
async policies(projectId) {
|
|
19492
|
+
const body = await this.client.request(
|
|
19493
|
+
this.path(projectId, "/policies"),
|
|
19494
|
+
{ context: "List policies" }
|
|
19495
|
+
);
|
|
19496
|
+
return body.policies;
|
|
19497
|
+
}
|
|
19498
|
+
/**
|
|
19499
|
+
* Merges the given fields into the table's policy (what is left out keeps
|
|
19500
|
+
* its stored value, or `none`) and returns the whole row as stored, under
|
|
19501
|
+
* the table's own spelling. An `owner` rule needs `ownerColumn`.
|
|
19502
|
+
*/
|
|
19503
|
+
async setPolicy(projectId, table, patch) {
|
|
19504
|
+
const body = {};
|
|
19505
|
+
for (const key of [...POLICY_ACTIONS, "ownerColumn"])
|
|
19506
|
+
if (patch[key] !== void 0) body[key] = patch[key];
|
|
19507
|
+
const answer = await this.client.request(
|
|
19508
|
+
this.path(projectId, `/policies/${encodeURIComponent(table)}`),
|
|
19509
|
+
{ method: "PUT", body, context: "Set policy" }
|
|
19510
|
+
);
|
|
19511
|
+
return answer.policy;
|
|
19512
|
+
}
|
|
19513
|
+
/**
|
|
19514
|
+
* Deletes the table's policy, so it denies anon and signed-in requests
|
|
19515
|
+
* again. `POLICY_NOT_FOUND` when the table has none.
|
|
19516
|
+
*/
|
|
19517
|
+
async removePolicy(projectId, table) {
|
|
19518
|
+
const answer = await this.client.request(
|
|
19519
|
+
this.path(projectId, `/policies/${encodeURIComponent(table)}`),
|
|
19520
|
+
{ method: "DELETE", context: "Remove policy" }
|
|
19521
|
+
);
|
|
19522
|
+
return answer.policy;
|
|
19523
|
+
}
|
|
18906
19524
|
};
|
|
18907
19525
|
|
|
18908
19526
|
// ../../packages/sdk/src/database.ts
|
|
19527
|
+
var BOOKMARK_RE = /^[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{32}$/;
|
|
19528
|
+
var BOOKMARK_FORMAT_MESSAGE = "bookmark must look like 00000001-00000008-000050ed-272153b8520cfb93b49834d93a1a1ab3 (four hyphen-separated hex groups, 59 characters).";
|
|
19529
|
+
function isBookmark(value) {
|
|
19530
|
+
return typeof value === "string" && BOOKMARK_RE.test(value);
|
|
19531
|
+
}
|
|
19532
|
+
var RESTORE_OUTCOME_UNKNOWN_CODES = /* @__PURE__ */ new Set([
|
|
19533
|
+
"DATABASE_RESTORE_FAILED",
|
|
19534
|
+
"DATABASE_UNAVAILABLE",
|
|
19535
|
+
"TIMEOUT",
|
|
19536
|
+
"NETWORK_ERROR"
|
|
19537
|
+
]);
|
|
19538
|
+
function cellText(statement, row, column) {
|
|
19539
|
+
const index = statement.columns.indexOf(column);
|
|
19540
|
+
const value = index < 0 ? void 0 : row[index];
|
|
19541
|
+
return value === null || value === void 0 ? "" : String(value);
|
|
19542
|
+
}
|
|
18909
19543
|
var MAX_CELL_CHARS = 60;
|
|
18910
19544
|
function formatCell(value) {
|
|
18911
19545
|
if (value === null || value === void 0) return "NULL";
|
|
@@ -19099,62 +19733,115 @@ function hasConfig(dir, base2, evidence) {
|
|
|
19099
19733
|
}
|
|
19100
19734
|
return false;
|
|
19101
19735
|
}
|
|
19736
|
+
var SERVER_ONLY2 = [
|
|
19737
|
+
["@tanstack/react-start", "TanStack Start"],
|
|
19738
|
+
["@tanstack/solid-start", "TanStack Start"],
|
|
19739
|
+
["@remix-run/dev", "Remix"],
|
|
19740
|
+
["@react-router/dev", "React Router (framework mode)"],
|
|
19741
|
+
["@solidjs/start", "SolidStart"],
|
|
19742
|
+
["@builder.io/qwik-city", "Qwik City"],
|
|
19743
|
+
["@analogjs/platform", "Analog"]
|
|
19744
|
+
];
|
|
19745
|
+
var DETECTORS = [
|
|
19746
|
+
{
|
|
19747
|
+
framework: "nextjs",
|
|
19748
|
+
name: "Next.js",
|
|
19749
|
+
dependency: "next",
|
|
19750
|
+
config: "next.config",
|
|
19751
|
+
buildCommand: "next build",
|
|
19752
|
+
outputDirectory: ".next"
|
|
19753
|
+
},
|
|
19754
|
+
{
|
|
19755
|
+
framework: "nuxt",
|
|
19756
|
+
name: "Nuxt",
|
|
19757
|
+
dependency: "nuxt",
|
|
19758
|
+
config: "nuxt.config",
|
|
19759
|
+
buildCommand: "nuxt generate",
|
|
19760
|
+
outputDirectory: ".output/public"
|
|
19761
|
+
},
|
|
19762
|
+
{
|
|
19763
|
+
framework: "astro",
|
|
19764
|
+
name: "Astro",
|
|
19765
|
+
dependency: "astro",
|
|
19766
|
+
config: "astro.config",
|
|
19767
|
+
buildCommand: "astro build",
|
|
19768
|
+
outputDirectory: "dist"
|
|
19769
|
+
},
|
|
19770
|
+
{
|
|
19771
|
+
framework: "sveltekit",
|
|
19772
|
+
name: "SvelteKit",
|
|
19773
|
+
dependency: "@sveltejs/kit",
|
|
19774
|
+
buildCommand: "vite build",
|
|
19775
|
+
outputDirectory: "build"
|
|
19776
|
+
},
|
|
19777
|
+
{
|
|
19778
|
+
framework: "angular",
|
|
19779
|
+
name: "Angular",
|
|
19780
|
+
dependency: "@angular/core",
|
|
19781
|
+
buildCommand: "ng build",
|
|
19782
|
+
outputDirectory: "dist/<project>/browser"
|
|
19783
|
+
},
|
|
19784
|
+
{
|
|
19785
|
+
framework: "docusaurus",
|
|
19786
|
+
name: "Docusaurus",
|
|
19787
|
+
dependency: "@docusaurus/core",
|
|
19788
|
+
buildCommand: "docusaurus build",
|
|
19789
|
+
outputDirectory: "build"
|
|
19790
|
+
},
|
|
19791
|
+
{
|
|
19792
|
+
framework: "gatsby",
|
|
19793
|
+
name: "Gatsby",
|
|
19794
|
+
dependency: "gatsby",
|
|
19795
|
+
buildCommand: "gatsby build",
|
|
19796
|
+
outputDirectory: "public"
|
|
19797
|
+
},
|
|
19798
|
+
{
|
|
19799
|
+
framework: "vite",
|
|
19800
|
+
name: "Vite",
|
|
19801
|
+
dependency: "vite",
|
|
19802
|
+
config: "vite.config",
|
|
19803
|
+
buildCommand: "vite build",
|
|
19804
|
+
outputDirectory: "dist"
|
|
19805
|
+
},
|
|
19806
|
+
{
|
|
19807
|
+
framework: "react",
|
|
19808
|
+
name: "Create React App",
|
|
19809
|
+
dependency: "react-scripts",
|
|
19810
|
+
buildCommand: "react-scripts build",
|
|
19811
|
+
outputDirectory: "build"
|
|
19812
|
+
}
|
|
19813
|
+
];
|
|
19102
19814
|
function detectFramework2(dir) {
|
|
19103
19815
|
const evidence = [];
|
|
19104
19816
|
const pkg = readPackageJson2(dir);
|
|
19105
|
-
|
|
19106
|
-
|
|
19107
|
-
|
|
19108
|
-
|
|
19109
|
-
|
|
19110
|
-
framework: "nextjs",
|
|
19111
|
-
name: "Next.js",
|
|
19112
|
-
buildCommand: "next build",
|
|
19113
|
-
outputDirectory: ".next",
|
|
19114
|
-
evidence,
|
|
19115
|
-
deployable: true
|
|
19116
|
-
};
|
|
19117
|
-
}
|
|
19118
|
-
if (hasDependency(pkg, "nuxt") || hasConfig(dir, "nuxt.config", evidence)) {
|
|
19119
|
-
if (hasDependency(pkg, "nuxt")) evidence.push("package.json: nuxt");
|
|
19120
|
-
return {
|
|
19121
|
-
framework: "nuxt",
|
|
19122
|
-
name: "Nuxt",
|
|
19123
|
-
buildCommand: "nuxt build",
|
|
19124
|
-
outputDirectory: ".output",
|
|
19125
|
-
evidence,
|
|
19126
|
-
deployable: false
|
|
19127
|
-
};
|
|
19128
|
-
}
|
|
19129
|
-
if (hasDependency(pkg, "astro") || hasConfig(dir, "astro.config", evidence)) {
|
|
19130
|
-
if (hasDependency(pkg, "astro")) evidence.push("package.json: astro");
|
|
19817
|
+
const serverOnly = SERVER_ONLY2.find(
|
|
19818
|
+
([dependency]) => hasDependency(pkg, dependency)
|
|
19819
|
+
);
|
|
19820
|
+
if (serverOnly) {
|
|
19821
|
+
evidence.push(`package.json: ${serverOnly[0]}`);
|
|
19131
19822
|
return {
|
|
19132
|
-
framework: "
|
|
19133
|
-
name:
|
|
19134
|
-
buildCommand:
|
|
19135
|
-
outputDirectory:
|
|
19823
|
+
framework: "server-only",
|
|
19824
|
+
name: serverOnly[1],
|
|
19825
|
+
buildCommand: null,
|
|
19826
|
+
outputDirectory: null,
|
|
19136
19827
|
evidence,
|
|
19137
19828
|
deployable: false
|
|
19138
19829
|
};
|
|
19139
19830
|
}
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
evidence
|
|
19148
|
-
|
|
19149
|
-
};
|
|
19150
|
-
}
|
|
19151
|
-
if (hasDependency(pkg, "react-scripts")) {
|
|
19152
|
-
evidence.push("package.json: react-scripts");
|
|
19831
|
+
for (const detector of DETECTORS) {
|
|
19832
|
+
const byDependency = hasDependency(pkg, detector.dependency);
|
|
19833
|
+
const byConfig = !byDependency && detector.config ? hasConfig(dir, detector.config, evidence) : false;
|
|
19834
|
+
if (!byDependency && !byConfig) continue;
|
|
19835
|
+
if (byDependency) evidence.push(`package.json: ${detector.dependency}`);
|
|
19836
|
+
if (detector.framework === "nextjs") {
|
|
19837
|
+
if (existsSync3(join20(dir, "app"))) evidence.push("app/");
|
|
19838
|
+
if (existsSync3(join20(dir, "pages"))) evidence.push("pages/");
|
|
19839
|
+
}
|
|
19153
19840
|
return {
|
|
19154
|
-
framework:
|
|
19155
|
-
name:
|
|
19156
|
-
buildCommand:
|
|
19157
|
-
outputDirectory:
|
|
19841
|
+
framework: detector.framework,
|
|
19842
|
+
name: detector.name,
|
|
19843
|
+
buildCommand: detector.buildCommand,
|
|
19844
|
+
outputDirectory: detector.outputDirectory,
|
|
19158
19845
|
evidence,
|
|
19159
19846
|
deployable: true
|
|
19160
19847
|
};
|
|
@@ -19180,27 +19867,455 @@ function detectFramework2(dir) {
|
|
|
19180
19867
|
};
|
|
19181
19868
|
}
|
|
19182
19869
|
|
|
19870
|
+
// ../../packages/data-core/src/system-tables.ts
|
|
19871
|
+
var SYSTEM_TABLE_PREFIXES = [
|
|
19872
|
+
"_naro_",
|
|
19873
|
+
"_cf_",
|
|
19874
|
+
"sqlite_"
|
|
19875
|
+
];
|
|
19876
|
+
var SYSTEM_TABLE_NAMES = ["d1_migrations"];
|
|
19877
|
+
function asciiLower(text) {
|
|
19878
|
+
return text.replace(/[A-Z]+/g, (run) => run.toLowerCase());
|
|
19879
|
+
}
|
|
19880
|
+
function isSystemTable(name) {
|
|
19881
|
+
const lower = asciiLower(name);
|
|
19882
|
+
return SYSTEM_TABLE_PREFIXES.some((prefix) => lower.startsWith(prefix)) || SYSTEM_TABLE_NAMES.includes(lower);
|
|
19883
|
+
}
|
|
19884
|
+
function likeLiteral(text) {
|
|
19885
|
+
return text.replace(/[\\%_]/g, (char) => `\\${char}`).replace(/'/g, "''");
|
|
19886
|
+
}
|
|
19887
|
+
var COLUMN_REFERENCE = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$/;
|
|
19888
|
+
function systemTablePredicate(column = "name") {
|
|
19889
|
+
if (!COLUMN_REFERENCE.test(column))
|
|
19890
|
+
throw new Error(`Not a column reference: ${column}`);
|
|
19891
|
+
return [
|
|
19892
|
+
...SYSTEM_TABLE_PREFIXES.map(
|
|
19893
|
+
(prefix) => `${column} NOT LIKE '${likeLiteral(prefix)}%' ESCAPE '\\'`
|
|
19894
|
+
),
|
|
19895
|
+
...SYSTEM_TABLE_NAMES.map(
|
|
19896
|
+
(name) => `${column} NOT LIKE '${likeLiteral(name)}' ESCAPE '\\'`
|
|
19897
|
+
),
|
|
19898
|
+
`${column} NOT IN (SELECT name FROM pragma_table_list WHERE schema = 'main' AND type = 'shadow')`
|
|
19899
|
+
].join(" AND ");
|
|
19900
|
+
}
|
|
19901
|
+
var SYSTEM_TABLE_SQL_PREDICATE = systemTablePredicate("name");
|
|
19902
|
+
|
|
19903
|
+
// ../../packages/data-core/src/values.ts
|
|
19904
|
+
function isPlainObject2(value) {
|
|
19905
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
19906
|
+
return false;
|
|
19907
|
+
const prototype = Object.getPrototypeOf(value);
|
|
19908
|
+
return prototype === Object.prototype || prototype === null;
|
|
19909
|
+
}
|
|
19910
|
+
function isValue(value) {
|
|
19911
|
+
return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
19912
|
+
}
|
|
19913
|
+
|
|
19914
|
+
// ../../packages/data-core/src/query.ts
|
|
19915
|
+
var FILTER_OPERATORS = [
|
|
19916
|
+
"eq",
|
|
19917
|
+
"neq",
|
|
19918
|
+
"gt",
|
|
19919
|
+
"gte",
|
|
19920
|
+
"lt",
|
|
19921
|
+
"lte",
|
|
19922
|
+
"in",
|
|
19923
|
+
"is",
|
|
19924
|
+
"like",
|
|
19925
|
+
"ilike",
|
|
19926
|
+
// Phase 2-1: for a TEXT column holding a JSON array (spec P2-1 §2).
|
|
19927
|
+
"contains",
|
|
19928
|
+
"containedBy",
|
|
19929
|
+
"overlaps"
|
|
19930
|
+
];
|
|
19931
|
+
function isAuthUid(value) {
|
|
19932
|
+
return isPlainObject2(value) && Object.keys(value).length === 1 && Object.hasOwn(value, "auth") && value.auth === "uid";
|
|
19933
|
+
}
|
|
19934
|
+
var MAX_BODY_BYTES = 1024 * 1024;
|
|
19935
|
+
var MAX_FILTERS = 100;
|
|
19936
|
+
var MAX_FILTER_DEPTH = 4;
|
|
19937
|
+
var MAX_ARRAY_VALUES = 100;
|
|
19938
|
+
var MAX_ARRAY_CONDITIONS = 4;
|
|
19939
|
+
var ARRAY_OPERATORS = [
|
|
19940
|
+
"contains",
|
|
19941
|
+
"containedBy",
|
|
19942
|
+
"overlaps"
|
|
19943
|
+
];
|
|
19944
|
+
var MAX_PATTERN_LENGTH = 1024;
|
|
19945
|
+
var MAX_IDENTIFIER_LENGTH = 128;
|
|
19946
|
+
var Invalid = class extends Error {
|
|
19947
|
+
constructor(path, reason) {
|
|
19948
|
+
super(reason);
|
|
19949
|
+
this.path = path;
|
|
19950
|
+
this.reason = reason;
|
|
19951
|
+
}
|
|
19952
|
+
};
|
|
19953
|
+
function invalid(path, reason) {
|
|
19954
|
+
throw new Invalid(path, reason);
|
|
19955
|
+
}
|
|
19956
|
+
function own(object2, key) {
|
|
19957
|
+
return Object.hasOwn(object2, key) ? object2[key] : void 0;
|
|
19958
|
+
}
|
|
19959
|
+
function isOperator(value) {
|
|
19960
|
+
return FILTER_OPERATORS.includes(value);
|
|
19961
|
+
}
|
|
19962
|
+
function readObject(value, path) {
|
|
19963
|
+
if (!isPlainObject2(value)) invalid(path, "must be an object");
|
|
19964
|
+
return value;
|
|
19965
|
+
}
|
|
19966
|
+
function checkKeys(object2, allowed, path) {
|
|
19967
|
+
for (const key of Object.keys(object2)) {
|
|
19968
|
+
if (allowed.includes(key)) continue;
|
|
19969
|
+
if (longerThan(key, MAX_IDENTIFIER_LENGTH))
|
|
19970
|
+
invalid(path, "has a field that is not a known field");
|
|
19971
|
+
invalid(path ? `${path}.${key}` : key, "is not a known field");
|
|
19972
|
+
}
|
|
19973
|
+
}
|
|
19974
|
+
function readName(value, path) {
|
|
19975
|
+
if (typeof value !== "string" || value === "")
|
|
19976
|
+
invalid(path, "must be a non-empty string");
|
|
19977
|
+
if (longerThan(value, MAX_IDENTIFIER_LENGTH))
|
|
19978
|
+
invalid(
|
|
19979
|
+
path,
|
|
19980
|
+
`must be at most ${String(MAX_IDENTIFIER_LENGTH)} characters`
|
|
19981
|
+
);
|
|
19982
|
+
return value;
|
|
19983
|
+
}
|
|
19984
|
+
function readValue(value, path, auth = false) {
|
|
19985
|
+
if (auth && isAuthUid(value)) return { auth: "uid" };
|
|
19986
|
+
if (!isValue(value))
|
|
19987
|
+
invalid(
|
|
19988
|
+
path,
|
|
19989
|
+
auth ? 'must be a string, a finite number, a boolean, null or {"auth":"uid"}' : "must be a string, a finite number, a boolean or null"
|
|
19990
|
+
);
|
|
19991
|
+
return value;
|
|
19992
|
+
}
|
|
19993
|
+
function longerThan(text, max) {
|
|
19994
|
+
if (text.length <= max) return false;
|
|
19995
|
+
if (text.length > max * 2) return true;
|
|
19996
|
+
let count = 0;
|
|
19997
|
+
for (const _ of text) if (++count > max) return true;
|
|
19998
|
+
return false;
|
|
19999
|
+
}
|
|
20000
|
+
function endsWithLoneEscape(pattern) {
|
|
20001
|
+
let run = 0;
|
|
20002
|
+
for (let index = pattern.length - 1; index >= 0; index--) {
|
|
20003
|
+
if (pattern[index] !== "\\") break;
|
|
20004
|
+
run++;
|
|
20005
|
+
}
|
|
20006
|
+
return run % 2 === 1;
|
|
20007
|
+
}
|
|
20008
|
+
function readFilter(value, path, auth) {
|
|
20009
|
+
const object2 = readObject(value, path);
|
|
20010
|
+
checkKeys(object2, ["operator", "column", "value"], path);
|
|
20011
|
+
const operator = own(object2, "operator");
|
|
20012
|
+
if (!isOperator(operator))
|
|
20013
|
+
invalid(
|
|
20014
|
+
`${path}.operator`,
|
|
20015
|
+
`must be one of ${FILTER_OPERATORS.join(", ")}`
|
|
20016
|
+
);
|
|
20017
|
+
const column = readName(own(object2, "column"), `${path}.column`);
|
|
20018
|
+
if (!Object.hasOwn(object2, "value")) invalid(`${path}.value`, "is required");
|
|
20019
|
+
const raw = own(object2, "value");
|
|
20020
|
+
const valuePath = `${path}.value`;
|
|
20021
|
+
switch (operator) {
|
|
20022
|
+
case "in": {
|
|
20023
|
+
if (!Array.isArray(raw)) invalid(valuePath, "must be an array");
|
|
20024
|
+
const values = raw.map(
|
|
20025
|
+
(item, index) => readValue(item, `${valuePath}[${index}]`, auth)
|
|
20026
|
+
);
|
|
20027
|
+
return { operator, column, value: values };
|
|
20028
|
+
}
|
|
20029
|
+
case "is":
|
|
20030
|
+
if (raw !== null && raw !== true && raw !== false)
|
|
20031
|
+
invalid(valuePath, "must be null, true or false");
|
|
20032
|
+
return { operator, column, value: raw };
|
|
20033
|
+
case "like":
|
|
20034
|
+
case "ilike":
|
|
20035
|
+
if (typeof raw !== "string") invalid(valuePath, "must be a string");
|
|
20036
|
+
if (longerThan(raw, MAX_PATTERN_LENGTH))
|
|
20037
|
+
invalid(
|
|
20038
|
+
valuePath,
|
|
20039
|
+
`must be at most ${String(MAX_PATTERN_LENGTH)} characters`
|
|
20040
|
+
);
|
|
20041
|
+
if (raw.includes("\0"))
|
|
20042
|
+
invalid(valuePath, "must not contain a NUL character");
|
|
20043
|
+
if (endsWithLoneEscape(raw))
|
|
20044
|
+
invalid(valuePath, "must not end with an unescaped backslash");
|
|
20045
|
+
return { operator, column, value: raw };
|
|
20046
|
+
case "contains":
|
|
20047
|
+
case "containedBy":
|
|
20048
|
+
case "overlaps":
|
|
20049
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > MAX_ARRAY_VALUES || !raw.every(
|
|
20050
|
+
(item) => typeof item === "string" || typeof item === "number" && Number.isFinite(item) || auth && isAuthUid(item)
|
|
20051
|
+
))
|
|
20052
|
+
invalid(
|
|
20053
|
+
valuePath,
|
|
20054
|
+
auth ? `must be a non-empty array of strings, numbers and {"auth":"uid"} (at most ${String(MAX_ARRAY_VALUES)})` : `must be a non-empty array of strings and numbers (at most ${String(MAX_ARRAY_VALUES)})`
|
|
20055
|
+
);
|
|
20056
|
+
return {
|
|
20057
|
+
operator,
|
|
20058
|
+
column,
|
|
20059
|
+
value: raw.map(
|
|
20060
|
+
(item) => isAuthUid(item) ? { auth: "uid" } : item
|
|
20061
|
+
)
|
|
20062
|
+
};
|
|
20063
|
+
default:
|
|
20064
|
+
return { operator, column, value: readValue(raw, valuePath, auth) };
|
|
20065
|
+
}
|
|
20066
|
+
}
|
|
20067
|
+
var NODE_KEYS = ["operator", "or", "and", "not"];
|
|
20068
|
+
function readNode(value, path, depth, budget) {
|
|
20069
|
+
const object2 = readObject(value, path);
|
|
20070
|
+
const kinds = NODE_KEYS.filter((key) => Object.hasOwn(object2, key));
|
|
20071
|
+
const [kind] = kinds;
|
|
20072
|
+
if (kinds.length !== 1 || kind === void 0)
|
|
20073
|
+
invalid(path, `needs exactly one of ${NODE_KEYS.join(", ")}`);
|
|
20074
|
+
if (kind === "operator") {
|
|
20075
|
+
if (++budget.conditions > MAX_FILTERS)
|
|
20076
|
+
invalid(budget.path, `has more than ${String(MAX_FILTERS)} conditions`);
|
|
20077
|
+
const filter = readFilter(object2, path, budget.auth);
|
|
20078
|
+
if (ARRAY_OPERATORS.includes(filter.operator) && ++budget.arrays > MAX_ARRAY_CONDITIONS)
|
|
20079
|
+
invalid(
|
|
20080
|
+
budget.path,
|
|
20081
|
+
`has more than ${String(MAX_ARRAY_CONDITIONS)} ${ARRAY_OPERATORS.join("/")} conditions`
|
|
20082
|
+
);
|
|
20083
|
+
return filter;
|
|
20084
|
+
}
|
|
20085
|
+
if (depth >= MAX_FILTER_DEPTH)
|
|
20086
|
+
invalid(path, `nests deeper than ${String(MAX_FILTER_DEPTH)} groups`);
|
|
20087
|
+
checkKeys(object2, [kind], path);
|
|
20088
|
+
const inner = own(object2, kind);
|
|
20089
|
+
const innerPath = `${path}.${kind}`;
|
|
20090
|
+
if (kind === "not")
|
|
20091
|
+
return { not: readNode(inner, innerPath, depth + 1, budget) };
|
|
20092
|
+
if (!Array.isArray(inner)) invalid(innerPath, "must be an array");
|
|
20093
|
+
if (inner.length === 0) invalid(innerPath, "must not be empty");
|
|
20094
|
+
const children = inner.map(
|
|
20095
|
+
(child, index) => readNode(child, `${innerPath}[${index}]`, depth + 1, budget)
|
|
20096
|
+
);
|
|
20097
|
+
return kind === "or" ? { or: children } : { and: children };
|
|
20098
|
+
}
|
|
20099
|
+
function parsePolicyNode(value, path) {
|
|
20100
|
+
try {
|
|
20101
|
+
const budget = { path, conditions: 0, arrays: 0, auth: true };
|
|
20102
|
+
return { ok: true, node: readNode(value, path, 0, budget) };
|
|
20103
|
+
} catch (error51) {
|
|
20104
|
+
if (error51 instanceof Invalid)
|
|
20105
|
+
return { ok: false, path: error51.path, reason: error51.reason };
|
|
20106
|
+
throw error51;
|
|
20107
|
+
}
|
|
20108
|
+
}
|
|
20109
|
+
|
|
20110
|
+
// ../../packages/data-core/src/schema.ts
|
|
20111
|
+
var SCHEMA_SQL = [
|
|
20112
|
+
`SELECT t.name AS table_name, t.type AS table_type, c.cid AS cid, c.name AS column_name, c.type AS column_type, c."notnull" AS not_null, c.dflt_value AS default_value, c.pk AS pk, c.hidden AS hidden FROM pragma_table_list AS t JOIN pragma_table_xinfo(t.name, t.schema) AS c WHERE t.schema = 'main' AND t.type IN ('table', 'virtual') AND ${systemTablePredicate("t.name")} ORDER BY t.name, c.cid`,
|
|
20113
|
+
// Phase 2-2 (적대적 리뷰 반영): each table's CREATE TABLE text, where
|
|
20114
|
+
// alone a column's collation is written. A separate statement, so the
|
|
20115
|
+
// text comes once per table rather than once per column. D1 answers
|
|
20116
|
+
// `sqlite_master` reads (the database routes' `_tables.ts` reads it too).
|
|
20117
|
+
`SELECT name AS table_name, sql AS table_sql FROM sqlite_master WHERE type = 'table' AND ${systemTablePredicate("name")}`
|
|
20118
|
+
];
|
|
20119
|
+
|
|
20120
|
+
// ../../packages/data-core/src/condition.ts
|
|
20121
|
+
var MAX_CONDITION_BYTES = 16 * 1024;
|
|
20122
|
+
|
|
20123
|
+
// ../../packages/data-core/src/redirect-origins.ts
|
|
20124
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
20125
|
+
"localhost",
|
|
20126
|
+
"127.0.0.1",
|
|
20127
|
+
"[::1]"
|
|
20128
|
+
]);
|
|
20129
|
+
function isSecureOrLoopback(url2) {
|
|
20130
|
+
if (url2.protocol === "https:") return true;
|
|
20131
|
+
return url2.protocol === "http:" && LOOPBACK_HOSTS.has(url2.hostname);
|
|
20132
|
+
}
|
|
20133
|
+
var MAX_ENTRY_LENGTH = 255;
|
|
20134
|
+
var TYPED_PATH = /^[a-z][a-z0-9+.-]*:\/\/[^/]*(\/.*)?$/i;
|
|
20135
|
+
var SEGMENT = /^[A-Za-z0-9._~-]+$/;
|
|
20136
|
+
function parseRedirectEntry(value) {
|
|
20137
|
+
if (typeof value !== "string") return null;
|
|
20138
|
+
const text = value.trim();
|
|
20139
|
+
if (text === "" || text.length > MAX_ENTRY_LENGTH) return null;
|
|
20140
|
+
if (/[\s?#*%\\]/.test(text)) return null;
|
|
20141
|
+
let url2;
|
|
20142
|
+
try {
|
|
20143
|
+
url2 = new URL(text);
|
|
20144
|
+
} catch {
|
|
20145
|
+
return null;
|
|
20146
|
+
}
|
|
20147
|
+
if (url2.username !== "" || url2.password !== "") return null;
|
|
20148
|
+
if (url2.search !== "" || url2.hash !== "") return null;
|
|
20149
|
+
if (!isSecureOrLoopback(url2)) return null;
|
|
20150
|
+
const typed = TYPED_PATH.exec(text)?.[1] ?? "/";
|
|
20151
|
+
if (typed !== url2.pathname) return null;
|
|
20152
|
+
const path = url2.pathname;
|
|
20153
|
+
if (path !== "/") {
|
|
20154
|
+
if (!path.endsWith("/")) return null;
|
|
20155
|
+
const segments = path.slice(1, -1).split("/");
|
|
20156
|
+
if (!segments.every(
|
|
20157
|
+
(segment) => SEGMENT.test(segment) && segment !== "." && segment !== ".."
|
|
20158
|
+
))
|
|
20159
|
+
return null;
|
|
20160
|
+
}
|
|
20161
|
+
return { origin: url2.origin, path };
|
|
20162
|
+
}
|
|
20163
|
+
function formatRedirectEntry(entry) {
|
|
20164
|
+
return entry.path === "/" ? entry.origin : `${entry.origin}${entry.path}`;
|
|
20165
|
+
}
|
|
20166
|
+
|
|
20167
|
+
// ../../packages/data-core/src/system-sql.ts
|
|
20168
|
+
var MIGRATIONS_TABLE = "CREATE TABLE IF NOT EXISTS _naro_migrations (version INTEGER PRIMARY KEY, name TEXT NOT NULL, applied_at TEXT NOT NULL DEFAULT (datetime('now')))";
|
|
20169
|
+
var SYSTEM_MIGRATIONS = [
|
|
20170
|
+
{
|
|
20171
|
+
version: 1,
|
|
20172
|
+
name: "system_tables",
|
|
20173
|
+
statements: [
|
|
20174
|
+
MIGRATIONS_TABLE,
|
|
20175
|
+
"CREATE TABLE IF NOT EXISTS _naro_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')))",
|
|
20176
|
+
// Spec §11.2, as written there.
|
|
20177
|
+
"CREATE TABLE IF NOT EXISTS _naro_policies (table_name TEXT PRIMARY KEY, select_rule TEXT NOT NULL DEFAULT 'none', insert_rule TEXT NOT NULL DEFAULT 'none', update_rule TEXT NOT NULL DEFAULT 'none', delete_rule TEXT NOT NULL DEFAULT 'none', owner_column TEXT, updated_at TEXT NOT NULL DEFAULT (datetime('now')))"
|
|
20178
|
+
]
|
|
20179
|
+
},
|
|
20180
|
+
{
|
|
20181
|
+
// Spec P2-2 §3.1: an action whose rule column says 'condition' reads its
|
|
20182
|
+
// condition from these. A worker from before them reads 'condition' as
|
|
20183
|
+
// an unknown rule — none.
|
|
20184
|
+
version: 2,
|
|
20185
|
+
name: "policy_conditions",
|
|
20186
|
+
statements: [],
|
|
20187
|
+
addColumns: ["select", "insert", "update", "delete"].map((action) => ({
|
|
20188
|
+
table: "_naro_policies",
|
|
20189
|
+
column: `${action}_condition`,
|
|
20190
|
+
definition: "TEXT"
|
|
20191
|
+
}))
|
|
20192
|
+
},
|
|
20193
|
+
{
|
|
20194
|
+
// Spec P2-6 §11: the Naro worker's sign-in handoff. A provider sign-in's
|
|
20195
|
+
// flow (state → the app's redirect and the browser's PKCE challenge), the
|
|
20196
|
+
// one-time codes a redirect hands the app, the sends the per-address
|
|
20197
|
+
// email limit counts, and (adversarial review I3) the unconfirmed
|
|
20198
|
+
// accounts someone else also tried to sign up as. Times are epoch
|
|
20199
|
+
// milliseconds.
|
|
20200
|
+
version: 3,
|
|
20201
|
+
name: "auth_handoff",
|
|
20202
|
+
statements: [
|
|
20203
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_flows (state TEXT PRIMARY KEY, provider TEXT NOT NULL, code_challenge TEXT NOT NULL, redirect_to TEXT NOT NULL, expires_at INTEGER NOT NULL)",
|
|
20204
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_codes (code_hash TEXT PRIMARY KEY, code_challenge TEXT NOT NULL, session_token TEXT NOT NULL, sealed_token TEXT NOT NULL, expires_at INTEGER NOT NULL)",
|
|
20205
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_email_log (address_hash TEXT NOT NULL, sent_at INTEGER NOT NULL)",
|
|
20206
|
+
"CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
|
|
20207
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
|
|
20208
|
+
]
|
|
20209
|
+
}
|
|
20210
|
+
];
|
|
20211
|
+
|
|
20212
|
+
// ../../packages/data-core/src/write-quota.ts
|
|
20213
|
+
var WRITE_QUOTA_KEY = "write_quota";
|
|
20214
|
+
var WRITE_QUOTA_SQL = {
|
|
20215
|
+
read: `SELECT value FROM _naro_meta WHERE key = '${WRITE_QUOTA_KEY}'`,
|
|
20216
|
+
upsert: `INSERT INTO _naro_meta (key, value, updated_at) VALUES ('${WRITE_QUOTA_KEY}', ?1, ?2) ON CONFLICT (key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
|
|
20217
|
+
};
|
|
20218
|
+
|
|
19183
20219
|
// ../../packages/sdk/src/generate-types.ts
|
|
19184
20220
|
var HEADER = [
|
|
19185
20221
|
"// Generated by `naro gen types`. Do not edit - re-run the command instead.",
|
|
19186
20222
|
"// `D1Database` comes from @cloudflare/workers-types."
|
|
19187
20223
|
];
|
|
19188
|
-
function
|
|
20224
|
+
function affinity(declared, strict) {
|
|
19189
20225
|
const type = declared.toUpperCase();
|
|
19190
|
-
if (type.trim() === "") return "
|
|
19191
|
-
if (type.
|
|
20226
|
+
if (type.trim() === "") return "NONE";
|
|
20227
|
+
if (strict && type.trim() === "ANY") return "ANY";
|
|
20228
|
+
if (type.includes("INT")) return "INTEGER";
|
|
19192
20229
|
if (type.includes("CHAR") || type.includes("CLOB") || type.includes("TEXT"))
|
|
19193
|
-
return "
|
|
19194
|
-
if (type.includes("BLOB")) return "
|
|
20230
|
+
return "TEXT";
|
|
20231
|
+
if (type.includes("BLOB")) return "BLOB";
|
|
19195
20232
|
if (type.includes("REAL") || type.includes("FLOA") || type.includes("DOUB"))
|
|
19196
|
-
return "
|
|
19197
|
-
|
|
20233
|
+
return "REAL";
|
|
20234
|
+
if (type.includes("BOOL")) return "BOOLEAN";
|
|
20235
|
+
if (type.includes("JSON")) return "JSON";
|
|
20236
|
+
return "NUMERIC";
|
|
20237
|
+
}
|
|
20238
|
+
var READ = {
|
|
20239
|
+
INTEGER: "number",
|
|
20240
|
+
TEXT: "string",
|
|
20241
|
+
BLOB: "number[]",
|
|
20242
|
+
REAL: "number",
|
|
20243
|
+
NONE: "number | string | number[]",
|
|
20244
|
+
ANY: "number | string | number[]",
|
|
20245
|
+
BOOLEAN: "number",
|
|
20246
|
+
JSON: "number | string",
|
|
20247
|
+
NUMERIC: "number | string"
|
|
20248
|
+
};
|
|
20249
|
+
var WRITE = {
|
|
20250
|
+
INTEGER: "number",
|
|
20251
|
+
TEXT: "string | Json",
|
|
20252
|
+
BLOB: "never",
|
|
20253
|
+
REAL: "number",
|
|
20254
|
+
NONE: "number | string | Json",
|
|
20255
|
+
ANY: "number | string | Json",
|
|
20256
|
+
BOOLEAN: "number | boolean",
|
|
20257
|
+
JSON: "number | string | Json",
|
|
20258
|
+
NUMERIC: "number | string"
|
|
20259
|
+
};
|
|
20260
|
+
var JSON_TYPE = "export type Json = readonly unknown[] | { readonly [key: string]: unknown };";
|
|
20261
|
+
function withNull(type, nullable2) {
|
|
20262
|
+
if (!nullable2) return type;
|
|
20263
|
+
return type === "never" ? "null" : `${type} | null`;
|
|
19198
20264
|
}
|
|
19199
20265
|
function interfaceBase(tableName) {
|
|
19200
20266
|
const pascal = tableName.split(/[^A-Za-z0-9$]+/).filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("").replace(/[^A-Za-z0-9_$]/g, "");
|
|
19201
20267
|
return pascal === "" || /^[0-9]/.test(pascal) ? `_${pascal}` : pascal;
|
|
19202
20268
|
}
|
|
20269
|
+
function sqlSkeleton(sql) {
|
|
20270
|
+
let out = "";
|
|
20271
|
+
let index = 0;
|
|
20272
|
+
while (index < sql.length) {
|
|
20273
|
+
const char = sql[index] ?? "";
|
|
20274
|
+
const pair = sql.slice(index, index + 2);
|
|
20275
|
+
if (pair === "--") {
|
|
20276
|
+
const end = sql.indexOf("\n", index);
|
|
20277
|
+
index = end < 0 ? sql.length : end;
|
|
20278
|
+
out += " ";
|
|
20279
|
+
} else if (pair === "/*") {
|
|
20280
|
+
const end = sql.indexOf("*/", index + 2);
|
|
20281
|
+
index = end < 0 ? sql.length : end + 2;
|
|
20282
|
+
out += " ";
|
|
20283
|
+
} else if (char === "'" || char === '"' || char === "`" || char === "[") {
|
|
20284
|
+
const close = char === "[" ? "]" : char;
|
|
20285
|
+
let end = index + 1;
|
|
20286
|
+
for (; ; ) {
|
|
20287
|
+
const found = sql.indexOf(close, end);
|
|
20288
|
+
if (found < 0) {
|
|
20289
|
+
end = sql.length;
|
|
20290
|
+
break;
|
|
20291
|
+
}
|
|
20292
|
+
if (close !== "]" && sql[found + 1] === close) {
|
|
20293
|
+
end = found + 2;
|
|
20294
|
+
continue;
|
|
20295
|
+
}
|
|
20296
|
+
end = found + 1;
|
|
20297
|
+
break;
|
|
20298
|
+
}
|
|
20299
|
+
out += `${char}${close}`;
|
|
20300
|
+
index = end;
|
|
20301
|
+
} else {
|
|
20302
|
+
out += char;
|
|
20303
|
+
index += 1;
|
|
20304
|
+
}
|
|
20305
|
+
}
|
|
20306
|
+
return out;
|
|
20307
|
+
}
|
|
20308
|
+
function tableOptions(sql) {
|
|
20309
|
+
const skeleton = sql === null ? "" : sqlSkeleton(sql);
|
|
20310
|
+
const close = skeleton.lastIndexOf(")");
|
|
20311
|
+
const tail = close < 0 ? "" : skeleton.slice(close + 1);
|
|
20312
|
+
return {
|
|
20313
|
+
withoutRowid: /\bWITHOUT\s+ROWID\b/i.test(tail),
|
|
20314
|
+
strict: /\bSTRICT\b/i.test(tail)
|
|
20315
|
+
};
|
|
20316
|
+
}
|
|
19203
20317
|
function rowidAlias(table) {
|
|
20318
|
+
if (tableOptions(table.sql).withoutRowid) return null;
|
|
19204
20319
|
const keys = table.columns.filter((column) => column.primaryKey);
|
|
19205
20320
|
const only = keys.length === 1 ? keys[0] : void 0;
|
|
19206
20321
|
return only && only.type.trim().toUpperCase() === "INTEGER" ? only.name : null;
|
|
@@ -19212,34 +20327,120 @@ function keyLiteral(name) {
|
|
|
19212
20327
|
(unit) => `${UNICODE_ESCAPE}${unit.charCodeAt(0).toString(16).padStart(4, "0")}`
|
|
19213
20328
|
);
|
|
19214
20329
|
}
|
|
19215
|
-
function
|
|
19216
|
-
const
|
|
19217
|
-
|
|
20330
|
+
function ownerFilledOnInsert(policies, table) {
|
|
20331
|
+
const folded = asciiLower(table);
|
|
20332
|
+
const matches = policies.filter(
|
|
20333
|
+
(policy2) => asciiLower(policy2.table) === folded
|
|
20334
|
+
);
|
|
20335
|
+
const policy = policies.find((candidate) => candidate.table === table) ?? (matches.length === 1 ? matches[0] : void 0);
|
|
20336
|
+
return policy?.insert === "owner" ? policy.ownerColumn : null;
|
|
20337
|
+
}
|
|
20338
|
+
async function policiesForTypes(client, projectId) {
|
|
20339
|
+
try {
|
|
20340
|
+
return { policies: await client.backend.policies(projectId) };
|
|
20341
|
+
} catch (error51) {
|
|
20342
|
+
return {
|
|
20343
|
+
policies: void 0,
|
|
20344
|
+
reason: isNaroError(error51) ? error51.code : "UNKNOWN_ERROR"
|
|
20345
|
+
};
|
|
20346
|
+
}
|
|
20347
|
+
}
|
|
20348
|
+
function isNullable(column, alias) {
|
|
20349
|
+
return !column.notNull && column.name !== alias;
|
|
20350
|
+
}
|
|
20351
|
+
function rowProperty(column, { alias, strict }) {
|
|
20352
|
+
const type = READ[affinity(column.type, strict)];
|
|
20353
|
+
return ` ${keyLiteral(column.name)}: ${withNull(type, isNullable(column, alias))};`;
|
|
20354
|
+
}
|
|
20355
|
+
function writeProperty(column, { alias, strict }, optional2) {
|
|
20356
|
+
const type = WRITE[affinity(column.type, strict)];
|
|
20357
|
+
return ` ${keyLiteral(column.name)}${optional2 ? "?" : ""}: ${withNull(type, isNullable(column, alias))};`;
|
|
19218
20358
|
}
|
|
19219
20359
|
function block(name, lines) {
|
|
19220
20360
|
return lines.length === 0 ? [`export interface ${name} {}`] : [`export interface ${name} {`, ...lines, "}"];
|
|
19221
20361
|
}
|
|
19222
|
-
function
|
|
20362
|
+
function member(indent, name, lines) {
|
|
20363
|
+
return lines.length === 0 ? [`${indent}${name}: {};`] : [`${indent}${name}: {`, ...lines, `${indent}};`];
|
|
20364
|
+
}
|
|
20365
|
+
function tableEntry(described) {
|
|
20366
|
+
const { table, row, alias, owner } = described;
|
|
20367
|
+
const writable = table.columns.filter((column) => column.generated !== true);
|
|
20368
|
+
const optionalOnInsert = (column) => isNullable(column, alias) || column.defaultValue !== null || column.name === alias || asciiLower(column.name) === owner;
|
|
20369
|
+
return [
|
|
20370
|
+
` ${keyLiteral(table.name)}: {`,
|
|
20371
|
+
` Row: ${row};`,
|
|
20372
|
+
...member(
|
|
20373
|
+
" ",
|
|
20374
|
+
"Insert",
|
|
20375
|
+
writable.map(
|
|
20376
|
+
(column) => writeProperty(column, described, optionalOnInsert(column))
|
|
20377
|
+
)
|
|
20378
|
+
),
|
|
20379
|
+
...member(
|
|
20380
|
+
" ",
|
|
20381
|
+
"Update",
|
|
20382
|
+
writable.map((column) => writeProperty(column, described, true))
|
|
20383
|
+
),
|
|
20384
|
+
" };"
|
|
20385
|
+
];
|
|
20386
|
+
}
|
|
20387
|
+
function viewEntry({ table, row }) {
|
|
20388
|
+
return [` ${keyLiteral(table.name)}: {`, ` Row: ${row};`, " };"];
|
|
20389
|
+
}
|
|
20390
|
+
var ALIASES = [
|
|
20391
|
+
'export type Tables<Name extends keyof Database["tables"] | keyof Database["views"]> =',
|
|
20392
|
+
' (Database["tables"] & Database["views"])[Name]["Row"];',
|
|
20393
|
+
'export type TablesInsert<Name extends keyof Database["tables"]> =',
|
|
20394
|
+
' Database["tables"][Name]["Insert"];',
|
|
20395
|
+
'export type TablesUpdate<Name extends keyof Database["tables"]> =',
|
|
20396
|
+
' Database["tables"][Name]["Update"];'
|
|
20397
|
+
];
|
|
20398
|
+
function generateTypes(tables, options = {}) {
|
|
19223
20399
|
const used = /* @__PURE__ */ new Set();
|
|
19224
20400
|
const out = [...HEADER];
|
|
19225
|
-
const
|
|
20401
|
+
const described = [];
|
|
19226
20402
|
for (const table of tables) {
|
|
19227
20403
|
const base2 = `${interfaceBase(table.name)}Row`;
|
|
19228
|
-
let
|
|
19229
|
-
for (let suffix = 2; used.has(
|
|
19230
|
-
|
|
19231
|
-
used.add(
|
|
19232
|
-
const
|
|
20404
|
+
let row = base2;
|
|
20405
|
+
for (let suffix = 2; used.has(row); suffix += 1)
|
|
20406
|
+
row = `${base2}_${String(suffix)}`;
|
|
20407
|
+
used.add(row);
|
|
20408
|
+
const owner = ownerFilledOnInsert(options.policies ?? [], table.name);
|
|
20409
|
+
const entry = {
|
|
20410
|
+
table,
|
|
20411
|
+
row,
|
|
20412
|
+
alias: rowidAlias(table),
|
|
20413
|
+
strict: tableOptions(table.sql).strict,
|
|
20414
|
+
owner: owner === null ? null : asciiLower(owner)
|
|
20415
|
+
};
|
|
20416
|
+
described.push(entry);
|
|
19233
20417
|
out.push(
|
|
19234
20418
|
"",
|
|
19235
20419
|
...block(
|
|
19236
|
-
|
|
19237
|
-
table.columns.map((column) =>
|
|
20420
|
+
row,
|
|
20421
|
+
table.columns.map((column) => rowProperty(column, entry))
|
|
19238
20422
|
)
|
|
19239
20423
|
);
|
|
19240
|
-
map2.push(` ${keyLiteral(table.name)}: ${name};`);
|
|
19241
20424
|
}
|
|
19242
|
-
out.push(
|
|
20425
|
+
out.push(
|
|
20426
|
+
"",
|
|
20427
|
+
JSON_TYPE,
|
|
20428
|
+
"",
|
|
20429
|
+
"export type Database = {",
|
|
20430
|
+
...member(
|
|
20431
|
+
" ",
|
|
20432
|
+
"tables",
|
|
20433
|
+
described.filter(({ table }) => table.type !== "view").flatMap(tableEntry)
|
|
20434
|
+
),
|
|
20435
|
+
...member(
|
|
20436
|
+
" ",
|
|
20437
|
+
"views",
|
|
20438
|
+
described.filter(({ table }) => table.type === "view").flatMap(viewEntry)
|
|
20439
|
+
),
|
|
20440
|
+
"};",
|
|
20441
|
+
"",
|
|
20442
|
+
...ALIASES
|
|
20443
|
+
);
|
|
19243
20444
|
out.push("", ...block("Env", [" DB: D1Database;"]));
|
|
19244
20445
|
return `${out.join("\n")}
|
|
19245
20446
|
`;
|
|
@@ -19302,6 +20503,113 @@ function ensureGitignoreHasNaro(dir) {
|
|
|
19302
20503
|
return true;
|
|
19303
20504
|
}
|
|
19304
20505
|
|
|
20506
|
+
// ../../packages/sdk/src/lint.ts
|
|
20507
|
+
function byText(left, right) {
|
|
20508
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
20509
|
+
}
|
|
20510
|
+
function lintedTables(input) {
|
|
20511
|
+
const virtual = new Set(
|
|
20512
|
+
input.virtualTables.map((name) => name.toLowerCase())
|
|
20513
|
+
);
|
|
20514
|
+
return input.tables.filter(
|
|
20515
|
+
(table) => table.type === "table" && !isSystemTable(table.name) && !virtual.has(table.name.toLowerCase())
|
|
20516
|
+
);
|
|
20517
|
+
}
|
|
20518
|
+
function countLintedTables(input) {
|
|
20519
|
+
return lintedTables(input).length;
|
|
20520
|
+
}
|
|
20521
|
+
function brokenForeignKeys(input) {
|
|
20522
|
+
const groups = /* @__PURE__ */ new Map();
|
|
20523
|
+
for (const violation of input.foreignKeyCheck) {
|
|
20524
|
+
if (isSystemTable(violation.table)) continue;
|
|
20525
|
+
const parents = groups.get(violation.table) ?? /* @__PURE__ */ new Map();
|
|
20526
|
+
const group = parents.get(violation.parent) ?? {
|
|
20527
|
+
rows: /* @__PURE__ */ new Set(),
|
|
20528
|
+
loose: 0
|
|
20529
|
+
};
|
|
20530
|
+
if (violation.rowid === null) group.loose += 1;
|
|
20531
|
+
else group.rows.add(violation.rowid);
|
|
20532
|
+
parents.set(violation.parent, group);
|
|
20533
|
+
groups.set(violation.table, parents);
|
|
20534
|
+
}
|
|
20535
|
+
return [...groups.entries()].toSorted(([left], [right]) => byText(left, right)).flatMap(
|
|
20536
|
+
([table, parents]) => [...parents.entries()].toSorted(([left], [right]) => byText(left, right)).map(([parent, group]) => ({
|
|
20537
|
+
rule: "broken-foreign-key",
|
|
20538
|
+
severity: "error",
|
|
20539
|
+
object: table,
|
|
20540
|
+
message: `${String(group.rows.size + group.loose)} row(s) point at a ${parent} row that is gone.`
|
|
20541
|
+
}))
|
|
20542
|
+
);
|
|
20543
|
+
}
|
|
20544
|
+
function integrity(input) {
|
|
20545
|
+
return input.quickCheck.filter((line) => line.trim().toLowerCase() !== "ok").map((line) => ({
|
|
20546
|
+
rule: "integrity",
|
|
20547
|
+
severity: "error",
|
|
20548
|
+
object: "database",
|
|
20549
|
+
message: `The database failed SQLite's quick_check: ${line}`
|
|
20550
|
+
}));
|
|
20551
|
+
}
|
|
20552
|
+
function tableIssues(input) {
|
|
20553
|
+
const issues = [];
|
|
20554
|
+
for (const table of lintedTables(input)) {
|
|
20555
|
+
if (table.columns.length === 0) continue;
|
|
20556
|
+
const leading = new Set(
|
|
20557
|
+
table.indexes.flatMap(
|
|
20558
|
+
(index) => index.columns[0] === void 0 ? [] : [index.columns[0].toLowerCase()]
|
|
20559
|
+
)
|
|
20560
|
+
);
|
|
20561
|
+
const keyColumns = table.columns.filter((column) => column.primaryKey);
|
|
20562
|
+
const declared = keyColumns.length === 1 && keyColumns[0]?.type.trim().toUpperCase() === "INTEGER" ? keyColumns[0]?.name.toLowerCase() ?? null : null;
|
|
20563
|
+
const rowidColumn = declared !== null && !leading.has(declared) ? declared : null;
|
|
20564
|
+
if (keyColumns.length === 0)
|
|
20565
|
+
issues.push({
|
|
20566
|
+
rule: "no-primary-key",
|
|
20567
|
+
severity: "warn",
|
|
20568
|
+
object: table.name,
|
|
20569
|
+
message: "The table has no primary key, so its rows are identified by a rowid that changes after a VACUUM or a restore."
|
|
20570
|
+
});
|
|
20571
|
+
for (const column of keyColumns) {
|
|
20572
|
+
if (rowidColumn !== null || column.notNull) continue;
|
|
20573
|
+
issues.push({
|
|
20574
|
+
rule: "nullable-primary-key",
|
|
20575
|
+
severity: "warn",
|
|
20576
|
+
object: `${table.name}.${column.name}`,
|
|
20577
|
+
message: "The primary key column is not INTEGER and has no NOT NULL, so SQLite accepts NULL in it."
|
|
20578
|
+
});
|
|
20579
|
+
}
|
|
20580
|
+
for (const foreignKey of table.foreignKeys) {
|
|
20581
|
+
const from = foreignKey.from.toLowerCase();
|
|
20582
|
+
if (leading.has(from) || from === rowidColumn) continue;
|
|
20583
|
+
issues.push({
|
|
20584
|
+
rule: "unindexed-foreign-key",
|
|
20585
|
+
severity: "warn",
|
|
20586
|
+
object: `${table.name}.${foreignKey.from}`,
|
|
20587
|
+
message: `No index starts with this column, so a DELETE or UPDATE on ${foreignKey.table} scans the whole table.`
|
|
20588
|
+
});
|
|
20589
|
+
}
|
|
20590
|
+
}
|
|
20591
|
+
return issues;
|
|
20592
|
+
}
|
|
20593
|
+
function lintSchema(input) {
|
|
20594
|
+
return [
|
|
20595
|
+
...brokenForeignKeys(input),
|
|
20596
|
+
...integrity(input),
|
|
20597
|
+
...tableIssues(input),
|
|
20598
|
+
...input.triggers.filter((name) => !isSystemTable(name)).map((name) => ({
|
|
20599
|
+
rule: "trigger-not-reproducible",
|
|
20600
|
+
severity: "warn",
|
|
20601
|
+
object: name,
|
|
20602
|
+
message: "Cloudflare's REST API cannot create a trigger, so db pull writes it out but db push cannot put it back."
|
|
20603
|
+
})),
|
|
20604
|
+
...input.virtualTables.filter((name) => !isSystemTable(name)).map((name) => ({
|
|
20605
|
+
rule: "virtual-table-blocks-export",
|
|
20606
|
+
severity: "warn",
|
|
20607
|
+
object: name,
|
|
20608
|
+
message: "A virtual table makes db dump refuse the whole database; dump the other tables with --table."
|
|
20609
|
+
}))
|
|
20610
|
+
];
|
|
20611
|
+
}
|
|
20612
|
+
|
|
19305
20613
|
// ../../packages/sdk/src/migrations.ts
|
|
19306
20614
|
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
19307
20615
|
import { resolve as resolve8 } from "path";
|
|
@@ -19309,29 +20617,17 @@ function resolveMigrationsDir(input) {
|
|
|
19309
20617
|
if (input.dir) return resolve8(input.baseDir, input.dir);
|
|
19310
20618
|
return resolve8(input.baseDir, input.rootDirectory ?? "", "migrations");
|
|
19311
20619
|
}
|
|
19312
|
-
function normalizeMigrationName(name) {
|
|
19313
|
-
return name.trim().toLowerCase().replace(/\.sql$/, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
19314
|
-
}
|
|
19315
|
-
function leadingNumber(name) {
|
|
19316
|
-
const match = /^(\d+)/.exec(name);
|
|
19317
|
-
return match ? Number(match[1]) : null;
|
|
19318
|
-
}
|
|
19319
20620
|
function sortMigrationNames(names) {
|
|
19320
20621
|
return names.toSorted((a, b) => {
|
|
19321
|
-
const left =
|
|
19322
|
-
const right =
|
|
19323
|
-
if (left !== null && right !== null && left !== right)
|
|
20622
|
+
const left = leadingMigrationNumber(a);
|
|
20623
|
+
const right = leadingMigrationNumber(b);
|
|
20624
|
+
if (left !== null && right !== null && left !== right)
|
|
20625
|
+
return left < right ? -1 : 1;
|
|
19324
20626
|
if (left === null && right !== null) return 1;
|
|
19325
20627
|
if (left !== null && right === null) return -1;
|
|
19326
20628
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
19327
20629
|
});
|
|
19328
20630
|
}
|
|
19329
|
-
function highestMigrationNumber(names) {
|
|
19330
|
-
return names.reduce(
|
|
19331
|
-
(max, name) => Math.max(max, leadingNumber(name) ?? 0),
|
|
19332
|
-
0
|
|
19333
|
-
);
|
|
19334
|
-
}
|
|
19335
20631
|
function listMigrationFiles(dir) {
|
|
19336
20632
|
if (!existsSync5(dir)) return [];
|
|
19337
20633
|
return sortMigrationNames(
|
|
@@ -19345,8 +20641,7 @@ function nextMigrationFileName(existing, name) {
|
|
|
19345
20641
|
"VALIDATION_FAILED",
|
|
19346
20642
|
"Migration name must contain letters or digits."
|
|
19347
20643
|
);
|
|
19348
|
-
|
|
19349
|
-
return `${number4}_${normalized}.sql`;
|
|
20644
|
+
return numberedMigrationName(existing, normalized);
|
|
19350
20645
|
}
|
|
19351
20646
|
function compareMigrations(local, applied) {
|
|
19352
20647
|
const ledger = new Map(applied.map((row) => [row.name, row]));
|
|
@@ -19370,7 +20665,7 @@ function compareMigrations(local, applied) {
|
|
|
19370
20665
|
});
|
|
19371
20666
|
continue;
|
|
19372
20667
|
}
|
|
19373
|
-
const number4 =
|
|
20668
|
+
const number4 = leadingMigrationNumber(name);
|
|
19374
20669
|
rows.push({
|
|
19375
20670
|
name,
|
|
19376
20671
|
status: "pending",
|
|
@@ -19386,6 +20681,186 @@ function compareMigrations(local, applied) {
|
|
|
19386
20681
|
const order = sortMigrationNames(rows.map((row) => row.name));
|
|
19387
20682
|
return rows.toSorted((a, b) => order.indexOf(a.name) - order.indexOf(b.name));
|
|
19388
20683
|
}
|
|
20684
|
+
function pendingMigrationNames(local, applied) {
|
|
20685
|
+
return compareMigrations(local, applied).filter((row) => row.status === "pending").map((row) => row.name);
|
|
20686
|
+
}
|
|
20687
|
+
function migrationsPendingError(pending) {
|
|
20688
|
+
return new NaroError(
|
|
20689
|
+
"DATABASE_MIGRATIONS_PENDING",
|
|
20690
|
+
`Run \`naro db push\` first; ${String(pending.length)} local migration(s) are not applied yet: ${pending.join(", ")}.`
|
|
20691
|
+
);
|
|
20692
|
+
}
|
|
20693
|
+
function migrationNameMismatch(sent, recorded) {
|
|
20694
|
+
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.`;
|
|
20695
|
+
}
|
|
20696
|
+
|
|
20697
|
+
// ../../packages/sdk/src/reset-plan.ts
|
|
20698
|
+
var RESET_DEFER_FOREIGN_KEYS = "pragma defer_foreign_keys = true;";
|
|
20699
|
+
var RESET_BATCH_STATEMENTS = 50;
|
|
20700
|
+
function clearInternal(internal) {
|
|
20701
|
+
return [
|
|
20702
|
+
...internal.sqliteSequence ? ["DELETE FROM sqlite_sequence;"] : [],
|
|
20703
|
+
...internal.ledger ? ["DELETE FROM d1_migrations;"] : []
|
|
20704
|
+
];
|
|
20705
|
+
}
|
|
20706
|
+
function quote(name) {
|
|
20707
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
20708
|
+
}
|
|
20709
|
+
function byName(left, right) {
|
|
20710
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
20711
|
+
}
|
|
20712
|
+
function sorted(names) {
|
|
20713
|
+
return [...names].toSorted(byName);
|
|
20714
|
+
}
|
|
20715
|
+
function walk(start, adjacency, seen, onLeave) {
|
|
20716
|
+
seen.add(start);
|
|
20717
|
+
const stack = [{ node: start, next: 0 }];
|
|
20718
|
+
while (stack.length > 0) {
|
|
20719
|
+
const frame = stack[stack.length - 1];
|
|
20720
|
+
if (frame === void 0) break;
|
|
20721
|
+
const neighbours = adjacency.get(frame.node) ?? [];
|
|
20722
|
+
const neighbour = neighbours[frame.next];
|
|
20723
|
+
if (neighbour === void 0) {
|
|
20724
|
+
onLeave(frame.node);
|
|
20725
|
+
stack.pop();
|
|
20726
|
+
continue;
|
|
20727
|
+
}
|
|
20728
|
+
frame.next += 1;
|
|
20729
|
+
if (!seen.has(neighbour)) {
|
|
20730
|
+
seen.add(neighbour);
|
|
20731
|
+
stack.push({ node: neighbour, next: 0 });
|
|
20732
|
+
}
|
|
20733
|
+
}
|
|
20734
|
+
}
|
|
20735
|
+
function components(nodes, edges) {
|
|
20736
|
+
const forward = /* @__PURE__ */ new Map();
|
|
20737
|
+
const backward = /* @__PURE__ */ new Map();
|
|
20738
|
+
for (const node of nodes) {
|
|
20739
|
+
forward.set(node, []);
|
|
20740
|
+
backward.set(node, []);
|
|
20741
|
+
}
|
|
20742
|
+
for (const edge of edges) {
|
|
20743
|
+
forward.get(edge.child)?.push(edge.parent);
|
|
20744
|
+
backward.get(edge.parent)?.push(edge.child);
|
|
20745
|
+
}
|
|
20746
|
+
for (const [node, list] of forward) forward.set(node, list.toSorted(byName));
|
|
20747
|
+
for (const [node, list] of backward)
|
|
20748
|
+
backward.set(node, list.toSorted(byName));
|
|
20749
|
+
const finished = [];
|
|
20750
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20751
|
+
for (const node of nodes)
|
|
20752
|
+
if (!seen.has(node))
|
|
20753
|
+
walk(node, forward, seen, (left) => finished.push(left));
|
|
20754
|
+
const assigned = /* @__PURE__ */ new Set();
|
|
20755
|
+
const found = [];
|
|
20756
|
+
for (let index = finished.length - 1; index >= 0; index--) {
|
|
20757
|
+
const node = finished[index];
|
|
20758
|
+
if (node === void 0 || assigned.has(node)) continue;
|
|
20759
|
+
const members = [];
|
|
20760
|
+
walk(node, backward, assigned, (left) => members.push(left));
|
|
20761
|
+
found.push(sorted(members));
|
|
20762
|
+
}
|
|
20763
|
+
return found;
|
|
20764
|
+
}
|
|
20765
|
+
function tableGroups(names, edges) {
|
|
20766
|
+
const groups = components(names, edges);
|
|
20767
|
+
const indexOf = /* @__PURE__ */ new Map();
|
|
20768
|
+
groups.forEach((group, index) => {
|
|
20769
|
+
for (const member2 of group) indexOf.set(member2, index);
|
|
20770
|
+
});
|
|
20771
|
+
const waitingOn = groups.map(() => 0);
|
|
20772
|
+
const parentsOf = groups.map(() => /* @__PURE__ */ new Set());
|
|
20773
|
+
for (const edge of edges) {
|
|
20774
|
+
const child = indexOf.get(edge.child);
|
|
20775
|
+
const parent = indexOf.get(edge.parent);
|
|
20776
|
+
if (child === void 0 || parent === void 0 || child === parent)
|
|
20777
|
+
continue;
|
|
20778
|
+
if (parentsOf[child]?.has(parent) === true) continue;
|
|
20779
|
+
parentsOf[child]?.add(parent);
|
|
20780
|
+
waitingOn[parent] = (waitingOn[parent] ?? 0) + 1;
|
|
20781
|
+
}
|
|
20782
|
+
const key = (index) => groups[index]?.[0] ?? "";
|
|
20783
|
+
const ready = groups.map((_, index) => index).filter((index) => waitingOn[index] === 0);
|
|
20784
|
+
const order = [];
|
|
20785
|
+
while (ready.length > 0) {
|
|
20786
|
+
ready.sort((left, right) => byName(key(left), key(right)));
|
|
20787
|
+
const next = ready.shift();
|
|
20788
|
+
if (next === void 0) break;
|
|
20789
|
+
const group = groups[next];
|
|
20790
|
+
if (group) order.push(group);
|
|
20791
|
+
for (const parent of parentsOf[next] ?? []) {
|
|
20792
|
+
waitingOn[parent] = (waitingOn[parent] ?? 1) - 1;
|
|
20793
|
+
if (waitingOn[parent] === 0) ready.push(parent);
|
|
20794
|
+
}
|
|
20795
|
+
}
|
|
20796
|
+
for (let index = 0; index < groups.length; index++) {
|
|
20797
|
+
const group = groups[index];
|
|
20798
|
+
if (group && (waitingOn[index] ?? 0) > 0) order.push(group);
|
|
20799
|
+
}
|
|
20800
|
+
return order;
|
|
20801
|
+
}
|
|
20802
|
+
function pack(groups) {
|
|
20803
|
+
const batches = [];
|
|
20804
|
+
let current = [RESET_DEFER_FOREIGN_KEYS];
|
|
20805
|
+
for (const group of groups) {
|
|
20806
|
+
if (current.length > 1 && current.length + group.length > RESET_BATCH_STATEMENTS) {
|
|
20807
|
+
batches.push(current);
|
|
20808
|
+
current = [RESET_DEFER_FOREIGN_KEYS];
|
|
20809
|
+
}
|
|
20810
|
+
current.push(...group);
|
|
20811
|
+
}
|
|
20812
|
+
if (current.length > 1) batches.push(current);
|
|
20813
|
+
return batches;
|
|
20814
|
+
}
|
|
20815
|
+
function nameList(names) {
|
|
20816
|
+
const shown = names.slice(0, 10).join(", ");
|
|
20817
|
+
const rest = names.length - 10;
|
|
20818
|
+
return rest > 0 ? `${shown} and ${String(rest)} more` : shown;
|
|
20819
|
+
}
|
|
20820
|
+
function refuseOversizedCycle(groups) {
|
|
20821
|
+
const room = RESET_BATCH_STATEMENTS - 1;
|
|
20822
|
+
const cycle = groups.find((group) => group.length > room);
|
|
20823
|
+
if (cycle === void 0) return;
|
|
20824
|
+
throw new NaroError(
|
|
20825
|
+
"VALIDATION_FAILED",
|
|
20826
|
+
`A foreign key cycle of ${String(cycle.length)} tables cannot be dropped in one request (the limit is ${String(room)}): ${nameList(cycle)}. Break the cycle or drop those tables by hand.`
|
|
20827
|
+
);
|
|
20828
|
+
}
|
|
20829
|
+
function buildResetPlan(input) {
|
|
20830
|
+
const objects = input.objects.filter((object2) => !isSystemTable(object2.name));
|
|
20831
|
+
const views = sorted(
|
|
20832
|
+
objects.filter((object2) => object2.type === "view").map((object2) => object2.name)
|
|
20833
|
+
);
|
|
20834
|
+
const triggers = sorted(
|
|
20835
|
+
objects.filter((object2) => object2.type === "trigger").map((object2) => object2.name)
|
|
20836
|
+
);
|
|
20837
|
+
const tableNames = sorted(
|
|
20838
|
+
objects.filter((object2) => object2.type === "table" || object2.type === "virtual").map((object2) => object2.name)
|
|
20839
|
+
);
|
|
20840
|
+
const canonical = new Map(tableNames.map((name) => [asciiLower(name), name]));
|
|
20841
|
+
const edges = [];
|
|
20842
|
+
for (const edge of input.foreignKeys) {
|
|
20843
|
+
const child = canonical.get(asciiLower(edge.child));
|
|
20844
|
+
const parent = canonical.get(asciiLower(edge.parent));
|
|
20845
|
+
if (child !== void 0 && parent !== void 0 && child !== parent)
|
|
20846
|
+
edges.push({ child, parent });
|
|
20847
|
+
}
|
|
20848
|
+
const ordered = tableGroups(tableNames, edges);
|
|
20849
|
+
refuseOversizedCycle(ordered);
|
|
20850
|
+
const clear = clearInternal(input.internal);
|
|
20851
|
+
const groups = [
|
|
20852
|
+
...triggers.map((name) => [`DROP TRIGGER ${quote(name)};`]),
|
|
20853
|
+
...views.map((name) => [`DROP VIEW ${quote(name)};`]),
|
|
20854
|
+
...ordered.map(
|
|
20855
|
+
(group) => group.map((name) => `DROP TABLE ${quote(name)};`)
|
|
20856
|
+
),
|
|
20857
|
+
...clear.length > 0 ? [clear] : []
|
|
20858
|
+
];
|
|
20859
|
+
return {
|
|
20860
|
+
batches: pack(groups),
|
|
20861
|
+
dropped: { tables: ordered.flat(), views, triggers }
|
|
20862
|
+
};
|
|
20863
|
+
}
|
|
19389
20864
|
|
|
19390
20865
|
// src/env-consent.ts
|
|
19391
20866
|
import { existsSync as existsSync6 } from "fs";
|
|
@@ -19743,7 +21218,7 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
19743
21218
|
if (!framework.deployable && !options.force) {
|
|
19744
21219
|
throw new NaroError(
|
|
19745
21220
|
"FRAMEWORK_UNSUPPORTED",
|
|
19746
|
-
framework.framework === "unknown" ? `No supported framework detected in ${appRoot}. Naro deploys Next.js, Vite, Create React App, and static
|
|
21221
|
+
framework.framework === "unknown" ? `No supported framework detected in ${appRoot}. Naro deploys Next.js, Vite, Create React App, static sites, and static builds of Astro, SvelteKit, Nuxt, Angular, Docusaurus and Gatsby \u2014 run this inside one of those projects, or point at it with --root <dir>.` : `${framework.name} needs a server runtime, which Naro does not deploy yet.`,
|
|
19747
21222
|
{ details: { framework: framework.framework } }
|
|
19748
21223
|
);
|
|
19749
21224
|
}
|
|
@@ -19889,6 +21364,7 @@ function deploymentUrl(detail, ctx, production) {
|
|
|
19889
21364
|
|
|
19890
21365
|
export {
|
|
19891
21366
|
parseGitHubRemote,
|
|
21367
|
+
schemaMigrationTitle,
|
|
19892
21368
|
projectPath,
|
|
19893
21369
|
NaroError,
|
|
19894
21370
|
isNaroError,
|
|
@@ -19904,19 +21380,37 @@ export {
|
|
|
19904
21380
|
resolveAuth,
|
|
19905
21381
|
openBrowser,
|
|
19906
21382
|
isTerminalStatus,
|
|
21383
|
+
isStalledBackend,
|
|
21384
|
+
POLICY_RULES,
|
|
21385
|
+
POLICY_ACTIONS,
|
|
19907
21386
|
NaroClient,
|
|
21387
|
+
BOOKMARK_FORMAT_MESSAGE,
|
|
21388
|
+
isBookmark,
|
|
21389
|
+
RESTORE_OUTCOME_UNKNOWN_CODES,
|
|
21390
|
+
cellText,
|
|
19908
21391
|
renderRows,
|
|
19909
21392
|
summarizeStatement,
|
|
19910
21393
|
serializeDotenv,
|
|
21394
|
+
systemTablePredicate,
|
|
21395
|
+
parsePolicyNode,
|
|
21396
|
+
parseRedirectEntry,
|
|
21397
|
+
formatRedirectEntry,
|
|
21398
|
+
policiesForTypes,
|
|
19911
21399
|
generateTypes,
|
|
19912
21400
|
readProjectLink,
|
|
19913
21401
|
writeProjectLink,
|
|
19914
21402
|
removeProjectLink,
|
|
19915
21403
|
ensureGitignoreHasNaro,
|
|
21404
|
+
countLintedTables,
|
|
21405
|
+
lintSchema,
|
|
19916
21406
|
resolveMigrationsDir,
|
|
19917
21407
|
listMigrationFiles,
|
|
19918
21408
|
nextMigrationFileName,
|
|
19919
21409
|
compareMigrations,
|
|
21410
|
+
pendingMigrationNames,
|
|
21411
|
+
migrationsPendingError,
|
|
21412
|
+
migrationNameMismatch,
|
|
21413
|
+
buildResetPlan,
|
|
19920
21414
|
promptForMissingEnv,
|
|
19921
21415
|
detectGitInfo,
|
|
19922
21416
|
parseDotenv2 as parseDotenv,
|