naro-cli 0.18.0 → 0.19.1
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 +392 -25
- package/dist/{chunk-WLC42Y6U.js → chunk-ZO2BEC46.js} +2133 -415
- package/dist/deploy.d.ts +12 -0
- package/dist/deploy.js +1 -1
- package/dist/env-consent.d.ts +9 -1
- package/dist/env-surface.d.ts +12 -0
- package/dist/index.js +672 -166
- package/package.json +3 -3
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/deploy.ts
|
|
7
7
|
import { existsSync as existsSync7 } from "fs";
|
|
8
|
-
import { mkdtemp as mkdtemp2, readFile as
|
|
8
|
+
import { mkdtemp as mkdtemp2, readFile as readFile15, rm as rm5 } from "fs/promises";
|
|
9
9
|
import { tmpdir as tmpdir2 } from "os";
|
|
10
|
-
import { basename as basename4, dirname as dirname6, join as
|
|
10
|
+
import { basename as basename4, dirname as dirname6, join as join26, resolve as resolve11 } from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
12
|
|
|
13
13
|
// ../../packages/build/src/artifact-upload.ts
|
|
@@ -287,9 +287,9 @@ function hashAsset(data, contentType2) {
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
// ../../packages/build/src/local-build-executor.ts
|
|
290
|
-
import { access as access8, mkdtemp, readFile as
|
|
290
|
+
import { access as access8, mkdtemp, readFile as readFile13, rm as rm3 } from "fs/promises";
|
|
291
291
|
import { tmpdir } from "os";
|
|
292
|
-
import { dirname as dirname4, join as
|
|
292
|
+
import { dirname as dirname4, join as join18, resolve as resolve7 } from "path";
|
|
293
293
|
|
|
294
294
|
// ../../packages/core/src/route-patterns.ts
|
|
295
295
|
var ROUTE_PATTERN = /^\/[A-Za-z0-9\-._~%!$&()*+,;=:@/[\]]*$/;
|
|
@@ -15091,7 +15091,13 @@ var createProjectSchema = external_exports.object({
|
|
|
15091
15091
|
rootDirectory: optionalRootDirectory
|
|
15092
15092
|
});
|
|
15093
15093
|
var createDeploymentSchema = external_exports.object({
|
|
15094
|
-
branch: external_exports.string().trim().max(240).optional().transform((value) => value || null)
|
|
15094
|
+
branch: external_exports.string().trim().max(240).optional().transform((value) => value || null),
|
|
15095
|
+
/**
|
|
15096
|
+
* The surface to deploy to: `production`, `preview`, or the slug of one of
|
|
15097
|
+
* the project's environments. Whether the name exists is the route's to say;
|
|
15098
|
+
* 63 is the DNS label a worker name has to fit in.
|
|
15099
|
+
*/
|
|
15100
|
+
environment: external_exports.string().trim().min(1).max(63).optional()
|
|
15095
15101
|
});
|
|
15096
15102
|
var connectRepositorySchema = external_exports.object({
|
|
15097
15103
|
// Web-only route context: the only consumer is apps/web's
|
|
@@ -15354,6 +15360,40 @@ function splitStatements(sql) {
|
|
|
15354
15360
|
};
|
|
15355
15361
|
}
|
|
15356
15362
|
|
|
15363
|
+
// ../../packages/core/src/surface.ts
|
|
15364
|
+
var PRODUCTION_SURFACE = { kind: "production" };
|
|
15365
|
+
var PREVIEW_SURFACE = { kind: "preview" };
|
|
15366
|
+
var ENVIRONMENT_SLUG_MAX_LENGTH = 20;
|
|
15367
|
+
var RESERVED_ENVIRONMENT_SLUGS = [
|
|
15368
|
+
"production",
|
|
15369
|
+
"preview",
|
|
15370
|
+
"all"
|
|
15371
|
+
];
|
|
15372
|
+
var SLUG_PATTERN = /^[a-z0-9](-?[a-z0-9])*$/;
|
|
15373
|
+
var ENVIRONMENT_SEPARATOR = "--";
|
|
15374
|
+
var PREVIEW_SUFFIX = "-preview";
|
|
15375
|
+
function environmentSlugProblem(value) {
|
|
15376
|
+
if (value.length < 1 || value.length > ENVIRONMENT_SLUG_MAX_LENGTH)
|
|
15377
|
+
return "invalid";
|
|
15378
|
+
if (!SLUG_PATTERN.test(value)) return "invalid";
|
|
15379
|
+
if (RESERVED_ENVIRONMENT_SLUGS.includes(value))
|
|
15380
|
+
return "reserved";
|
|
15381
|
+
return null;
|
|
15382
|
+
}
|
|
15383
|
+
function environmentWorkerName(subdomain, slug) {
|
|
15384
|
+
return `${subdomain}${ENVIRONMENT_SEPARATOR}${slug}`;
|
|
15385
|
+
}
|
|
15386
|
+
function workerNameForSurface(subdomain, surface) {
|
|
15387
|
+
switch (surface.kind) {
|
|
15388
|
+
case "production":
|
|
15389
|
+
return subdomain;
|
|
15390
|
+
case "preview":
|
|
15391
|
+
return `${subdomain}${PREVIEW_SUFFIX}`;
|
|
15392
|
+
case "environment":
|
|
15393
|
+
return environmentWorkerName(subdomain, surface.slug);
|
|
15394
|
+
}
|
|
15395
|
+
}
|
|
15396
|
+
|
|
15357
15397
|
// ../../packages/build/src/next-version.ts
|
|
15358
15398
|
var NEXT_RELEASE_DATES = {
|
|
15359
15399
|
16: "2025-10-21",
|
|
@@ -15573,7 +15613,7 @@ import { access as access5, readFile as readFile5, writeFile as writeFile4 } fro
|
|
|
15573
15613
|
import { join as join6, resolve as resolve2 } from "path";
|
|
15574
15614
|
|
|
15575
15615
|
// ../../packages/build/src/open-next.ts
|
|
15576
|
-
import { access as access3, readdir, readFile as readFile4 } from "fs/promises";
|
|
15616
|
+
import { access as access3, readdir, readFile as readFile4, realpath } from "fs/promises";
|
|
15577
15617
|
import {
|
|
15578
15618
|
basename,
|
|
15579
15619
|
dirname,
|
|
@@ -15689,7 +15729,33 @@ function contentType(path) {
|
|
|
15689
15729
|
return "application/octet-stream";
|
|
15690
15730
|
}
|
|
15691
15731
|
}
|
|
15692
|
-
async function
|
|
15732
|
+
async function assertInsideOnDisk(appRoot, directory, label2) {
|
|
15733
|
+
let realRoot;
|
|
15734
|
+
let realDirectory;
|
|
15735
|
+
try {
|
|
15736
|
+
[realRoot, realDirectory] = await Promise.all([
|
|
15737
|
+
realpath(appRoot),
|
|
15738
|
+
realpath(directory)
|
|
15739
|
+
]);
|
|
15740
|
+
} catch {
|
|
15741
|
+
return;
|
|
15742
|
+
}
|
|
15743
|
+
const relation = relative(realRoot, realDirectory);
|
|
15744
|
+
if (relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) {
|
|
15745
|
+
throw new DeploymentError(
|
|
15746
|
+
"ARTIFACT_INVALID",
|
|
15747
|
+
`${label2} output leads out of the application root through a symbolic link.`
|
|
15748
|
+
);
|
|
15749
|
+
}
|
|
15750
|
+
if (relation.split(sep).includes(".git")) {
|
|
15751
|
+
throw new DeploymentError(
|
|
15752
|
+
"ARTIFACT_INVALID",
|
|
15753
|
+
`${label2} output leads into .git through a symbolic link.`
|
|
15754
|
+
);
|
|
15755
|
+
}
|
|
15756
|
+
}
|
|
15757
|
+
async function listArtifactFiles(root, label2 = "OpenNext", appRoot) {
|
|
15758
|
+
if (appRoot !== void 0) await assertInsideOnDisk(appRoot, root, label2);
|
|
15693
15759
|
const files = [];
|
|
15694
15760
|
async function visit(directory) {
|
|
15695
15761
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
@@ -15746,7 +15812,6 @@ var OPEN_NEXT_LAYOUT = {
|
|
|
15746
15812
|
outputRoot: ".open-next",
|
|
15747
15813
|
bundleDir: ".open-next/worker-bundle"
|
|
15748
15814
|
};
|
|
15749
|
-
var listFiles = (root) => listArtifactFiles(root);
|
|
15750
15815
|
async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
15751
15816
|
const configPath = await findWranglerConfig(appRoot, wranglerConfigPath);
|
|
15752
15817
|
return readWorkerArtifact(appRoot, identity, {
|
|
@@ -15755,6 +15820,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
|
15755
15820
|
});
|
|
15756
15821
|
}
|
|
15757
15822
|
async function readWorkerArtifact(appRoot, identity, layout) {
|
|
15823
|
+
const listFiles = (root) => listArtifactFiles(root, "OpenNext", appRoot);
|
|
15758
15824
|
try {
|
|
15759
15825
|
const configPath = await findWranglerConfig(
|
|
15760
15826
|
appRoot,
|
|
@@ -15799,12 +15865,13 @@ async function readWorkerArtifact(appRoot, identity, layout) {
|
|
|
15799
15865
|
);
|
|
15800
15866
|
}
|
|
15801
15867
|
const bundleRoot = confinedPath(appRoot, layout.bundleDir, "Bundle dir");
|
|
15802
|
-
let
|
|
15868
|
+
let bundleExists = true;
|
|
15803
15869
|
try {
|
|
15804
15870
|
await access3(bundleRoot);
|
|
15805
|
-
bundleFiles = await listFiles(bundleRoot);
|
|
15806
15871
|
} catch {
|
|
15872
|
+
bundleExists = false;
|
|
15807
15873
|
}
|
|
15874
|
+
const bundleFiles = bundleExists ? await listFiles(bundleRoot) : null;
|
|
15808
15875
|
const outputFiles = bundleFiles ?? await listFiles(outputRoot);
|
|
15809
15876
|
const entrypointName = bundleFiles ? basename(entrypointPath) : relative(outputRoot, entrypointPath).split(sep).join("/");
|
|
15810
15877
|
const assetsPrefix = `${relative(outputRoot, configuredAssets).split(sep).join("/")}/`;
|
|
@@ -16152,7 +16219,7 @@ async function readVinextArtifact(appRoot, identity) {
|
|
|
16152
16219
|
const entrypointName = metadata.main.split(/[\\/]/).join("/");
|
|
16153
16220
|
const matchers = moduleMatchers(metadata.rules);
|
|
16154
16221
|
const isModule = (name) => matchers.length === 0 || matchers.some((matcher) => matcher.test(name));
|
|
16155
|
-
const serverFiles = await listArtifactFiles(serverRoot, "vinext");
|
|
16222
|
+
const serverFiles = await listArtifactFiles(serverRoot, "vinext", appRoot);
|
|
16156
16223
|
const entrypoint = serverFiles.find((file2) => file2.name === entrypointName);
|
|
16157
16224
|
if (!entrypoint) {
|
|
16158
16225
|
throw new DeploymentError(
|
|
@@ -16166,7 +16233,7 @@ async function readVinextArtifact(appRoot, identity) {
|
|
|
16166
16233
|
modules: serverFiles.filter(
|
|
16167
16234
|
(file2) => file2.name !== entrypointName && file2.name !== WRANGLER_OUTPUT_NAME && !file2.name.endsWith(".map") && isModule(file2.name)
|
|
16168
16235
|
),
|
|
16169
|
-
assets: await listArtifactFiles(clientRoot, "vinext"),
|
|
16236
|
+
assets: await listArtifactFiles(clientRoot, "vinext", appRoot),
|
|
16170
16237
|
compatibilityDate: metadata.compatibility_date,
|
|
16171
16238
|
compatibilityFlags: Array.isArray(metadata.compatibility_flags) ? metadata.compatibility_flags.filter(
|
|
16172
16239
|
(flag) => typeof flag === "string"
|
|
@@ -16839,7 +16906,8 @@ async function runCommand(spec, observer) {
|
|
|
16839
16906
|
}
|
|
16840
16907
|
|
|
16841
16908
|
// ../../packages/build/src/repository.ts
|
|
16842
|
-
import {
|
|
16909
|
+
import { realpath as realpath2 } from "fs/promises";
|
|
16910
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
16843
16911
|
function resolveAppRoot(checkout, configuredRoot) {
|
|
16844
16912
|
const checkoutRoot = resolve3(checkout);
|
|
16845
16913
|
if (!configuredRoot) return checkoutRoot;
|
|
@@ -16851,12 +16919,34 @@ function resolveAppRoot(checkout, configuredRoot) {
|
|
|
16851
16919
|
if (relativeRoot === ".." || relativeRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute2(relativeRoot)) {
|
|
16852
16920
|
throw new Error("Configured root directory escapes repository");
|
|
16853
16921
|
}
|
|
16922
|
+
if (relativeRoot.split(/[\\/]/).includes(".git")) {
|
|
16923
|
+
throw new Error("Configured root directory cannot be inside .git");
|
|
16924
|
+
}
|
|
16854
16925
|
return appRoot;
|
|
16855
16926
|
}
|
|
16927
|
+
async function assertAppRootInsideCheckout(checkout, appRoot) {
|
|
16928
|
+
let realCheckout;
|
|
16929
|
+
let realApp;
|
|
16930
|
+
try {
|
|
16931
|
+
[realCheckout, realApp] = await Promise.all([
|
|
16932
|
+
realpath2(checkout),
|
|
16933
|
+
realpath2(appRoot)
|
|
16934
|
+
]);
|
|
16935
|
+
} catch {
|
|
16936
|
+
return;
|
|
16937
|
+
}
|
|
16938
|
+
const relation = relative2(realCheckout, realApp);
|
|
16939
|
+
if (relation === ".." || relation.startsWith(`..${sep2}`) || isAbsolute2(relation)) {
|
|
16940
|
+
throw new Error("Configured root directory escapes repository");
|
|
16941
|
+
}
|
|
16942
|
+
if (relation.split(sep2).includes(".git")) {
|
|
16943
|
+
throw new Error("Configured root directory cannot be inside .git");
|
|
16944
|
+
}
|
|
16945
|
+
}
|
|
16856
16946
|
|
|
16857
16947
|
// ../../packages/build/src/static-artifact.ts
|
|
16858
16948
|
import { readdir as readdir3, readFile as readFile7 } from "fs/promises";
|
|
16859
|
-
import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve4, sep as
|
|
16949
|
+
import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve4, sep as sep3 } from "path";
|
|
16860
16950
|
var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
|
|
16861
16951
|
var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
|
|
16862
16952
|
var ALLOWED_DOTFILE = ".well-known";
|
|
@@ -16872,11 +16962,16 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16872
16962
|
);
|
|
16873
16963
|
const outputRoot = resolve4(appRoot, outputDirectory);
|
|
16874
16964
|
const relation = relative3(appRoot, outputRoot);
|
|
16875
|
-
if (relation === ".." || relation.startsWith(`..${
|
|
16965
|
+
if (relation === ".." || relation.startsWith(`..${sep3}`) || isAbsolute3(relation))
|
|
16876
16966
|
throw new DeploymentError(
|
|
16877
16967
|
"ARTIFACT_INVALID",
|
|
16878
16968
|
"Output directory escapes the application root."
|
|
16879
16969
|
);
|
|
16970
|
+
if (relation.split(sep3).includes(".git"))
|
|
16971
|
+
throw new DeploymentError(
|
|
16972
|
+
"ARTIFACT_INVALID",
|
|
16973
|
+
"Output directory cannot be inside .git."
|
|
16974
|
+
);
|
|
16880
16975
|
const assets = [];
|
|
16881
16976
|
async function visit(directory) {
|
|
16882
16977
|
const entries = await readdir3(directory, { withFileTypes: true });
|
|
@@ -16893,7 +16988,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16893
16988
|
if (entry.isDirectory()) await visit(path);
|
|
16894
16989
|
else if (entry.isFile()) {
|
|
16895
16990
|
assets.push({
|
|
16896
|
-
name: relative3(outputRoot, path).split(
|
|
16991
|
+
name: relative3(outputRoot, path).split(sep3).join("/"),
|
|
16897
16992
|
data: await readFile7(path),
|
|
16898
16993
|
contentType: contentType(path)
|
|
16899
16994
|
});
|
|
@@ -16901,6 +16996,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16901
16996
|
}
|
|
16902
16997
|
}
|
|
16903
16998
|
try {
|
|
16999
|
+
await assertInsideOnDisk(appRoot, outputRoot, "Static");
|
|
16904
17000
|
await visit(outputRoot);
|
|
16905
17001
|
} catch (error51) {
|
|
16906
17002
|
if (error51 instanceof DeploymentError) throw error51;
|
|
@@ -16940,7 +17036,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16940
17036
|
// ../../packages/build/src/strategy/fingerprint.ts
|
|
16941
17037
|
import { createHash as createHash3 } from "crypto";
|
|
16942
17038
|
import { readFile as readFile8 } from "fs/promises";
|
|
16943
|
-
import { join as join10, relative as relative4, sep as
|
|
17039
|
+
import { join as join10, relative as relative4, sep as sep4 } from "path";
|
|
16944
17040
|
var APP_ROOT_FILES = [
|
|
16945
17041
|
"package.json",
|
|
16946
17042
|
"next.config.js",
|
|
@@ -16995,7 +17091,7 @@ async function hashStrategyInputs(checkout, appRoot) {
|
|
|
16995
17091
|
for (const path of candidates) {
|
|
16996
17092
|
const digest = await hashFile(path);
|
|
16997
17093
|
if (digest === null) continue;
|
|
16998
|
-
entries.push([relative4(checkout, path).split(
|
|
17094
|
+
entries.push([relative4(checkout, path).split(sep4).join("/"), digest]);
|
|
16999
17095
|
}
|
|
17000
17096
|
return entries.toSorted(
|
|
17001
17097
|
([left], [right]) => left < right ? -1 : left > right ? 1 : 0
|
|
@@ -17016,8 +17112,76 @@ async function computeStrategyFingerprint(input) {
|
|
|
17016
17112
|
}
|
|
17017
17113
|
|
|
17018
17114
|
// ../../packages/build/src/strategy/project-facts.ts
|
|
17019
|
-
import { access as access6, readFile as
|
|
17115
|
+
import { access as access6, readFile as readFile10 } from "fs/promises";
|
|
17116
|
+
import { join as join12 } from "path";
|
|
17117
|
+
|
|
17118
|
+
// ../../packages/build/src/strategy/user-adapter.ts
|
|
17119
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
17020
17120
|
import { join as join11 } from "path";
|
|
17121
|
+
var ADAPTER_PATH = /\badapterPath\b/;
|
|
17122
|
+
var EXPERIMENTAL_ADAPTER_PATH = /\bexperimental\s*:\s*\{[^{}]*\badapterPath\b/;
|
|
17123
|
+
function adapterKeyFromConfigSource(source) {
|
|
17124
|
+
const code = stripComments(source);
|
|
17125
|
+
if (!ADAPTER_PATH.test(code)) return null;
|
|
17126
|
+
return EXPERIMENTAL_ADAPTER_PATH.test(code) ? "experimental.adapterPath" : "adapterPath";
|
|
17127
|
+
}
|
|
17128
|
+
function keysNextReads(nextVersion) {
|
|
17129
|
+
const version2 = parseNextVersion(nextVersion);
|
|
17130
|
+
if (!version2) return ["adapterPath", "experimental.adapterPath"];
|
|
17131
|
+
if (version2.major < 15 || version2.major === 15 && version2.minor < 4)
|
|
17132
|
+
return [];
|
|
17133
|
+
if (version2.major > 16 || version2.major === 16 && version2.minor >= 2)
|
|
17134
|
+
return ["adapterPath", "experimental.adapterPath"];
|
|
17135
|
+
return ["experimental.adapterPath"];
|
|
17136
|
+
}
|
|
17137
|
+
function userAdapterOf(input) {
|
|
17138
|
+
const keys = keysNextReads(input.nextVersion);
|
|
17139
|
+
if (keys.length === 0) return null;
|
|
17140
|
+
const config2 = input.facts.adapterConfig;
|
|
17141
|
+
if (config2 && keys.includes(config2.key))
|
|
17142
|
+
return { source: "next-config", file: config2.file, key: config2.key };
|
|
17143
|
+
if (input.env.NEXT_ADAPTER_PATH) return { source: "env" };
|
|
17144
|
+
return null;
|
|
17145
|
+
}
|
|
17146
|
+
async function adapterInBuildOutput(appRoot, nextVersion) {
|
|
17147
|
+
const keys = keysNextReads(nextVersion);
|
|
17148
|
+
if (keys.length === 0) return null;
|
|
17149
|
+
let manifest2;
|
|
17150
|
+
try {
|
|
17151
|
+
const parsed = JSON.parse(
|
|
17152
|
+
await readFile9(
|
|
17153
|
+
join11(appRoot, ".next", "required-server-files.json"),
|
|
17154
|
+
"utf8"
|
|
17155
|
+
)
|
|
17156
|
+
);
|
|
17157
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
17158
|
+
manifest2 = parsed;
|
|
17159
|
+
} catch {
|
|
17160
|
+
return null;
|
|
17161
|
+
}
|
|
17162
|
+
const topLevel = manifest2.config?.adapterPath;
|
|
17163
|
+
if (keys.includes("adapterPath") && typeof topLevel === "string" && topLevel)
|
|
17164
|
+
return { source: "build-output", key: "adapterPath" };
|
|
17165
|
+
const experimental = manifest2.config?.experimental?.adapterPath;
|
|
17166
|
+
if (keys.includes("experimental.adapterPath") && typeof experimental === "string" && experimental)
|
|
17167
|
+
return { source: "build-output", key: "experimental.adapterPath" };
|
|
17168
|
+
return null;
|
|
17169
|
+
}
|
|
17170
|
+
function describeUserAdapter(adapter) {
|
|
17171
|
+
switch (adapter.source) {
|
|
17172
|
+
case "next-config":
|
|
17173
|
+
return `${adapter.file} ${adapter.key}`;
|
|
17174
|
+
case "env":
|
|
17175
|
+
return "NEXT_ADAPTER_PATH";
|
|
17176
|
+
case "build-output":
|
|
17177
|
+
return `${adapter.key} in the Next.js build output`;
|
|
17178
|
+
}
|
|
17179
|
+
}
|
|
17180
|
+
function openNextAdapterWarning(adapter) {
|
|
17181
|
+
return `P2 opennext-cloudflare: The project sets its own Next.js adapter (${describeUserAdapter(adapter)}); its modifyConfig and onBuildComplete also run in the OpenNext build.`;
|
|
17182
|
+
}
|
|
17183
|
+
|
|
17184
|
+
// ../../packages/build/src/strategy/project-facts.ts
|
|
17021
17185
|
var SOURCE_EXTENSIONS2 = ["ts", "tsx", "js", "jsx", "mjs", "mts"];
|
|
17022
17186
|
var SOURCE_DIRECTORIES = ["", "src"];
|
|
17023
17187
|
var VITE_CONFIGS = [
|
|
@@ -17038,7 +17202,7 @@ async function findSourceFile(appRoot, basename5) {
|
|
|
17038
17202
|
for (const dir of SOURCE_DIRECTORIES) {
|
|
17039
17203
|
for (const ext of SOURCE_EXTENSIONS2) {
|
|
17040
17204
|
const rel = dir ? `${dir}/${basename5}.${ext}` : `${basename5}.${ext}`;
|
|
17041
|
-
if (await exists4(
|
|
17205
|
+
if (await exists4(join12(appRoot, rel))) return rel;
|
|
17042
17206
|
}
|
|
17043
17207
|
}
|
|
17044
17208
|
return null;
|
|
@@ -17058,19 +17222,28 @@ function matcherRoutesFromSource(source) {
|
|
|
17058
17222
|
}
|
|
17059
17223
|
async function readIfExists(path) {
|
|
17060
17224
|
try {
|
|
17061
|
-
return await
|
|
17225
|
+
return await readFile10(path, "utf8");
|
|
17062
17226
|
} catch {
|
|
17063
17227
|
return null;
|
|
17064
17228
|
}
|
|
17065
17229
|
}
|
|
17230
|
+
async function findAdapterConfig(appRoot) {
|
|
17231
|
+
for (const file2 of NEXT_CONFIG_FILES) {
|
|
17232
|
+
const source = await readIfExists(join12(appRoot, file2));
|
|
17233
|
+
if (source === null) continue;
|
|
17234
|
+
const key = adapterKeyFromConfigSource(source);
|
|
17235
|
+
return key ? { file: file2, key } : null;
|
|
17236
|
+
}
|
|
17237
|
+
return null;
|
|
17238
|
+
}
|
|
17066
17239
|
async function collectProjectFacts(appRoot) {
|
|
17067
17240
|
const proxyFile = await findSourceFile(appRoot, "proxy");
|
|
17068
17241
|
const middlewareFile = await findSourceFile(appRoot, "middleware");
|
|
17069
17242
|
const routeSourceFile = middlewareFile ?? proxyFile;
|
|
17070
|
-
const routeSource = routeSourceFile ? await readIfExists(
|
|
17243
|
+
const routeSource = routeSourceFile ? await readIfExists(join12(appRoot, routeSourceFile)) : null;
|
|
17071
17244
|
let usesVinext = false;
|
|
17072
17245
|
let nextSpec = null;
|
|
17073
|
-
const packageJsonSource = await readIfExists(
|
|
17246
|
+
const packageJsonSource = await readIfExists(join12(appRoot, "package.json"));
|
|
17074
17247
|
if (packageJsonSource) {
|
|
17075
17248
|
try {
|
|
17076
17249
|
const packageJson = JSON.parse(packageJsonSource);
|
|
@@ -17081,7 +17254,7 @@ async function collectProjectFacts(appRoot) {
|
|
|
17081
17254
|
nextSpec = dependency("next");
|
|
17082
17255
|
if (dependency("vinext") !== null) {
|
|
17083
17256
|
for (const name of VITE_CONFIGS) {
|
|
17084
|
-
const config2 = await readIfExists(
|
|
17257
|
+
const config2 = await readIfExists(join12(appRoot, name));
|
|
17085
17258
|
if (config2 && /\bvinext\s*\(/.test(config2) && /@cloudflare\/vite-plugin/.test(config2)) {
|
|
17086
17259
|
usesVinext = true;
|
|
17087
17260
|
break;
|
|
@@ -17096,7 +17269,8 @@ async function collectProjectFacts(appRoot) {
|
|
|
17096
17269
|
middlewareFile,
|
|
17097
17270
|
matcherRoutes: routeSource ? matcherRoutesFromSource(routeSource) : [],
|
|
17098
17271
|
usesVinext,
|
|
17099
|
-
nextSpec
|
|
17272
|
+
nextSpec,
|
|
17273
|
+
adapterConfig: await findAdapterConfig(appRoot)
|
|
17100
17274
|
};
|
|
17101
17275
|
}
|
|
17102
17276
|
|
|
@@ -17104,6 +17278,12 @@ async function collectProjectFacts(appRoot) {
|
|
|
17104
17278
|
function label(strategy) {
|
|
17105
17279
|
return `P${String(strategy.priority)} ${strategy.id}`;
|
|
17106
17280
|
}
|
|
17281
|
+
function clause(text2) {
|
|
17282
|
+
return text2.endsWith(".") && !text2.endsWith("..") ? text2.slice(0, -1) : text2;
|
|
17283
|
+
}
|
|
17284
|
+
function sentence(text2) {
|
|
17285
|
+
return text2.endsWith(".") ? text2 : `${text2}.`;
|
|
17286
|
+
}
|
|
17107
17287
|
function describeOutcome(outcome) {
|
|
17108
17288
|
switch (outcome.status) {
|
|
17109
17289
|
case "success":
|
|
@@ -17128,7 +17308,7 @@ async function evaluateOne(strategy, context, observer) {
|
|
|
17128
17308
|
await observer.onLog({
|
|
17129
17309
|
level: outcome.status === "success" ? "INFO" : "WARN",
|
|
17130
17310
|
stage: "BUILDING",
|
|
17131
|
-
message: `${label(strategy)} ${describeOutcome(outcome)}
|
|
17311
|
+
message: sentence(`${label(strategy)} ${describeOutcome(outcome)}`)
|
|
17132
17312
|
});
|
|
17133
17313
|
return { id: strategy.id, priority: strategy.priority, outcome };
|
|
17134
17314
|
}
|
|
@@ -17226,10 +17406,13 @@ async function resolveDeploymentStrategy(strategies, context, observer, options)
|
|
|
17226
17406
|
attempts
|
|
17227
17407
|
};
|
|
17228
17408
|
}
|
|
17409
|
+
const othersLeft = candidates.some(
|
|
17410
|
+
(candidate) => candidate.id !== pinnedStrategy.id
|
|
17411
|
+
);
|
|
17229
17412
|
await observer.onLog({
|
|
17230
17413
|
level: "WARN",
|
|
17231
17414
|
stage: "BUILDING",
|
|
17232
|
-
message: `Stored strategy ${label(pinnedStrategy)} no longer works; resolving again with the remaining candidates.`
|
|
17415
|
+
message: othersLeft ? `Stored strategy ${label(pinnedStrategy)} no longer works; resolving again with the remaining candidates.` : `Stored strategy ${label(pinnedStrategy)} no longer works, and no other deployment strategy is left to try for Next.js ${context.nextVersion}.`
|
|
17233
17416
|
});
|
|
17234
17417
|
}
|
|
17235
17418
|
} else if (options.pinned) {
|
|
@@ -17242,13 +17425,14 @@ async function resolveDeploymentStrategy(strategies, context, observer, options)
|
|
|
17242
17425
|
const remaining = candidates.filter(
|
|
17243
17426
|
(candidate) => !attempts.some((attempt) => attempt.id === candidate.id)
|
|
17244
17427
|
);
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17428
|
+
if (remaining.length > 0)
|
|
17429
|
+
await observer.onLog({
|
|
17430
|
+
level: "INFO",
|
|
17431
|
+
stage: "BUILDING",
|
|
17432
|
+
message: `Resolving deployment strategy for Next.js ${context.nextVersion}: ${remaining.map(label).join(
|
|
17433
|
+
", "
|
|
17434
|
+
)}${options.concurrency > 1 && remaining.length > 1 ? " (in parallel)" : ""}.`
|
|
17435
|
+
});
|
|
17252
17436
|
if (options.concurrency > 1) {
|
|
17253
17437
|
const results = await runPool(
|
|
17254
17438
|
remaining.map(
|
|
@@ -17266,11 +17450,25 @@ async function resolveDeploymentStrategy(strategies, context, observer, options)
|
|
|
17266
17450
|
}
|
|
17267
17451
|
const best = pickBest(candidates, attempts);
|
|
17268
17452
|
if (!best) {
|
|
17453
|
+
const summary = attempts.map(
|
|
17454
|
+
(attempt) => clause(
|
|
17455
|
+
`P${String(attempt.priority)} ${attempt.id} ${describeOutcome(attempt.outcome)}`
|
|
17456
|
+
)
|
|
17457
|
+
).join("; ");
|
|
17458
|
+
if (attempts.some(
|
|
17459
|
+
(attempt) => attempt.outcome.status === "failed" && attempt.outcome.appBuild
|
|
17460
|
+
))
|
|
17461
|
+
throw new DeploymentError(
|
|
17462
|
+
"BUILD_FAILED",
|
|
17463
|
+
sentence(
|
|
17464
|
+
`The app's own build failed for Next.js ${context.nextVersion}: ${summary}`
|
|
17465
|
+
)
|
|
17466
|
+
);
|
|
17269
17467
|
throw new DeploymentError(
|
|
17270
17468
|
"NO_DEPLOYMENT_STRATEGY",
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
)
|
|
17469
|
+
sentence(
|
|
17470
|
+
`No deployment strategy succeeded for Next.js ${context.nextVersion}: ${summary}`
|
|
17471
|
+
)
|
|
17274
17472
|
);
|
|
17275
17473
|
}
|
|
17276
17474
|
await observer.onLog({
|
|
@@ -17459,11 +17657,11 @@ function defaultSmokeProbes(matcherRoutes) {
|
|
|
17459
17657
|
|
|
17460
17658
|
// ../../packages/build/src/strategy/strategies/opennext-aws.ts
|
|
17461
17659
|
import { writeFile as writeFile6 } from "fs/promises";
|
|
17462
|
-
import { join as
|
|
17660
|
+
import { join as join14 } from "path";
|
|
17463
17661
|
|
|
17464
17662
|
// ../../packages/build/src/strategy/strategies/shared.ts
|
|
17465
|
-
import { access as access7, readFile as
|
|
17466
|
-
import { dirname as dirname2, join as
|
|
17663
|
+
import { access as access7, readFile as readFile11, rm, writeFile as writeFile5 } from "fs/promises";
|
|
17664
|
+
import { dirname as dirname2, join as join13, resolve as resolve5 } from "path";
|
|
17467
17665
|
function capturingObserver(observer) {
|
|
17468
17666
|
const lines = [];
|
|
17469
17667
|
return {
|
|
@@ -17499,6 +17697,9 @@ function describeFailure(step) {
|
|
|
17499
17697
|
function failed(stage, reason) {
|
|
17500
17698
|
return { status: "failed", stage, reason };
|
|
17501
17699
|
}
|
|
17700
|
+
function appBuildFailed(reason) {
|
|
17701
|
+
return { status: "failed", stage: "build", reason, appBuild: true };
|
|
17702
|
+
}
|
|
17502
17703
|
async function info(observer, message) {
|
|
17503
17704
|
await observer.onLog({ level: "INFO", stage: "BUILDING", message });
|
|
17504
17705
|
}
|
|
@@ -17517,7 +17718,7 @@ async function findProjectBin(appRoot, checkout, name) {
|
|
|
17517
17718
|
let dir = resolve5(appRoot);
|
|
17518
17719
|
const stop = resolve5(checkout);
|
|
17519
17720
|
for (; ; ) {
|
|
17520
|
-
const candidate =
|
|
17721
|
+
const candidate = join13(dir, "node_modules", ".bin", name);
|
|
17521
17722
|
if (await pathExists2(candidate)) return candidate;
|
|
17522
17723
|
if (dir === stop) return null;
|
|
17523
17724
|
const parent = dirname2(dir);
|
|
@@ -17531,7 +17732,7 @@ function dotenvQuote(value) {
|
|
|
17531
17732
|
return `"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`;
|
|
17532
17733
|
}
|
|
17533
17734
|
async function withDevVars(wranglerConfigPath, env, run) {
|
|
17534
|
-
const path =
|
|
17735
|
+
const path = join13(dirname2(wranglerConfigPath), ".dev.vars");
|
|
17535
17736
|
const keys = Object.keys(env).filter(
|
|
17536
17737
|
(key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
|
|
17537
17738
|
);
|
|
@@ -17564,7 +17765,7 @@ async function bundleWorker(context, layout, observer, errorCode2) {
|
|
|
17564
17765
|
cwd: layout.appRoot,
|
|
17565
17766
|
env: {
|
|
17566
17767
|
CI: "1",
|
|
17567
|
-
WRANGLER_LOG_PATH:
|
|
17768
|
+
WRANGLER_LOG_PATH: join13(
|
|
17568
17769
|
layout.appRoot,
|
|
17569
17770
|
layout.outputRoot,
|
|
17570
17771
|
"wrangler.log"
|
|
@@ -17597,12 +17798,12 @@ async function smokeWorker(context, layout, observer) {
|
|
|
17597
17798
|
"--show-interactive-dev-session=false",
|
|
17598
17799
|
"--log-level=warn",
|
|
17599
17800
|
"--persist-to",
|
|
17600
|
-
|
|
17801
|
+
join13(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
|
|
17601
17802
|
],
|
|
17602
17803
|
cwd: layout.appRoot,
|
|
17603
17804
|
env: {
|
|
17604
17805
|
CI: "1",
|
|
17605
|
-
WRANGLER_LOG_PATH:
|
|
17806
|
+
WRANGLER_LOG_PATH: join13(
|
|
17606
17807
|
layout.appRoot,
|
|
17607
17808
|
layout.outputRoot,
|
|
17608
17809
|
"wrangler-dev.log"
|
|
@@ -17618,10 +17819,10 @@ async function smokeWorker(context, layout, observer) {
|
|
|
17618
17819
|
);
|
|
17619
17820
|
}
|
|
17620
17821
|
async function approvePnpmBuilds(checkout, packages) {
|
|
17621
|
-
const path =
|
|
17822
|
+
const path = join13(checkout, "pnpm-workspace.yaml");
|
|
17622
17823
|
let source = "";
|
|
17623
17824
|
try {
|
|
17624
|
-
source = await
|
|
17825
|
+
source = await readFile11(path, "utf8");
|
|
17625
17826
|
} catch {
|
|
17626
17827
|
}
|
|
17627
17828
|
let next = source;
|
|
@@ -17714,7 +17915,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17714
17915
|
} catch (error51) {
|
|
17715
17916
|
return failed("prepare", describeError(error51));
|
|
17716
17917
|
}
|
|
17717
|
-
const configPath =
|
|
17918
|
+
const configPath = join14(fork.appRoot, CONFIG_NAME);
|
|
17718
17919
|
try {
|
|
17719
17920
|
await writeFile6(
|
|
17720
17921
|
configPath,
|
|
@@ -17743,7 +17944,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17743
17944
|
observer
|
|
17744
17945
|
);
|
|
17745
17946
|
if (!build.ok) return failed("build", describeFailure(build));
|
|
17746
|
-
const serverFunction =
|
|
17947
|
+
const serverFunction = join14(fork.appRoot, SERVER_FUNCTION);
|
|
17747
17948
|
if (!await pathExists2(serverFunction))
|
|
17748
17949
|
return failed("bundle", `OpenNext AWS produced no ${SERVER_FUNCTION}.`);
|
|
17749
17950
|
const smoke = await context.ports.smoke.run(
|
|
@@ -17765,7 +17966,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17765
17966
|
status: "success",
|
|
17766
17967
|
artifact: {
|
|
17767
17968
|
kind: "opennext-aws",
|
|
17768
|
-
outputRoot:
|
|
17969
|
+
outputRoot: join14(fork.appRoot, ".open-next"),
|
|
17769
17970
|
serverFunction
|
|
17770
17971
|
},
|
|
17771
17972
|
notes: [
|
|
@@ -17777,8 +17978,8 @@ function createOpenNextAwsStrategy() {
|
|
|
17777
17978
|
}
|
|
17778
17979
|
|
|
17779
17980
|
// ../../packages/build/src/strategy/strategies/opennext-cloudflare.ts
|
|
17780
|
-
import { readFile as
|
|
17781
|
-
import { join as
|
|
17981
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
17982
|
+
import { join as join15 } from "path";
|
|
17782
17983
|
var PROXY_TO_MIDDLEWARE_VARIANT = "proxy-to-middleware";
|
|
17783
17984
|
var NODE_MIDDLEWARE_REFUSAL = /Node\.js middleware is not (?:currently )?supported/i;
|
|
17784
17985
|
var NODE_MIDDLEWARE_EXPERIMENTAL = /Node\.js middleware support is experimental/i;
|
|
@@ -17786,8 +17987,8 @@ var NODE_MIDDLEWARE_NOT_DEPLOYED = 'Node.js-runtime middleware (runtime: "nodejs
|
|
|
17786
17987
|
async function buildDeclaresNodeMiddleware(appRoot) {
|
|
17787
17988
|
try {
|
|
17788
17989
|
const manifest2 = JSON.parse(
|
|
17789
|
-
await
|
|
17790
|
-
|
|
17990
|
+
await readFile12(
|
|
17991
|
+
join15(appRoot, ".next", "server", "functions-config-manifest.json"),
|
|
17791
17992
|
"utf8"
|
|
17792
17993
|
)
|
|
17793
17994
|
);
|
|
@@ -17797,7 +17998,7 @@ async function buildDeclaresNodeMiddleware(appRoot) {
|
|
|
17797
17998
|
}
|
|
17798
17999
|
}
|
|
17799
18000
|
function createOpenNextCloudflareStrategy(options) {
|
|
17800
|
-
async function attempt(context, roots, observer) {
|
|
18001
|
+
async function attempt(context, roots, observer, ownBuild, adapterNotice) {
|
|
17801
18002
|
let platform;
|
|
17802
18003
|
try {
|
|
17803
18004
|
platform = await options.createPlatformBuildConfig(
|
|
@@ -17832,9 +18033,16 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17832
18033
|
return {
|
|
17833
18034
|
ok: false,
|
|
17834
18035
|
nodeMiddleware: null,
|
|
17835
|
-
outcome: failed("build", describeFailure(nextBuild))
|
|
18036
|
+
outcome: ownBuild ? appBuildFailed(describeFailure(nextBuild)) : failed("build", describeFailure(nextBuild))
|
|
17836
18037
|
};
|
|
17837
18038
|
}
|
|
18039
|
+
if (!adapterNotice.warned) {
|
|
18040
|
+
const adapter = userAdapterOf(context) ?? await adapterInBuildOutput(roots.appRoot, context.nextVersion);
|
|
18041
|
+
if (adapter) {
|
|
18042
|
+
adapterNotice.warned = true;
|
|
18043
|
+
await warn(observer, openNextAdapterWarning(adapter));
|
|
18044
|
+
}
|
|
18045
|
+
}
|
|
17838
18046
|
if (await buildDeclaresNodeMiddleware(roots.appRoot)) {
|
|
17839
18047
|
return {
|
|
17840
18048
|
ok: false,
|
|
@@ -17930,6 +18138,7 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17930
18138
|
target: "cloudflare",
|
|
17931
18139
|
supports: () => ({ supported: true }),
|
|
17932
18140
|
async evaluate(context, observer) {
|
|
18141
|
+
const adapterNotice = { warned: false };
|
|
17933
18142
|
const rememberedRewrite = context.pinnedVariant === PROXY_TO_MIDDLEWARE_VARIANT && context.facts.proxyFile !== null;
|
|
17934
18143
|
if (rememberedRewrite) {
|
|
17935
18144
|
await info(
|
|
@@ -17941,7 +18150,13 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17941
18150
|
observer,
|
|
17942
18151
|
"P2 opennext-cloudflare: building the project as written."
|
|
17943
18152
|
);
|
|
17944
|
-
const first = await attempt(
|
|
18153
|
+
const first = await attempt(
|
|
18154
|
+
context,
|
|
18155
|
+
context.workspace,
|
|
18156
|
+
observer,
|
|
18157
|
+
true,
|
|
18158
|
+
adapterNotice
|
|
18159
|
+
);
|
|
17945
18160
|
if (first.ok) {
|
|
17946
18161
|
return {
|
|
17947
18162
|
status: "success",
|
|
@@ -17973,8 +18188,54 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17973
18188
|
observer,
|
|
17974
18189
|
`Converted ${conversion.file ?? context.facts.proxyFile} to edge middleware for this build only. Next.js 16 proxy runs on the Node.js runtime, which Cloudflare does not support; middleware runs on the Edge runtime, so Node.js-only APIs are unavailable. Your repository is unchanged.`
|
|
17975
18190
|
);
|
|
17976
|
-
const second = await attempt(
|
|
17977
|
-
|
|
18191
|
+
const second = await attempt(
|
|
18192
|
+
context,
|
|
18193
|
+
fork,
|
|
18194
|
+
observer,
|
|
18195
|
+
rememberedRewrite,
|
|
18196
|
+
adapterNotice
|
|
18197
|
+
);
|
|
18198
|
+
if (!second.ok) {
|
|
18199
|
+
if (!rememberedRewrite || second.outcome.status !== "failed" || !second.outcome.appBuild)
|
|
18200
|
+
return second.outcome;
|
|
18201
|
+
await info(
|
|
18202
|
+
observer,
|
|
18203
|
+
`P2 opennext-cloudflare: the rewritten copy did not build; building the project as written once to tell the app's own error from the rewrite's.`
|
|
18204
|
+
);
|
|
18205
|
+
const asWritten = await attempt(
|
|
18206
|
+
context,
|
|
18207
|
+
context.workspace,
|
|
18208
|
+
observer,
|
|
18209
|
+
true,
|
|
18210
|
+
adapterNotice
|
|
18211
|
+
);
|
|
18212
|
+
if (asWritten.ok) {
|
|
18213
|
+
await info(
|
|
18214
|
+
observer,
|
|
18215
|
+
"P2 opennext-cloudflare: the project as written builds and ships as it is; the rewrite is no longer needed."
|
|
18216
|
+
);
|
|
18217
|
+
return {
|
|
18218
|
+
status: "success",
|
|
18219
|
+
artifact: {
|
|
18220
|
+
kind: "cloudflare-worker",
|
|
18221
|
+
artifact: asWritten.artifact
|
|
18222
|
+
},
|
|
18223
|
+
notes: []
|
|
18224
|
+
};
|
|
18225
|
+
}
|
|
18226
|
+
if (asWritten.outcome.status === "failed" && asWritten.outcome.appBuild) {
|
|
18227
|
+
await warn(
|
|
18228
|
+
observer,
|
|
18229
|
+
"P2 opennext-cloudflare: the project as written does not build either, so the error is the app's own."
|
|
18230
|
+
);
|
|
18231
|
+
return asWritten.outcome;
|
|
18232
|
+
}
|
|
18233
|
+
await warn(
|
|
18234
|
+
observer,
|
|
18235
|
+
"P2 opennext-cloudflare: the project as written builds, so the rewritten copy's error is the Edge rewrite's (a Node-only API reached from proxy.ts), not the app's code."
|
|
18236
|
+
);
|
|
18237
|
+
return failed("build", second.outcome.reason);
|
|
18238
|
+
}
|
|
17978
18239
|
return {
|
|
17979
18240
|
status: "success",
|
|
17980
18241
|
artifact: { kind: "cloudflare-worker", artifact: second.artifact },
|
|
@@ -18015,7 +18276,7 @@ function createAwsMiddlewareCloudflareAppStrategy() {
|
|
|
18015
18276
|
}
|
|
18016
18277
|
|
|
18017
18278
|
// ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
|
|
18018
|
-
import { join as
|
|
18279
|
+
import { join as join16 } from "path";
|
|
18019
18280
|
var VINEXT_MIN_NEXT_MAJOR = 16;
|
|
18020
18281
|
var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
|
|
18021
18282
|
var VINEXT_OUTPUT = {
|
|
@@ -18171,7 +18432,7 @@ function createVinextCloudflareStrategy(options) {
|
|
|
18171
18432
|
context,
|
|
18172
18433
|
{
|
|
18173
18434
|
appRoot: fork.appRoot,
|
|
18174
|
-
wranglerConfigPath:
|
|
18435
|
+
wranglerConfigPath: join16(
|
|
18175
18436
|
fork.appRoot,
|
|
18176
18437
|
"dist",
|
|
18177
18438
|
"server",
|
|
@@ -18196,7 +18457,7 @@ function createVinextCloudflareStrategy(options) {
|
|
|
18196
18457
|
// ../../packages/build/src/strategy/workspace.ts
|
|
18197
18458
|
import { execFile } from "child_process";
|
|
18198
18459
|
import { cp, mkdir as mkdir2, readdir as readdir4, rm as rm2 } from "fs/promises";
|
|
18199
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
18460
|
+
import { basename as basename2, dirname as dirname3, join as join17, relative as relative5, resolve as resolve6 } from "path";
|
|
18200
18461
|
import { promisify } from "util";
|
|
18201
18462
|
var execFileAsync = promisify(execFile);
|
|
18202
18463
|
var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
|
|
@@ -18219,14 +18480,14 @@ async function copyInstalledPackages(source, target2) {
|
|
|
18219
18480
|
const walk2 = async (dir) => {
|
|
18220
18481
|
for (const entry of await readdir4(dir, { withFileTypes: true })) {
|
|
18221
18482
|
if (!entry.isDirectory() || entry.name === ".git") continue;
|
|
18222
|
-
const path =
|
|
18483
|
+
const path = join17(dir, entry.name);
|
|
18223
18484
|
if (entry.name === "node_modules") installs.push(path);
|
|
18224
18485
|
else await walk2(path);
|
|
18225
18486
|
}
|
|
18226
18487
|
};
|
|
18227
18488
|
await walk2(origin);
|
|
18228
18489
|
for (const install of installs)
|
|
18229
|
-
await copyTree(install,
|
|
18490
|
+
await copyTree(install, join17(target2, relative5(origin, install)));
|
|
18230
18491
|
}
|
|
18231
18492
|
async function copyTree(from, to) {
|
|
18232
18493
|
await mkdir2(dirname3(to), { recursive: true });
|
|
@@ -18241,7 +18502,7 @@ async function copyTree(from, to) {
|
|
|
18241
18502
|
await cp(from, to, { recursive: true, verbatimSymlinks: true });
|
|
18242
18503
|
}
|
|
18243
18504
|
function forkedAppRoot(checkout, appRoot, forkCheckout) {
|
|
18244
|
-
return
|
|
18505
|
+
return join17(forkCheckout, relative5(checkout, appRoot));
|
|
18245
18506
|
}
|
|
18246
18507
|
|
|
18247
18508
|
// ../../packages/build/src/local-build-executor.ts
|
|
@@ -18263,7 +18524,7 @@ async function withRouteList(artifact, list, appRoot, observer) {
|
|
|
18263
18524
|
}
|
|
18264
18525
|
}
|
|
18265
18526
|
var defaultLocalBuildDependencies = {
|
|
18266
|
-
createWorkspace: () => mkdtemp(
|
|
18527
|
+
createWorkspace: () => mkdtemp(join18(tmpdir(), "next-deploy-build-")),
|
|
18267
18528
|
removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
|
|
18268
18529
|
runCommand,
|
|
18269
18530
|
detectPackageManager,
|
|
@@ -18297,8 +18558,8 @@ async function readInstalledNextVersion(appRoot, checkout) {
|
|
|
18297
18558
|
const stop = resolve7(checkout);
|
|
18298
18559
|
for (; ; ) {
|
|
18299
18560
|
try {
|
|
18300
|
-
const source = await
|
|
18301
|
-
|
|
18561
|
+
const source = await readFile13(
|
|
18562
|
+
join18(dir, "node_modules", "next", "package.json"),
|
|
18302
18563
|
"utf8"
|
|
18303
18564
|
);
|
|
18304
18565
|
const version2 = JSON.parse(source).version;
|
|
@@ -18391,8 +18652,8 @@ var LocalBuildExecutor = class {
|
|
|
18391
18652
|
}
|
|
18392
18653
|
async build(input, observer, cache) {
|
|
18393
18654
|
const workspace = await this.dependencies.createWorkspace();
|
|
18394
|
-
const checkout =
|
|
18395
|
-
const pmCacheDir =
|
|
18655
|
+
const checkout = join18(workspace, "checkout");
|
|
18656
|
+
const pmCacheDir = join18(workspace, ".pm-store");
|
|
18396
18657
|
let cacheSeq = 0;
|
|
18397
18658
|
const commandAvailable = this.dependencies.commandAvailable ?? hasCommand;
|
|
18398
18659
|
const compressor = "zstd";
|
|
@@ -18401,7 +18662,7 @@ var LocalBuildExecutor = class {
|
|
|
18401
18662
|
transport: cache,
|
|
18402
18663
|
fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
|
|
18403
18664
|
runCommand: this.dependencies.runCommand,
|
|
18404
|
-
tmpFile: (suffix) =>
|
|
18665
|
+
tmpFile: (suffix) => join18(workspace, `cache-${String(cacheSeq++)}${suffix}`),
|
|
18405
18666
|
// One latch for the whole build so a disabled cache warns once.
|
|
18406
18667
|
report: { unavailable: false }
|
|
18407
18668
|
} : null;
|
|
@@ -18480,6 +18741,7 @@ var LocalBuildExecutor = class {
|
|
|
18480
18741
|
checkout,
|
|
18481
18742
|
input.rootDirectory
|
|
18482
18743
|
);
|
|
18744
|
+
await assertAppRootInsideCheckout(checkout, appRoot);
|
|
18483
18745
|
const stacks = await import("./stack-PDV3NPWC.js");
|
|
18484
18746
|
const stack = await (this.dependencies.detectStack ?? stacks.detectStack)(
|
|
18485
18747
|
appRoot
|
|
@@ -18592,7 +18854,7 @@ var LocalBuildExecutor = class {
|
|
|
18592
18854
|
}
|
|
18593
18855
|
}
|
|
18594
18856
|
await notifyStatus(observer, "BUILDING");
|
|
18595
|
-
const buildCacheDir = resolved.preset.cachePath ?
|
|
18857
|
+
const buildCacheDir = resolved.preset.cachePath ? join18(appRoot, resolved.preset.cachePath) : null;
|
|
18596
18858
|
const buildCacheKey = sanitizeCacheKey(input.branch);
|
|
18597
18859
|
if (cachePorts && buildCacheDir) {
|
|
18598
18860
|
await tryRestore(
|
|
@@ -18616,13 +18878,13 @@ var LocalBuildExecutor = class {
|
|
|
18616
18878
|
entries: fingerprintEntries,
|
|
18617
18879
|
nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown"
|
|
18618
18880
|
});
|
|
18619
|
-
const pristine =
|
|
18881
|
+
const pristine = join18(workspace, "pristine");
|
|
18620
18882
|
const fork = this.dependencies.forkWorkspace ?? defaultFork;
|
|
18621
18883
|
await fork(checkout, pristine);
|
|
18622
18884
|
let forks = 0;
|
|
18623
18885
|
const resolverConcurrency = this.dependencies.resolverConcurrency ?? 1;
|
|
18624
18886
|
const forkWorkspace = async (label2) => {
|
|
18625
|
-
const target2 =
|
|
18887
|
+
const target2 = join18(
|
|
18626
18888
|
workspace,
|
|
18627
18889
|
"forks",
|
|
18628
18890
|
`${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
|
|
@@ -18910,12 +19172,12 @@ async function errorFromResponse(response, context) {
|
|
|
18910
19172
|
// ../../packages/sdk/src/session.ts
|
|
18911
19173
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
18912
19174
|
import { homedir } from "os";
|
|
18913
|
-
import { join as
|
|
19175
|
+
import { join as join19 } from "path";
|
|
18914
19176
|
function credentialsDir(home = homedir()) {
|
|
18915
|
-
return
|
|
19177
|
+
return join19(home, ".naro");
|
|
18916
19178
|
}
|
|
18917
19179
|
function credentialsFile(home = homedir()) {
|
|
18918
|
-
return
|
|
19180
|
+
return join19(credentialsDir(home), "credentials.json");
|
|
18919
19181
|
}
|
|
18920
19182
|
function saveSession(session, home = homedir()) {
|
|
18921
19183
|
mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
|
|
@@ -19351,6 +19613,14 @@ function openBrowser(url2) {
|
|
|
19351
19613
|
child.unref();
|
|
19352
19614
|
}
|
|
19353
19615
|
|
|
19616
|
+
// ../../packages/sdk/src/environments.ts
|
|
19617
|
+
function isReservedEnvTarget(value) {
|
|
19618
|
+
return RESERVED_ENVIRONMENT_SLUGS.includes(value);
|
|
19619
|
+
}
|
|
19620
|
+
function isEnvTargetName(value) {
|
|
19621
|
+
return environmentSlugProblem(value) !== "invalid";
|
|
19622
|
+
}
|
|
19623
|
+
|
|
19354
19624
|
// ../../packages/sdk/src/insights.ts
|
|
19355
19625
|
var SPEED_INSIGHTS_METRICS = [
|
|
19356
19626
|
"lcp",
|
|
@@ -19463,6 +19733,7 @@ var NaroClient = class {
|
|
|
19463
19733
|
insights;
|
|
19464
19734
|
analytics;
|
|
19465
19735
|
protection;
|
|
19736
|
+
environments;
|
|
19466
19737
|
constructor(options) {
|
|
19467
19738
|
this.baseUrl = (options.baseUrl ?? controlPlaneUrl()).replace(/\/$/, "");
|
|
19468
19739
|
this.getAccessToken = options.getAccessToken;
|
|
@@ -19481,6 +19752,7 @@ var NaroClient = class {
|
|
|
19481
19752
|
this.insights = new InsightsApi(this);
|
|
19482
19753
|
this.analytics = new AnalyticsApi(this);
|
|
19483
19754
|
this.protection = new ProtectionApi(this);
|
|
19755
|
+
this.environments = new EnvironmentsApi(this);
|
|
19484
19756
|
}
|
|
19485
19757
|
async request(path, options) {
|
|
19486
19758
|
const token = await this.getAccessToken();
|
|
@@ -19645,7 +19917,10 @@ var DeploymentsApi = class {
|
|
|
19645
19917
|
{
|
|
19646
19918
|
method: "POST",
|
|
19647
19919
|
// createDeploymentSchema accepts string | undefined — never null.
|
|
19648
|
-
body:
|
|
19920
|
+
body: {
|
|
19921
|
+
...options.branch ? { branch: options.branch } : {},
|
|
19922
|
+
...options.environment ? { environment: options.environment } : {}
|
|
19923
|
+
},
|
|
19649
19924
|
context: "Create deployment"
|
|
19650
19925
|
}
|
|
19651
19926
|
);
|
|
@@ -19729,6 +20004,8 @@ var DeploymentsApi = class {
|
|
|
19729
20004
|
}
|
|
19730
20005
|
}
|
|
19731
20006
|
};
|
|
20007
|
+
var STATIC_ASSETS_BINDING = "ASSETS";
|
|
20008
|
+
var MANAGED_DATABASE_BINDING = "DB";
|
|
19732
20009
|
var EnvApi = class {
|
|
19733
20010
|
constructor(client) {
|
|
19734
20011
|
this.client = client;
|
|
@@ -19758,18 +20035,69 @@ var EnvApi = class {
|
|
|
19758
20035
|
}
|
|
19759
20036
|
);
|
|
19760
20037
|
}
|
|
20038
|
+
/**
|
|
20039
|
+
* The names the env route refuses as a key — `409 VALIDATION_FAILED`
|
|
20040
|
+
* `<KEY> is already used by a storage binding.` — read the way it reads
|
|
20041
|
+
* them: `ASSETS` (the static files), every binding of the project's storage
|
|
20042
|
+
* and, on a project using the managed database (`database_provider` is
|
|
20043
|
+
* `d1`), `DB`, which may not be a storage row yet. For a caller that sends
|
|
20044
|
+
* many entries in several requests (`naro env import`) and has to refuse
|
|
20045
|
+
* before the first one is saved. A control plane whose project row has no
|
|
20046
|
+
* `database_provider` reads as having no managed database, so `DB` is only
|
|
20047
|
+
* left out there.
|
|
20048
|
+
*/
|
|
20049
|
+
async takenBindingNames(projectId) {
|
|
20050
|
+
const base2 = `/api/projects/${encodeURIComponent(projectId)}`;
|
|
20051
|
+
const [storage, project] = await Promise.all([
|
|
20052
|
+
this.client.request(
|
|
20053
|
+
`${base2}/storage`,
|
|
20054
|
+
{ context: "List storage" }
|
|
20055
|
+
),
|
|
20056
|
+
this.client.request(
|
|
20057
|
+
base2,
|
|
20058
|
+
{ context: "Read project" }
|
|
20059
|
+
)
|
|
20060
|
+
]);
|
|
20061
|
+
const names = /* @__PURE__ */ new Set([
|
|
20062
|
+
STATIC_ASSETS_BINDING,
|
|
20063
|
+
...storage.storage.map((row) => row.bindingName)
|
|
20064
|
+
]);
|
|
20065
|
+
if (project.project.database_provider === "d1")
|
|
20066
|
+
names.add(MANAGED_DATABASE_BINDING);
|
|
20067
|
+
return [...names];
|
|
20068
|
+
}
|
|
19761
20069
|
async removeById(projectId, envVarId) {
|
|
19762
20070
|
await this.client.request(
|
|
19763
20071
|
`/api/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(envVarId)}`,
|
|
19764
20072
|
{ method: "DELETE", context: "Remove environment variable" }
|
|
19765
20073
|
);
|
|
19766
20074
|
}
|
|
19767
|
-
/**
|
|
20075
|
+
/**
|
|
20076
|
+
* Removes a key's rows: the one `target` names, or, with no target, the
|
|
20077
|
+
* `production`, `preview` and `all` rows. An environment's value goes only
|
|
20078
|
+
* when its slug is the target, so removing a key everywhere else does not
|
|
20079
|
+
* take a QA or staging value with it. A key only environments hold fails
|
|
20080
|
+
* ENV_NOT_FOUND naming them (`details.environments`), not as if there were
|
|
20081
|
+
* none.
|
|
20082
|
+
*/
|
|
19768
20083
|
async remove(projectId, key, target2) {
|
|
19769
20084
|
const rows = await this.list(projectId);
|
|
19770
20085
|
const matches = rows.filter(
|
|
19771
|
-
(row) => row.key === key && (target2 === void 0
|
|
20086
|
+
(row) => row.key === key && (target2 === void 0 ? isReservedEnvTarget(row.target) : row.target === target2)
|
|
19772
20087
|
);
|
|
20088
|
+
if (matches.length === 0 && target2 === void 0) {
|
|
20089
|
+
const environments = [
|
|
20090
|
+
...new Set(
|
|
20091
|
+
rows.filter((row) => row.key === key).map((row) => row.target).filter((slug) => !isReservedEnvTarget(slug))
|
|
20092
|
+
)
|
|
20093
|
+
].toSorted();
|
|
20094
|
+
if (environments.length > 0)
|
|
20095
|
+
throw new NaroError(
|
|
20096
|
+
"ENV_NOT_FOUND",
|
|
20097
|
+
`No ${key} variable found outside named environments. Pass the environment to remove it: ${environments.join(", ")}.`,
|
|
20098
|
+
{ details: { key, environments } }
|
|
20099
|
+
);
|
|
20100
|
+
}
|
|
19773
20101
|
if (matches.length === 0) {
|
|
19774
20102
|
throw new NaroError(
|
|
19775
20103
|
"ENV_NOT_FOUND",
|
|
@@ -20061,6 +20389,55 @@ var ProtectionApi = class {
|
|
|
20061
20389
|
);
|
|
20062
20390
|
}
|
|
20063
20391
|
};
|
|
20392
|
+
var EnvironmentsApi = class {
|
|
20393
|
+
constructor(client) {
|
|
20394
|
+
this.client = client;
|
|
20395
|
+
}
|
|
20396
|
+
base(projectId) {
|
|
20397
|
+
return `/api/projects/${encodeURIComponent(projectId)}/environments`;
|
|
20398
|
+
}
|
|
20399
|
+
/** The project's environments and how many its plan allows (null: no limit). */
|
|
20400
|
+
async list(projectId) {
|
|
20401
|
+
return this.client.request(this.base(projectId), {
|
|
20402
|
+
context: "List environments"
|
|
20403
|
+
});
|
|
20404
|
+
}
|
|
20405
|
+
/** One environment by its slug, from the list: the routes address it by id. */
|
|
20406
|
+
async get(projectId, slug) {
|
|
20407
|
+
const { environments } = await this.list(projectId);
|
|
20408
|
+
const environment = environments.find((entry) => entry.slug === slug);
|
|
20409
|
+
if (!environment)
|
|
20410
|
+
throw new NaroError("ENVIRONMENT_NOT_FOUND", "Environment not found.");
|
|
20411
|
+
return environment;
|
|
20412
|
+
}
|
|
20413
|
+
async create(projectId, input) {
|
|
20414
|
+
const body = await this.client.request(
|
|
20415
|
+
this.base(projectId),
|
|
20416
|
+
{
|
|
20417
|
+
method: "POST",
|
|
20418
|
+
// JSON drops a key that is undefined and keeps a null, which is what
|
|
20419
|
+
// the route reads: no copyEnvFrom is "preview", null copies nothing.
|
|
20420
|
+
body: input,
|
|
20421
|
+
context: "Create environment"
|
|
20422
|
+
}
|
|
20423
|
+
);
|
|
20424
|
+
return body.environment;
|
|
20425
|
+
}
|
|
20426
|
+
async update(projectId, environmentId, patch) {
|
|
20427
|
+
const body = await this.client.request(
|
|
20428
|
+
`${this.base(projectId)}/${encodeURIComponent(environmentId)}`,
|
|
20429
|
+
{ method: "PATCH", body: patch, context: "Update environment" }
|
|
20430
|
+
);
|
|
20431
|
+
return body.environment;
|
|
20432
|
+
}
|
|
20433
|
+
/** Removes the environment, its worker and its protection. */
|
|
20434
|
+
async delete(projectId, environmentId) {
|
|
20435
|
+
await this.client.request(
|
|
20436
|
+
`${this.base(projectId)}/${encodeURIComponent(environmentId)}`,
|
|
20437
|
+
{ method: "DELETE", context: "Delete environment" }
|
|
20438
|
+
);
|
|
20439
|
+
}
|
|
20440
|
+
};
|
|
20064
20441
|
var EXPORT_FIRST_POLL_MS = 1e3;
|
|
20065
20442
|
var EXPORT_MAX_POLL_MS = 5e3;
|
|
20066
20443
|
var EXPORT_TIMEOUT_MS = 15 * 6e4;
|
|
@@ -20245,10 +20622,12 @@ var DbApi = class {
|
|
|
20245
20622
|
* `d1_migrations` — all operations or none. `dryRun` returns the SQL and
|
|
20246
20623
|
* the name without applying anything. Save `sql` as `migrations/<name>` so
|
|
20247
20624
|
* `naro db reset` re-applies it; a refused operation's index is in
|
|
20248
|
-
* `details.operation
|
|
20249
|
-
*
|
|
20250
|
-
*
|
|
20251
|
-
*
|
|
20625
|
+
* `details.operation`, or — refused by the policy translation, or failed
|
|
20626
|
+
* in SQLite — in `statementIndex` (one statement per operation). The Data API policies go in the same migration: a
|
|
20627
|
+
* rename carries the table's and rewrites the others that read it, a
|
|
20628
|
+
* dropped table's go with it, and what an earlier table left under a name
|
|
20629
|
+
* the change gives is removed — `removedPolicies` lists what went. A change
|
|
20630
|
+
* that fails changes none of them.
|
|
20252
20631
|
*/
|
|
20253
20632
|
async schema(projectId, operations, options = {}) {
|
|
20254
20633
|
return this.client.request(
|
|
@@ -20992,40 +21371,92 @@ function summarizeStatement(statement2) {
|
|
|
20992
21371
|
|
|
20993
21372
|
// ../../packages/sdk/src/dotenv.ts
|
|
20994
21373
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
20995
|
-
import { join as
|
|
21374
|
+
import { join as join20 } from "path";
|
|
21375
|
+
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/dgm;
|
|
21376
|
+
var QUOTES = /* @__PURE__ */ new Set(["'", '"', "`"]);
|
|
21377
|
+
function parseDotenvDetailed(text2) {
|
|
21378
|
+
const vars = {};
|
|
21379
|
+
const unclosedQuotes = [];
|
|
21380
|
+
const lines = text2.replace(/\r\n?/g, "\n");
|
|
21381
|
+
let line = 1;
|
|
21382
|
+
let counted = 0;
|
|
21383
|
+
for (const match of lines.matchAll(LINE)) {
|
|
21384
|
+
const key = match[1];
|
|
21385
|
+
const raw = match[2] ?? "";
|
|
21386
|
+
let value = raw.trim();
|
|
21387
|
+
const maybeQuote = value[0];
|
|
21388
|
+
if (maybeQuote !== void 0 && QUOTES.has(maybeQuote) && !value.includes(maybeQuote, 1)) {
|
|
21389
|
+
const at = (match.indices?.[2]?.[0] ?? match.index) + (raw.length - raw.trimStart().length);
|
|
21390
|
+
for (; counted < at; counted += 1) if (lines[counted] === "\n") line += 1;
|
|
21391
|
+
unclosedQuotes.push(line);
|
|
21392
|
+
}
|
|
21393
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
|
21394
|
+
if (maybeQuote === '"')
|
|
21395
|
+
value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
|
|
21396
|
+
vars[key] = value;
|
|
21397
|
+
}
|
|
21398
|
+
return { vars, unclosedQuotes };
|
|
21399
|
+
}
|
|
20996
21400
|
function parseDotenv(text2) {
|
|
20997
|
-
|
|
20998
|
-
for (const line of text2.split(/\r?\n/)) {
|
|
20999
|
-
const trimmed = line.trim();
|
|
21000
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
21001
|
-
const eq = trimmed.indexOf("=");
|
|
21002
|
-
if (eq <= 0) continue;
|
|
21003
|
-
const key = trimmed.slice(0, eq).trim();
|
|
21004
|
-
let value = trimmed.slice(eq + 1).trim();
|
|
21005
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
|
|
21006
|
-
value = value.slice(1, -1);
|
|
21007
|
-
out[key] = value;
|
|
21008
|
-
}
|
|
21009
|
-
return out;
|
|
21401
|
+
return parseDotenvDetailed(text2).vars;
|
|
21010
21402
|
}
|
|
21011
21403
|
function readProjectEnv(dir) {
|
|
21012
21404
|
for (const name of [".env.local", ".env.production", ".env"]) {
|
|
21013
|
-
const path =
|
|
21405
|
+
const path = join20(dir, name);
|
|
21014
21406
|
if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
|
|
21015
21407
|
}
|
|
21016
21408
|
return {};
|
|
21017
21409
|
}
|
|
21410
|
+
var PLAIN = /^[^\s"'#$`\\]*$/;
|
|
21411
|
+
function bareReadsBack(value) {
|
|
21412
|
+
return !/[#\r\n\u2028\u2029]/.test(value) && value.trim() === value && !QUOTES.has(value[0] ?? "");
|
|
21413
|
+
}
|
|
21414
|
+
function dotenvValue(value) {
|
|
21415
|
+
if (PLAIN.test(value)) return value;
|
|
21416
|
+
const endsInBackslash = value.endsWith("\\");
|
|
21417
|
+
if (!value.includes("\r") && !endsInBackslash) {
|
|
21418
|
+
if (!value.includes("'")) return `'${value}'`;
|
|
21419
|
+
if (!value.includes("`")) return `\`${value}\``;
|
|
21420
|
+
}
|
|
21421
|
+
if (!value.includes('"') && !/\\[nr]/.test(value) && !endsInBackslash)
|
|
21422
|
+
return `"${value.replaceAll("\r", "\\r")}"`;
|
|
21423
|
+
if (bareReadsBack(value)) return value;
|
|
21424
|
+
return void 0;
|
|
21425
|
+
}
|
|
21426
|
+
var UNWRITABLE_HINT = "Store them without carriage returns or one of the quote characters (for a file: tr -d '\\r' < sa.json | naro env add KEY), or read them with naro env list --reveal.";
|
|
21018
21427
|
function serializeDotenv(vars) {
|
|
21019
|
-
const
|
|
21020
|
-
|
|
21021
|
-
|
|
21022
|
-
|
|
21428
|
+
const lines = [];
|
|
21429
|
+
const allQuotes = [];
|
|
21430
|
+
const other = [];
|
|
21431
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
21432
|
+
const written = dotenvValue(value);
|
|
21433
|
+
if (written !== void 0) lines.push(`${key}=${written}`);
|
|
21434
|
+
else if (value.includes("'") && value.includes('"') && value.includes("`"))
|
|
21435
|
+
allQuotes.push(key);
|
|
21436
|
+
else other.push(key);
|
|
21437
|
+
}
|
|
21438
|
+
const problems2 = [];
|
|
21439
|
+
if (allQuotes.length > 0)
|
|
21440
|
+
problems2.push(
|
|
21441
|
+
`Cannot write ${allQuotes.join(", ")} to a dotenv file: their values hold ', " and \` together.`
|
|
21442
|
+
);
|
|
21443
|
+
if (other.length > 0)
|
|
21444
|
+
problems2.push(
|
|
21445
|
+
`Cannot write ${other.join(", ")} to a dotenv file: dotenv would not read their values back unchanged.`
|
|
21446
|
+
);
|
|
21447
|
+
if (problems2.length > 0)
|
|
21448
|
+
throw new NaroError(
|
|
21449
|
+
"VALIDATION_FAILED",
|
|
21450
|
+
`${problems2.join(" ")} ${UNWRITABLE_HINT}`,
|
|
21451
|
+
{ details: { keys: [...allQuotes, ...other] } }
|
|
21452
|
+
);
|
|
21453
|
+
return `${lines.join("\n")}
|
|
21023
21454
|
`;
|
|
21024
21455
|
}
|
|
21025
21456
|
|
|
21026
21457
|
// ../../packages/sdk/src/env-scan.ts
|
|
21027
21458
|
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
|
|
21028
|
-
import { join as
|
|
21459
|
+
import { join as join21 } from "path";
|
|
21029
21460
|
var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
|
|
21030
21461
|
".ts",
|
|
21031
21462
|
".tsx",
|
|
@@ -21080,7 +21511,7 @@ function collect(dir, out) {
|
|
|
21080
21511
|
for (const entry of entries) {
|
|
21081
21512
|
if (out.length >= MAX_FILES) return;
|
|
21082
21513
|
if (entry.isSymbolicLink()) continue;
|
|
21083
|
-
const path =
|
|
21514
|
+
const path = join21(dir, entry.name);
|
|
21084
21515
|
if (entry.isDirectory()) {
|
|
21085
21516
|
if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
|
|
21086
21517
|
continue;
|
|
@@ -21112,7 +21543,7 @@ function scanEnvReferences(dir) {
|
|
|
21112
21543
|
}
|
|
21113
21544
|
}
|
|
21114
21545
|
for (const name of EXAMPLE_FILES) {
|
|
21115
|
-
const path =
|
|
21546
|
+
const path = join21(dir, name);
|
|
21116
21547
|
if (!existsSync2(path)) continue;
|
|
21117
21548
|
let parsed;
|
|
21118
21549
|
try {
|
|
@@ -21130,11 +21561,11 @@ function scanEnvReferences(dir) {
|
|
|
21130
21561
|
|
|
21131
21562
|
// ../../packages/sdk/src/framework.ts
|
|
21132
21563
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
21133
|
-
import { join as
|
|
21564
|
+
import { join as join22 } from "path";
|
|
21134
21565
|
function readPackageJson2(dir) {
|
|
21135
21566
|
try {
|
|
21136
21567
|
return JSON.parse(
|
|
21137
|
-
readFileSync4(
|
|
21568
|
+
readFileSync4(join22(dir, "package.json"), "utf8")
|
|
21138
21569
|
);
|
|
21139
21570
|
} catch {
|
|
21140
21571
|
return null;
|
|
@@ -21145,7 +21576,7 @@ function hasDependency(pkg, name) {
|
|
|
21145
21576
|
}
|
|
21146
21577
|
function hasConfig(dir, base2, evidence) {
|
|
21147
21578
|
for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
|
|
21148
|
-
if (existsSync3(
|
|
21579
|
+
if (existsSync3(join22(dir, `${base2}.${ext}`))) {
|
|
21149
21580
|
evidence.push(`${base2}.${ext}`);
|
|
21150
21581
|
return true;
|
|
21151
21582
|
}
|
|
@@ -21253,8 +21684,8 @@ function detectFramework2(dir) {
|
|
|
21253
21684
|
if (!byDependency && !byConfig) continue;
|
|
21254
21685
|
if (byDependency) evidence.push(`package.json: ${detector.dependency}`);
|
|
21255
21686
|
if (detector.framework === "nextjs") {
|
|
21256
|
-
if (existsSync3(
|
|
21257
|
-
if (existsSync3(
|
|
21687
|
+
if (existsSync3(join22(dir, "app"))) evidence.push("app/");
|
|
21688
|
+
if (existsSync3(join22(dir, "pages"))) evidence.push("pages/");
|
|
21258
21689
|
}
|
|
21259
21690
|
return {
|
|
21260
21691
|
framework: detector.framework,
|
|
@@ -21265,7 +21696,7 @@ function detectFramework2(dir) {
|
|
|
21265
21696
|
deployable: true
|
|
21266
21697
|
};
|
|
21267
21698
|
}
|
|
21268
|
-
if (!pkg && existsSync3(
|
|
21699
|
+
if (!pkg && existsSync3(join22(dir, "index.html"))) {
|
|
21269
21700
|
evidence.push("index.html (no package.json)");
|
|
21270
21701
|
return {
|
|
21271
21702
|
framework: "static",
|
|
@@ -21286,8 +21717,26 @@ function detectFramework2(dir) {
|
|
|
21286
21717
|
};
|
|
21287
21718
|
}
|
|
21288
21719
|
|
|
21720
|
+
// ../../packages/data-core/src/unicode-controls.ts
|
|
21721
|
+
function hasControlCharacter(text2) {
|
|
21722
|
+
for (let i = 0; i < text2.length; i += 1) {
|
|
21723
|
+
const code = text2.charCodeAt(i);
|
|
21724
|
+
if (code <= 31 || code >= 127 && code <= 159) return true;
|
|
21725
|
+
}
|
|
21726
|
+
return false;
|
|
21727
|
+
}
|
|
21728
|
+
function hasBidiControl(text2) {
|
|
21729
|
+
for (let i = 0; i < text2.length; i += 1) {
|
|
21730
|
+
const code = text2.charCodeAt(i);
|
|
21731
|
+
if (code === 1564 || code === 8206 || code === 8207 || code >= 8234 && code <= 8238 || code >= 8294 && code <= 8297)
|
|
21732
|
+
return true;
|
|
21733
|
+
}
|
|
21734
|
+
return false;
|
|
21735
|
+
}
|
|
21736
|
+
|
|
21289
21737
|
// ../../packages/data-core/src/storage-path.ts
|
|
21290
21738
|
var MAX_SINGLE_UPLOAD_BYTES = 52428800;
|
|
21739
|
+
var MAX_OBJECT_NAME_BYTES = 900;
|
|
21291
21740
|
var STORAGE_RESERVED_WORDS = [
|
|
21292
21741
|
"authenticated",
|
|
21293
21742
|
"copy",
|
|
@@ -21303,6 +21752,32 @@ var RESERVED = new Set(STORAGE_RESERVED_WORDS);
|
|
|
21303
21752
|
var ENCODER = new TextEncoder();
|
|
21304
21753
|
var LONE_SURROGATE2 = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
|
|
21305
21754
|
var LONE_SURROGATES = new RegExp(LONE_SURROGATE2.source, "g");
|
|
21755
|
+
function hasLoneSurrogate(text2) {
|
|
21756
|
+
return LONE_SURROGATE2.test(text2);
|
|
21757
|
+
}
|
|
21758
|
+
function refuse(reason) {
|
|
21759
|
+
return { ok: false, reason };
|
|
21760
|
+
}
|
|
21761
|
+
function checkWellFormed(name) {
|
|
21762
|
+
if (name === "") return refuse("is empty");
|
|
21763
|
+
if (name.length > MAX_OBJECT_NAME_BYTES || ENCODER.encode(name).length > MAX_OBJECT_NAME_BYTES)
|
|
21764
|
+
return refuse(`is longer than ${String(MAX_OBJECT_NAME_BYTES)} bytes`);
|
|
21765
|
+
if (name.startsWith("/") || name.endsWith("/"))
|
|
21766
|
+
return refuse("starts or ends with /");
|
|
21767
|
+
if (name.includes("//")) return refuse("has an empty segment (//)");
|
|
21768
|
+
for (const segment of name.split("/"))
|
|
21769
|
+
if (segment === "." || segment === "..")
|
|
21770
|
+
return refuse("has a . or .. segment");
|
|
21771
|
+
if (hasControlCharacter(name)) return refuse("has a control character");
|
|
21772
|
+
if (name.includes("\\")) return refuse("has a backslash");
|
|
21773
|
+
if (hasBidiControl(name))
|
|
21774
|
+
return refuse("has a bidirectional control character");
|
|
21775
|
+
return { ok: true, name };
|
|
21776
|
+
}
|
|
21777
|
+
function checkObjectName(name) {
|
|
21778
|
+
if (hasLoneSurrogate(name)) return refuse("is not valid UTF-8");
|
|
21779
|
+
return checkWellFormed(name);
|
|
21780
|
+
}
|
|
21306
21781
|
function objectParent(name) {
|
|
21307
21782
|
const slash = name.lastIndexOf("/");
|
|
21308
21783
|
return slash < 0 ? "" : name.slice(0, slash);
|
|
@@ -21332,6 +21807,7 @@ var STORAGE_OBJECTS = "storage.objects";
|
|
|
21332
21807
|
var STORAGE_OBJECTS_TABLE = "_naro_storage_objects";
|
|
21333
21808
|
var STORAGE_OBJECTS_QUALIFIER = "objects";
|
|
21334
21809
|
var STORAGE_UNAVAILABLE_MESSAGE = "storage.objects is not available on this backend yet. Run `naro backend enable` to update it.";
|
|
21810
|
+
var STORAGE_UNAVAILABLE_REASON = `${STORAGE_OBJECTS} is not available on this backend yet`;
|
|
21335
21811
|
var STORAGE_BUCKETS_TABLE = "_naro_storage_buckets";
|
|
21336
21812
|
var STORAGE_POLICY_COLUMNS = [
|
|
21337
21813
|
"id",
|
|
@@ -21499,7 +21975,8 @@ var CATALOG = {
|
|
|
21499
21975
|
policy,
|
|
21500
21976
|
recursion,
|
|
21501
21977
|
values,
|
|
21502
|
-
bytes
|
|
21978
|
+
bytes,
|
|
21979
|
+
depth
|
|
21503
21980
|
}) => {
|
|
21504
21981
|
if (recursion)
|
|
21505
21982
|
return `Infinite recursion detected in policy for relation ${table}.`;
|
|
@@ -21507,6 +21984,8 @@ var CATALOG = {
|
|
|
21507
21984
|
return `The policies of ${table} bind more than ${values === true ? "50" : String(values)} values in one statement.`;
|
|
21508
21985
|
if (bytes)
|
|
21509
21986
|
return `The policies of ${table} write more than ${bytes === true ? "50000" : String(bytes)} bytes of SQL into one statement.`;
|
|
21987
|
+
if (depth)
|
|
21988
|
+
return `The policies of ${table} nest an expression more than 100 deep into one statement.`;
|
|
21510
21989
|
if (policy !== void 0)
|
|
21511
21990
|
return `Policy ${policy} on ${table} cannot be applied.`;
|
|
21512
21991
|
return condition ? `The ${action} policy of ${table} has a condition that cannot be applied.` : `The ${action} policy of ${table} is owner but has no valid owner column.`;
|
|
@@ -21564,7 +22043,7 @@ var CATALOG = {
|
|
|
21564
22043
|
},
|
|
21565
22044
|
OBJECT_TOO_LARGE: {
|
|
21566
22045
|
status: 413,
|
|
21567
|
-
message: (vars) => "single" in vars ? "Uploads over 50 MiB go in parts: use the multipart upload." : `The object is ${String(vars.size)} bytes; bucket ${storageName(vars.bucket)} takes objects up to ${String(vars.max)} bytes.`
|
|
22046
|
+
message: (vars) => "single" in vars ? "Uploads over 50 MiB go in parts: use the multipart upload." : "transfer" in vars ? "Objects over 5,363,466,240 bytes (5 GiB \u2212 5 MiB) cannot be copied or moved." : `The object is ${String(vars.size)} bytes; bucket ${storageName(vars.bucket)} takes objects up to ${String(vars.max)} bytes.`
|
|
21568
22047
|
},
|
|
21569
22048
|
MIME_TYPE_NOT_ALLOWED: {
|
|
21570
22049
|
status: 415,
|
|
@@ -21576,7 +22055,7 @@ var CATALOG = {
|
|
|
21576
22055
|
},
|
|
21577
22056
|
OBJECT_EXISTS: {
|
|
21578
22057
|
status: 409,
|
|
21579
|
-
message: ({ bucket, path }) => `Object ${storageName(path)} already exists in bucket ${storageName(bucket)}. Upload with upsert to replace it
|
|
22058
|
+
message: ({ bucket, path, move }) => `Object ${storageName(path)} already exists in bucket ${storageName(bucket)}. ${move === true ? "A move never replaces an object: remove it or choose another name." : "Upload with upsert to replace it."}`
|
|
21580
22059
|
},
|
|
21581
22060
|
OBJECT_BUSY: {
|
|
21582
22061
|
status: 409,
|
|
@@ -22254,6 +22733,15 @@ function resolvePolicyTable(snapshot, name) {
|
|
|
22254
22733
|
if (folded === REALTIME_MESSAGES) return REALTIME_MESSAGES_TABLE;
|
|
22255
22734
|
return resolveTable(snapshot, name);
|
|
22256
22735
|
}
|
|
22736
|
+
function resolveSubqueryTable(snapshot, name) {
|
|
22737
|
+
if (asciiLower(name) === STORAGE_OBJECTS)
|
|
22738
|
+
return snapshot.storageSubqueries === false ? void 0 : snapshot.storage;
|
|
22739
|
+
return resolveTable(snapshot, name);
|
|
22740
|
+
}
|
|
22741
|
+
function subqueryName(subquery) {
|
|
22742
|
+
if (subquery.alias !== null) return subquery.alias;
|
|
22743
|
+
return asciiLower(subquery.table) === STORAGE_OBJECTS ? STORAGE_OBJECTS_QUALIFIER : subquery.table;
|
|
22744
|
+
}
|
|
22257
22745
|
async function readTableNames(db) {
|
|
22258
22746
|
return tableNamesFromRows(rowsOf(await db.prepare(TABLE_NAMES_SQL).all()));
|
|
22259
22747
|
}
|
|
@@ -22295,6 +22783,14 @@ function utf8ByteLength(text2) {
|
|
|
22295
22783
|
function quoteIdentifier(name) {
|
|
22296
22784
|
return `"${name.replaceAll('"', '""')}"`;
|
|
22297
22785
|
}
|
|
22786
|
+
var SIDE_BY_SIDE = 8;
|
|
22787
|
+
function joinConditions(terms, op) {
|
|
22788
|
+
if (terms.length <= SIDE_BY_SIDE) return `(${terms.join(` ${op} `)})`;
|
|
22789
|
+
const groups = [];
|
|
22790
|
+
for (let at = 0; at < terms.length; at += SIDE_BY_SIDE)
|
|
22791
|
+
groups.push(joinConditions(terms.slice(at, at + SIDE_BY_SIDE), op));
|
|
22792
|
+
return joinConditions(groups, op);
|
|
22793
|
+
}
|
|
22298
22794
|
var Failure = class extends Error {
|
|
22299
22795
|
constructor(error51, reason) {
|
|
22300
22796
|
super(error51.code);
|
|
@@ -22478,6 +22974,12 @@ var VALUE_KINDS = /* @__PURE__ */ new Set([
|
|
|
22478
22974
|
function isValueExpr(expr) {
|
|
22479
22975
|
return VALUE_KINDS.has(expr.kind);
|
|
22480
22976
|
}
|
|
22977
|
+
function comparedValue(value) {
|
|
22978
|
+
let current = value;
|
|
22979
|
+
while (current.kind === "func" && current.name === "coalesce" && current.args.length === 1 && current.args[0] !== void 0)
|
|
22980
|
+
current = current.args[0];
|
|
22981
|
+
return current;
|
|
22982
|
+
}
|
|
22481
22983
|
function isConditionExpr(expr) {
|
|
22482
22984
|
if (!isValueExpr(expr)) return true;
|
|
22483
22985
|
if (expr.kind === "column") return true;
|
|
@@ -22529,7 +23031,6 @@ var SPECIAL_WORDS = /* @__PURE__ */ new Set([
|
|
|
22529
23031
|
"auth"
|
|
22530
23032
|
]);
|
|
22531
23033
|
var TEXT_TYPES = /* @__PURE__ */ new Set(["text", "varchar", "uuid"]);
|
|
22532
|
-
var STORAGE_SUBQUERY_PROBLEM = "A policy cannot read storage.objects in a subquery yet";
|
|
22533
23034
|
var FOLDERNAME_INDEX_PROBLEM = `storage.foldername(name) is available only as (storage.foldername(name))[n] with n from 1 to ${String(MAX_FOLDERNAME_INDEX)}`;
|
|
22534
23035
|
function storageHelperColumnProblem(fn) {
|
|
22535
23036
|
return `storage.${fn}() takes the name column of storage.objects`;
|
|
@@ -22980,8 +23481,12 @@ function atRealtimeMessages(stream) {
|
|
|
22980
23481
|
}
|
|
22981
23482
|
function readTableName(stream) {
|
|
22982
23483
|
const first = stream.peek();
|
|
22983
|
-
if (atStorageObjects(stream))
|
|
22984
|
-
|
|
23484
|
+
if (atStorageObjects(stream)) {
|
|
23485
|
+
stream.next();
|
|
23486
|
+
stream.next();
|
|
23487
|
+
stream.next();
|
|
23488
|
+
return STORAGE_OBJECTS;
|
|
23489
|
+
}
|
|
22985
23490
|
if (atRealtimeMessages(stream))
|
|
22986
23491
|
throw new PolicySyntaxError(REALTIME_SUBQUERY_PROBLEM, first.position);
|
|
22987
23492
|
let token = first;
|
|
@@ -23008,12 +23513,6 @@ function readTableName(stream) {
|
|
|
23008
23513
|
return name;
|
|
23009
23514
|
}
|
|
23010
23515
|
function readPolicyTarget(stream) {
|
|
23011
|
-
if (atStorageObjects(stream)) {
|
|
23012
|
-
stream.next();
|
|
23013
|
-
stream.next();
|
|
23014
|
-
stream.next();
|
|
23015
|
-
return STORAGE_OBJECTS;
|
|
23016
|
-
}
|
|
23017
23516
|
if (atRealtimeMessages(stream)) {
|
|
23018
23517
|
stream.next();
|
|
23019
23518
|
stream.next();
|
|
@@ -23433,9 +23932,10 @@ function printValue(expr) {
|
|
|
23433
23932
|
}
|
|
23434
23933
|
function printSubquery(subquery) {
|
|
23435
23934
|
const select = subquery.select === "*" ? "*" : printValue(subquery.select);
|
|
23935
|
+
const table = asciiLower(subquery.table) === STORAGE_OBJECTS ? STORAGE_OBJECTS : quoteIdent(subquery.table);
|
|
23436
23936
|
const alias = subquery.alias === null ? "" : ` ${quoteIdent(subquery.alias)}`;
|
|
23437
23937
|
const where = subquery.where === null ? "" : ` WHERE ${printExpr(subquery.where)}`;
|
|
23438
|
-
return `SELECT ${select} FROM ${
|
|
23938
|
+
return `SELECT ${select} FROM ${table}${alias}${where}`;
|
|
23439
23939
|
}
|
|
23440
23940
|
function printElement(element) {
|
|
23441
23941
|
if (typeof element === "object") return printValue(element);
|
|
@@ -23788,6 +24288,9 @@ var FILTER_OPERATORS = [
|
|
|
23788
24288
|
"containedBy",
|
|
23789
24289
|
"overlaps"
|
|
23790
24290
|
];
|
|
24291
|
+
function leastFilters(firstColumn) {
|
|
24292
|
+
return firstColumn === void 0 ? [] : [{ operator: "is", column: firstColumn, value: null }];
|
|
24293
|
+
}
|
|
23791
24294
|
function isAuthUid(value) {
|
|
23792
24295
|
return isPlainObject2(value) && Object.keys(value).length === 1 && Object.hasOwn(value, "auth") && value.auth === "uid";
|
|
23793
24296
|
}
|
|
@@ -24401,12 +24904,21 @@ function storedSource(table, names, alias) {
|
|
|
24401
24904
|
function tableLabel(table) {
|
|
24402
24905
|
return table.name === STORAGE_OBJECTS_TABLE ? STORAGE_OBJECTS : table.name;
|
|
24403
24906
|
}
|
|
24907
|
+
function storageReadySql(alias) {
|
|
24908
|
+
return `(${quoteIdentifier(alias)}.${quoteIdentifier("status")} = 'ready')`;
|
|
24909
|
+
}
|
|
24910
|
+
function ownNames(table) {
|
|
24911
|
+
return table.name === STORAGE_OBJECTS_TABLE ? [STORAGE_OBJECTS_QUALIFIER] : [asciiLower(table.name)];
|
|
24912
|
+
}
|
|
24404
24913
|
function missingColumnReason(name, tables) {
|
|
24405
24914
|
return `column ${name} does not exist on ${tables.join(" or ")}`;
|
|
24406
24915
|
}
|
|
24407
24916
|
function missingRelationReason(table) {
|
|
24408
24917
|
return `relation ${table} does not exist (a system table cannot be read either)`;
|
|
24409
24918
|
}
|
|
24919
|
+
function unresolvedSubqueryReason(table) {
|
|
24920
|
+
return asciiLower(table) === STORAGE_OBJECTS ? STORAGE_UNAVAILABLE_REASON : missingRelationReason(table);
|
|
24921
|
+
}
|
|
24410
24922
|
function resolveColumn(env, ref) {
|
|
24411
24923
|
const name = asciiLower(ref.name);
|
|
24412
24924
|
if (ref.qualifier !== null) {
|
|
@@ -24516,10 +25028,10 @@ function storageHelperSql(fn, index, operand) {
|
|
|
24516
25028
|
}
|
|
24517
25029
|
function storageCallSql(env, expr) {
|
|
24518
25030
|
const problem = storageHelperColumnProblem(expr.fn);
|
|
24519
|
-
if (
|
|
25031
|
+
if (!env.scopes.some((scope) => scope.table.name === STORAGE_OBJECTS_TABLE))
|
|
24520
25032
|
return blamed(env, problem);
|
|
24521
25033
|
const { source, column: column2 } = resolveColumn(env, expr.column);
|
|
24522
|
-
if (source
|
|
25034
|
+
if (source.table.name !== STORAGE_OBJECTS_TABLE || column2.name !== "name")
|
|
24523
25035
|
return blamed(env, problem);
|
|
24524
25036
|
const index = expr.fn === "foldername" ? expr.index : null;
|
|
24525
25037
|
if (expr.fn === "foldername" ? index === null || !Number.isInteger(index) || index < 1 || index > MAX_FOLDERNAME_INDEX : expr.index !== null)
|
|
@@ -24533,7 +25045,8 @@ function storageCallSql(env, expr) {
|
|
|
24533
25045
|
})
|
|
24534
25046
|
);
|
|
24535
25047
|
}
|
|
24536
|
-
function operandSide(env,
|
|
25048
|
+
function operandSide(env, value) {
|
|
25049
|
+
const expr = comparedValue(value);
|
|
24537
25050
|
if (expr.kind === "cast") return operandSide(env, expr.operand);
|
|
24538
25051
|
if (expr.kind !== "column" && expr.kind !== "realtime")
|
|
24539
25052
|
return {
|
|
@@ -24594,7 +25107,10 @@ function conditionSql(env, expr) {
|
|
|
24594
25107
|
switch (expr.kind) {
|
|
24595
25108
|
case "and":
|
|
24596
25109
|
case "or":
|
|
24597
|
-
return
|
|
25110
|
+
return joinConditions(
|
|
25111
|
+
expr.args.map((arg) => conditionSql(env, arg)),
|
|
25112
|
+
expr.kind === "and" ? "AND" : "OR"
|
|
25113
|
+
);
|
|
24598
25114
|
case "not":
|
|
24599
25115
|
return `(NOT ${conditionSql(env, expr.arg)})`;
|
|
24600
25116
|
default:
|
|
@@ -24677,8 +25193,8 @@ function conditionSql(env, expr) {
|
|
|
24677
25193
|
}
|
|
24678
25194
|
}
|
|
24679
25195
|
function subquerySql(env, subquery, select) {
|
|
24680
|
-
const table =
|
|
24681
|
-
if (!table) return blamed(env,
|
|
25196
|
+
const table = resolveSubqueryTable(env.snapshot, subquery.table);
|
|
25197
|
+
if (!table) return blamed(env, unresolvedSubqueryReason(subquery.table));
|
|
24682
25198
|
const { state } = env;
|
|
24683
25199
|
if (++state.subqueries > MAX_STATEMENT_SUBQUERIES)
|
|
24684
25200
|
return blamed(env, STATEMENT_SUBQUERIES_REASON);
|
|
@@ -24692,7 +25208,7 @@ function subquerySql(env, subquery, select) {
|
|
|
24692
25208
|
const alias = `_naro_s${String(++state.aliases)}`;
|
|
24693
25209
|
const source = storedSource(
|
|
24694
25210
|
table,
|
|
24695
|
-
[asciiLower(subquery
|
|
25211
|
+
[asciiLower(subqueryName(subquery))],
|
|
24696
25212
|
alias
|
|
24697
25213
|
);
|
|
24698
25214
|
const inner = { ...env, scopes: [...env.scopes, source] };
|
|
@@ -24704,11 +25220,12 @@ function subquerySql(env, subquery, select) {
|
|
|
24704
25220
|
selected = valueSql(inner, subquery.select);
|
|
24705
25221
|
if (env.check) item = operandSide(inner, subquery.select);
|
|
24706
25222
|
}
|
|
24707
|
-
const own2 = storedSource(table,
|
|
25223
|
+
const own2 = storedSource(table, ownNames(table), alias);
|
|
24708
25224
|
const rls = inlinedPolicies(env, table, own2);
|
|
25225
|
+
const ready = table.name === STORAGE_OBJECTS_TABLE ? `${storageReadySql(alias)} AND ` : "";
|
|
24709
25226
|
const where = subquery.where === null ? "" : ` AND ${conditionSql(inner, subquery.where)}`;
|
|
24710
25227
|
return {
|
|
24711
|
-
sql: `SELECT ${selected} FROM ${quoteIdentifier(table.name)} AS ${quoteIdentifier(alias)} WHERE ${rls}${where}`,
|
|
25228
|
+
sql: `SELECT ${selected} FROM ${quoteIdentifier(table.name)} AS ${quoteIdentifier(alias)} WHERE ${ready}${rls}${where}`,
|
|
24712
25229
|
item
|
|
24713
25230
|
};
|
|
24714
25231
|
} finally {
|
|
@@ -24761,7 +25278,10 @@ function groupTerms(env, sources, action, permissive, restrictive, pick2) {
|
|
|
24761
25278
|
terms.push(term);
|
|
24762
25279
|
}
|
|
24763
25280
|
const group = terms.length === 0 ? { sql: "0", params: [] } : terms.length === 1 ? terms[0] : {
|
|
24764
|
-
sql:
|
|
25281
|
+
sql: joinConditions(
|
|
25282
|
+
terms.map((term) => term.sql),
|
|
25283
|
+
"OR"
|
|
25284
|
+
),
|
|
24765
25285
|
params: terms.flatMap((term) => term.params)
|
|
24766
25286
|
};
|
|
24767
25287
|
const restricted = restrictive.flatMap((policy) => {
|
|
@@ -24771,9 +25291,10 @@ function groupTerms(env, sources, action, permissive, restrictive, pick2) {
|
|
|
24771
25291
|
return { permissive: group, restrictive: restricted };
|
|
24772
25292
|
}
|
|
24773
25293
|
function inlinedPolicies(env, table, source) {
|
|
25294
|
+
const label2 = tableLabel(table);
|
|
24774
25295
|
const found = applicablePolicies(
|
|
24775
25296
|
env.req.policies,
|
|
24776
|
-
|
|
25297
|
+
label2,
|
|
24777
25298
|
"select",
|
|
24778
25299
|
env.req.role
|
|
24779
25300
|
);
|
|
@@ -24785,7 +25306,7 @@ function inlinedPolicies(env, table, source) {
|
|
|
24785
25306
|
if (found.permissive.length === 0) return "0";
|
|
24786
25307
|
return withTableActive(
|
|
24787
25308
|
env.state,
|
|
24788
|
-
|
|
25309
|
+
label2,
|
|
24789
25310
|
[...found.permissive, ...found.restrictive],
|
|
24790
25311
|
() => {
|
|
24791
25312
|
const inner = { ...env, check: null, params: [] };
|
|
@@ -24804,7 +25325,10 @@ function inlinedPolicies(env, table, source) {
|
|
|
24804
25325
|
for (const part of parts) env.params.push(...part.params);
|
|
24805
25326
|
const [only] = parts;
|
|
24806
25327
|
if (parts.length === 1 && only) return only.sql;
|
|
24807
|
-
return
|
|
25328
|
+
return joinConditions(
|
|
25329
|
+
parts.map((part) => part.sql),
|
|
25330
|
+
"AND"
|
|
25331
|
+
);
|
|
24808
25332
|
}
|
|
24809
25333
|
);
|
|
24810
25334
|
}
|
|
@@ -24899,11 +25423,8 @@ function scopedValues(operand, scopes) {
|
|
|
24899
25423
|
return operandValues(operand).map((value) => ({ value, scopes }));
|
|
24900
25424
|
}
|
|
24901
25425
|
function subqueryScopes(snapshot, subquery, scopes) {
|
|
24902
|
-
const inner =
|
|
24903
|
-
return inner ? [
|
|
24904
|
-
...scopes,
|
|
24905
|
-
{ names: [asciiLower(subquery.alias ?? inner.name)], table: inner }
|
|
24906
|
-
] : void 0;
|
|
25426
|
+
const inner = resolveSubqueryTable(snapshot, subquery.table);
|
|
25427
|
+
return inner ? [...scopes, { names: [asciiLower(subqueryName(subquery))], table: inner }] : void 0;
|
|
24907
25428
|
}
|
|
24908
25429
|
function columnsReadOn(snapshot, table, expr, written) {
|
|
24909
25430
|
const found = [];
|
|
@@ -25238,7 +25759,7 @@ function nodeSql(table, node, params, path) {
|
|
|
25238
25759
|
const terms = children.map(
|
|
25239
25760
|
(child, index) => nodeSql(table, child, params, `${path}.${key}[${index}]`)
|
|
25240
25761
|
);
|
|
25241
|
-
return
|
|
25762
|
+
return joinConditions(terms, key === "or" ? "OR" : "AND");
|
|
25242
25763
|
}
|
|
25243
25764
|
function whereClause(table, filters, policy, params) {
|
|
25244
25765
|
const parts = [];
|
|
@@ -25249,7 +25770,7 @@ function whereClause(table, filters, policy, params) {
|
|
|
25249
25770
|
const client = filters.map(
|
|
25250
25771
|
(node, index) => nodeSql(table, node, params, `filters[${index}]`)
|
|
25251
25772
|
);
|
|
25252
|
-
if (client.length > 0) parts.push(
|
|
25773
|
+
if (client.length > 0) parts.push(joinConditions(client, "AND"));
|
|
25253
25774
|
return parts.length === 0 ? "" : ` WHERE ${parts.join(" AND ")}`;
|
|
25254
25775
|
}
|
|
25255
25776
|
function orderClause(table, order) {
|
|
@@ -25451,7 +25972,7 @@ var Rls = class {
|
|
|
25451
25972
|
const found = this.found(command);
|
|
25452
25973
|
return [...found.permissive, ...found.restrictive];
|
|
25453
25974
|
});
|
|
25454
|
-
const key = asciiLower(
|
|
25975
|
+
const key = asciiLower(this.policyTable);
|
|
25455
25976
|
if (hasSubqueries(all) && !this.state.active.includes(key))
|
|
25456
25977
|
this.state.active.push(key);
|
|
25457
25978
|
for (const command of commands) {
|
|
@@ -26257,51 +26778,829 @@ function compile(query, snapshot, decision, context, options = {}) {
|
|
|
26257
26778
|
}
|
|
26258
26779
|
}
|
|
26259
26780
|
|
|
26260
|
-
// ../../packages/data-core/src/
|
|
26261
|
-
var
|
|
26262
|
-
|
|
26263
|
-
|
|
26264
|
-
|
|
26265
|
-
|
|
26266
|
-
const column2 = row.column_name;
|
|
26267
|
-
if (typeof table !== "string" || typeof column2 !== "string") continue;
|
|
26268
|
-
if (Number(row.partial) !== 0) continue;
|
|
26269
|
-
const key = asciiLower(table);
|
|
26270
|
-
const columns = tables.get(key) ?? /* @__PURE__ */ new Map();
|
|
26271
|
-
tables.set(key, columns);
|
|
26272
|
-
const collations = columns.get(asciiLower(column2)) ?? [];
|
|
26273
|
-
collations.push(
|
|
26274
|
-
typeof row.collation === "string" ? row.collation.toUpperCase() : "BINARY"
|
|
26275
|
-
);
|
|
26276
|
-
columns.set(asciiLower(column2), collations);
|
|
26277
|
-
}
|
|
26278
|
-
return tables;
|
|
26781
|
+
// ../../packages/data-core/src/sql-depth.ts
|
|
26782
|
+
var D1_EXPRESSION_DEPTH = 100;
|
|
26783
|
+
var Unreadable = class extends Error {
|
|
26784
|
+
};
|
|
26785
|
+
function isWordStart(code) {
|
|
26786
|
+
return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code >= 128;
|
|
26279
26787
|
}
|
|
26280
|
-
function
|
|
26281
|
-
|
|
26282
|
-
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
26283
|
-
const scope = scopes[index];
|
|
26284
|
-
if (qualifier !== null && !scope.names.includes(asciiLower(qualifier)))
|
|
26285
|
-
continue;
|
|
26286
|
-
const found = scope.table.byName.get(column2);
|
|
26287
|
-
if (found) return { column: found, scope: index };
|
|
26288
|
-
if (qualifier !== null) return void 0;
|
|
26289
|
-
}
|
|
26290
|
-
return void 0;
|
|
26788
|
+
function isDigit2(code) {
|
|
26789
|
+
return code >= 48 && code <= 57;
|
|
26291
26790
|
}
|
|
26292
|
-
function
|
|
26293
|
-
|
|
26294
|
-
if (root?.table.name !== REALTIME_MESSAGES) return void 0;
|
|
26295
|
-
const column2 = root.table.byName.get("topic");
|
|
26296
|
-
return column2 ? { column: column2, scope: 0 } : void 0;
|
|
26791
|
+
function isWordPart(code) {
|
|
26792
|
+
return isWordStart(code) || isDigit2(code) || code === 36;
|
|
26297
26793
|
}
|
|
26298
|
-
function
|
|
26299
|
-
|
|
26300
|
-
|
|
26301
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26794
|
+
function isSpace3(code) {
|
|
26795
|
+
return code === 32 || code >= 9 && code <= 13;
|
|
26796
|
+
}
|
|
26797
|
+
var OPERATORS_2 = /* @__PURE__ */ new Set([
|
|
26798
|
+
"->",
|
|
26799
|
+
"||",
|
|
26800
|
+
"<=",
|
|
26801
|
+
">=",
|
|
26802
|
+
"<>",
|
|
26803
|
+
"!=",
|
|
26804
|
+
"==",
|
|
26805
|
+
"<<",
|
|
26806
|
+
">>"
|
|
26807
|
+
]);
|
|
26808
|
+
var OPERATORS_1 = "=<>+-*/%&|~(),.;";
|
|
26809
|
+
var NUMBER2 = /^(?:0[xX][0-9A-Fa-f_]+|[0-9_]*\.?[0-9_]*(?:[eE][+-]?[0-9_]+)?)/;
|
|
26810
|
+
function tokenize3(sql) {
|
|
26811
|
+
const tokens = [];
|
|
26812
|
+
const length = sql.length;
|
|
26813
|
+
let at = 0;
|
|
26814
|
+
const closing = (close) => {
|
|
26815
|
+
let from = at + 1;
|
|
26816
|
+
for (; ; ) {
|
|
26817
|
+
const found = sql.indexOf(close, from);
|
|
26818
|
+
if (found < 0) throw new Unreadable("an unclosed quote");
|
|
26819
|
+
if (close === "]" || sql[found + 1] !== close) return found;
|
|
26820
|
+
from = found + 2;
|
|
26821
|
+
}
|
|
26822
|
+
};
|
|
26823
|
+
while (at < length) {
|
|
26824
|
+
const code = sql.charCodeAt(at);
|
|
26825
|
+
const next = sql.charCodeAt(at + 1);
|
|
26826
|
+
if (isSpace3(code)) {
|
|
26827
|
+
at++;
|
|
26828
|
+
} else if (code === 45 && next === 45) {
|
|
26829
|
+
const end = sql.indexOf("\n", at);
|
|
26830
|
+
at = end < 0 ? length : end + 1;
|
|
26831
|
+
} else if (code === 47 && next === 42) {
|
|
26832
|
+
const end = sql.indexOf("*/", at + 2);
|
|
26833
|
+
at = end < 0 ? length : end + 2;
|
|
26834
|
+
} else if (code === 39) {
|
|
26835
|
+
at = closing("'") + 1;
|
|
26836
|
+
tokens.push({ kind: "literal" });
|
|
26837
|
+
} else if (code === 34 || code === 96 || code === 91) {
|
|
26838
|
+
at = closing(code === 91 ? "]" : String.fromCharCode(code)) + 1;
|
|
26839
|
+
tokens.push({ kind: "name" });
|
|
26840
|
+
} else if ((code === 88 || code === 120) && next === 39) {
|
|
26841
|
+
at++;
|
|
26842
|
+
at = closing("'") + 1;
|
|
26843
|
+
tokens.push({ kind: "literal" });
|
|
26844
|
+
} else if (isDigit2(code) || code === 46 && isDigit2(next)) {
|
|
26845
|
+
const text2 = NUMBER2.exec(sql.slice(at, at + 64))?.[0] || sql[at] || "";
|
|
26846
|
+
tokens.push(
|
|
26847
|
+
/^[0-9]+$/.test(text2) ? { kind: "literal", integer: Number(text2) } : { kind: "literal" }
|
|
26848
|
+
);
|
|
26849
|
+
at += text2.length;
|
|
26850
|
+
} else if (code === 63 || code === 58 || code === 64 || code === 36) {
|
|
26851
|
+
at++;
|
|
26852
|
+
while (at < length && isWordPart(sql.charCodeAt(at))) at++;
|
|
26853
|
+
tokens.push({ kind: "param" });
|
|
26854
|
+
} else if (isWordStart(code)) {
|
|
26855
|
+
const start = at;
|
|
26856
|
+
while (at < length && isWordPart(sql.charCodeAt(at))) at++;
|
|
26857
|
+
const text2 = sql.slice(start, at);
|
|
26858
|
+
tokens.push({ kind: "word", text: text2, upper: text2.toUpperCase() });
|
|
26859
|
+
} else if (sql.startsWith("->>", at)) {
|
|
26860
|
+
tokens.push({ kind: "op", text: "->>" });
|
|
26861
|
+
at += 3;
|
|
26862
|
+
} else if (OPERATORS_2.has(sql.slice(at, at + 2))) {
|
|
26863
|
+
tokens.push({ kind: "op", text: sql.slice(at, at + 2) });
|
|
26864
|
+
at += 2;
|
|
26865
|
+
} else if (OPERATORS_1.includes(sql[at] ?? "")) {
|
|
26866
|
+
tokens.push({ kind: "op", text: sql[at] ?? "" });
|
|
26867
|
+
at++;
|
|
26868
|
+
} else throw new Unreadable(`an unexpected ${sql[at] ?? ""}`);
|
|
26869
|
+
}
|
|
26870
|
+
tokens.push({ kind: "end" });
|
|
26871
|
+
return tokens;
|
|
26872
|
+
}
|
|
26873
|
+
var CLAUSE_WORDS2 = /* @__PURE__ */ new Set([
|
|
26874
|
+
"AS",
|
|
26875
|
+
"CROSS",
|
|
26876
|
+
"DEFAULT",
|
|
26877
|
+
"DO",
|
|
26878
|
+
"EXCEPT",
|
|
26879
|
+
"FROM",
|
|
26880
|
+
"FULL",
|
|
26881
|
+
"GROUP",
|
|
26882
|
+
"HAVING",
|
|
26883
|
+
"INDEXED",
|
|
26884
|
+
"INNER",
|
|
26885
|
+
"INTERSECT",
|
|
26886
|
+
"JOIN",
|
|
26887
|
+
"LEFT",
|
|
26888
|
+
"LIMIT",
|
|
26889
|
+
"NATURAL",
|
|
26890
|
+
"NOT",
|
|
26891
|
+
"OFFSET",
|
|
26892
|
+
"ON",
|
|
26893
|
+
"ORDER",
|
|
26894
|
+
"OUTER",
|
|
26895
|
+
"RETURNING",
|
|
26896
|
+
"RIGHT",
|
|
26897
|
+
"SET",
|
|
26898
|
+
"UNION",
|
|
26899
|
+
"USING",
|
|
26900
|
+
"VALUES",
|
|
26901
|
+
"WHERE",
|
|
26902
|
+
"WINDOW"
|
|
26903
|
+
]);
|
|
26904
|
+
var NOT_CONSTANT = /* @__PURE__ */ new Set([
|
|
26905
|
+
"avg",
|
|
26906
|
+
"changes",
|
|
26907
|
+
"count",
|
|
26908
|
+
"group_concat",
|
|
26909
|
+
"json_group_array",
|
|
26910
|
+
"json_group_object",
|
|
26911
|
+
"jsonb_group_array",
|
|
26912
|
+
"jsonb_group_object",
|
|
26913
|
+
"last_insert_rowid",
|
|
26914
|
+
"random",
|
|
26915
|
+
"randomblob",
|
|
26916
|
+
"sqlite_offset",
|
|
26917
|
+
"string_agg",
|
|
26918
|
+
"sum",
|
|
26919
|
+
"total",
|
|
26920
|
+
"total_changes"
|
|
26921
|
+
]);
|
|
26922
|
+
var OR = 1;
|
|
26923
|
+
var AND = 2;
|
|
26924
|
+
var NOT = 3;
|
|
26925
|
+
var COMPARE = 4;
|
|
26926
|
+
var ORDER = 5;
|
|
26927
|
+
var ESCAPE = 6;
|
|
26928
|
+
var BITS = 7;
|
|
26929
|
+
var SUM = 8;
|
|
26930
|
+
var PRODUCT = 9;
|
|
26931
|
+
var CONCAT = 10;
|
|
26932
|
+
var COLLATE = 11;
|
|
26933
|
+
var UNARY = 12;
|
|
26934
|
+
var BINARY = {
|
|
26935
|
+
"=": COMPARE,
|
|
26936
|
+
"==": COMPARE,
|
|
26937
|
+
"!=": COMPARE,
|
|
26938
|
+
"<>": COMPARE,
|
|
26939
|
+
"<": ORDER,
|
|
26940
|
+
"<=": ORDER,
|
|
26941
|
+
">": ORDER,
|
|
26942
|
+
">=": ORDER,
|
|
26943
|
+
"&": BITS,
|
|
26944
|
+
"|": BITS,
|
|
26945
|
+
"<<": BITS,
|
|
26946
|
+
">>": BITS,
|
|
26947
|
+
"+": SUM,
|
|
26948
|
+
"-": SUM,
|
|
26949
|
+
"*": PRODUCT,
|
|
26950
|
+
"/": PRODUCT,
|
|
26951
|
+
"%": PRODUCT,
|
|
26952
|
+
"||": CONCAT,
|
|
26953
|
+
"->": CONCAT,
|
|
26954
|
+
"->>": CONCAT
|
|
26955
|
+
};
|
|
26956
|
+
var LIKE_WORDS = /* @__PURE__ */ new Set(["LIKE", "GLOB", "REGEXP", "MATCH"]);
|
|
26957
|
+
var USABLE_OPERATORS = /* @__PURE__ */ new Set(["=", "==", "<", "<=", ">", ">="]);
|
|
26958
|
+
var Reader = class {
|
|
26959
|
+
constructor(tokens, planner) {
|
|
26960
|
+
this.tokens = tokens;
|
|
26961
|
+
this.planner = planner;
|
|
26962
|
+
}
|
|
26963
|
+
at = 0;
|
|
26964
|
+
/** The deepest node built, kept or not: each is checked as it is built. */
|
|
26965
|
+
deepest = 0;
|
|
26966
|
+
peek(offset = 0) {
|
|
26967
|
+
return this.tokens[this.at + offset] ?? { kind: "end" };
|
|
26968
|
+
}
|
|
26969
|
+
isWord(word, offset = 0) {
|
|
26970
|
+
const token = this.peek(offset);
|
|
26971
|
+
return token.kind === "word" && token.upper === word;
|
|
26972
|
+
}
|
|
26973
|
+
isOp(op, offset = 0) {
|
|
26974
|
+
const token = this.peek(offset);
|
|
26975
|
+
return token.kind === "op" && token.text === op;
|
|
26976
|
+
}
|
|
26977
|
+
isName(offset = 0) {
|
|
26978
|
+
const kind = this.peek(offset).kind;
|
|
26979
|
+
return kind === "word" || kind === "name";
|
|
26980
|
+
}
|
|
26981
|
+
takeWord(word) {
|
|
26982
|
+
if (!this.isWord(word)) return false;
|
|
26983
|
+
this.at++;
|
|
26984
|
+
return true;
|
|
26985
|
+
}
|
|
26986
|
+
takeOp(op) {
|
|
26987
|
+
if (!this.isOp(op)) return false;
|
|
26988
|
+
this.at++;
|
|
26989
|
+
return true;
|
|
26990
|
+
}
|
|
26991
|
+
expectWord(word) {
|
|
26992
|
+
if (!this.takeWord(word)) throw new Unreadable(`no ${word}`);
|
|
26993
|
+
}
|
|
26994
|
+
expectOp(op) {
|
|
26995
|
+
if (!this.takeOp(op)) throw new Unreadable(`no ${op}`);
|
|
26996
|
+
}
|
|
26997
|
+
name() {
|
|
26998
|
+
if (!this.isName()) throw new Unreadable("no name");
|
|
26999
|
+
this.at++;
|
|
27000
|
+
}
|
|
27001
|
+
/** `name` or `schema.name`. */
|
|
27002
|
+
qualifiedName() {
|
|
27003
|
+
this.name();
|
|
27004
|
+
if (this.isOp(".") && this.isName(1)) this.at += 2;
|
|
27005
|
+
}
|
|
27006
|
+
atEnd() {
|
|
27007
|
+
this.takeOp(";");
|
|
27008
|
+
return this.peek().kind === "end";
|
|
27009
|
+
}
|
|
27010
|
+
// --- nodes, as expr.c builds them ---
|
|
27011
|
+
made(node) {
|
|
27012
|
+
this.deepest = Math.max(this.deepest, node.height);
|
|
27013
|
+
return node;
|
|
27014
|
+
}
|
|
27015
|
+
leaf(extra = {}) {
|
|
27016
|
+
return this.made({ height: 1, constant: true, subqueries: [], ...extra });
|
|
27017
|
+
}
|
|
27018
|
+
/** sqlite3PExpr, exprSetHeight: one deeper than the deepest part. */
|
|
27019
|
+
over(parts, extra = {}) {
|
|
27020
|
+
return this.made(this.unchecked(parts, extra));
|
|
27021
|
+
}
|
|
27022
|
+
/**
|
|
27023
|
+
* sqlite3ExprAttachSubtrees alone, as for a CAST: its height is set and
|
|
27024
|
+
* not checked, so it counts only through what holds it.
|
|
27025
|
+
*/
|
|
27026
|
+
unchecked(parts, extra = {}) {
|
|
27027
|
+
let height = 0;
|
|
27028
|
+
let constant = true;
|
|
27029
|
+
let hasFunc = false;
|
|
27030
|
+
let hasSubquery = false;
|
|
27031
|
+
let subqueries = [];
|
|
27032
|
+
for (const part of parts) {
|
|
27033
|
+
if (part.height > height) height = part.height;
|
|
27034
|
+
if (!part.constant) constant = false;
|
|
27035
|
+
if (part.hasFunc) hasFunc = true;
|
|
27036
|
+
if (part.hasSubquery) hasSubquery = true;
|
|
27037
|
+
if (part.subqueries.length > 0)
|
|
27038
|
+
subqueries = subqueries.length === 0 ? part.subqueries : [...subqueries, ...part.subqueries];
|
|
27039
|
+
}
|
|
27040
|
+
const node = { height: height + 1, constant, subqueries, ...extra };
|
|
27041
|
+
if (hasFunc) node.hasFunc ??= true;
|
|
27042
|
+
if (hasSubquery) node.hasSubquery ??= true;
|
|
27043
|
+
return node;
|
|
27044
|
+
}
|
|
27045
|
+
/** A node holding a SELECT: EXISTS, `(SELECT …)`, `x IN (SELECT …)`. */
|
|
27046
|
+
holding(left, select) {
|
|
27047
|
+
const node = {
|
|
27048
|
+
height: Math.max(select.height, left?.height ?? 0) + 1,
|
|
27049
|
+
constant: false,
|
|
27050
|
+
subqueries: [...left?.subqueries ?? [], select],
|
|
27051
|
+
hasSubquery: true
|
|
27052
|
+
};
|
|
27053
|
+
if (left?.hasFunc) node.hasFunc = true;
|
|
27054
|
+
if (left?.column) node.usable = true;
|
|
27055
|
+
return this.made(node);
|
|
27056
|
+
}
|
|
27057
|
+
// --- expressions, by parse.y's precedence ---
|
|
27058
|
+
expr(min = OR) {
|
|
27059
|
+
let left = this.prefix();
|
|
27060
|
+
for (; ; ) {
|
|
27061
|
+
const token = this.peek();
|
|
27062
|
+
if (token.kind === "op") {
|
|
27063
|
+
const level = BINARY[token.text];
|
|
27064
|
+
if (level === void 0 || level < min) return left;
|
|
27065
|
+
this.at++;
|
|
27066
|
+
const right = this.expr(level + 1);
|
|
27067
|
+
if (token.text === "->" || token.text === "->>") {
|
|
27068
|
+
left = this.over([left, right], { hasFunc: true });
|
|
27069
|
+
continue;
|
|
27070
|
+
}
|
|
27071
|
+
const compared = USABLE_OPERATORS.has(token.text);
|
|
27072
|
+
left = this.over(
|
|
27073
|
+
[left, right],
|
|
27074
|
+
compared && (left.column || right.column) ? { usable: true } : {}
|
|
27075
|
+
);
|
|
27076
|
+
continue;
|
|
27077
|
+
}
|
|
27078
|
+
if (token.kind !== "word") return left;
|
|
27079
|
+
const word = token.upper;
|
|
27080
|
+
if (word === "OR" || word === "AND") {
|
|
27081
|
+
if ((word === "OR" ? OR : AND) < min) return left;
|
|
27082
|
+
this.at++;
|
|
27083
|
+
const right = this.expr((word === "OR" ? OR : AND) + 1);
|
|
27084
|
+
if (word === "OR")
|
|
27085
|
+
left = this.over([left, right], { or: [left, right] });
|
|
27086
|
+
else if ((left.isFalse || right.isFalse) && !left.hasFunc && !right.hasFunc)
|
|
27087
|
+
left = this.leaf({ isFalse: true, literal: true });
|
|
27088
|
+
else left = this.over([left, right], { and: [left, right] });
|
|
27089
|
+
continue;
|
|
27090
|
+
}
|
|
27091
|
+
if (word === "COLLATE") {
|
|
27092
|
+
if (COLLATE < min) return left;
|
|
27093
|
+
this.at++;
|
|
27094
|
+
this.name();
|
|
27095
|
+
left = this.made({
|
|
27096
|
+
height: 1,
|
|
27097
|
+
constant: left.constant,
|
|
27098
|
+
subqueries: left.subqueries,
|
|
27099
|
+
...left.column ? { column: true } : {}
|
|
27100
|
+
});
|
|
27101
|
+
continue;
|
|
27102
|
+
}
|
|
27103
|
+
if (COMPARE < min) return left;
|
|
27104
|
+
const negated = word === "NOT" ? 1 : 0;
|
|
27105
|
+
const after = this.peek(negated);
|
|
27106
|
+
const verb = after.kind === "word" ? after.upper : "";
|
|
27107
|
+
if (word === "IS") {
|
|
27108
|
+
this.at++;
|
|
27109
|
+
let not = this.takeWord("NOT");
|
|
27110
|
+
if (this.takeWord("DISTINCT")) {
|
|
27111
|
+
this.expectWord("FROM");
|
|
27112
|
+
not = !not;
|
|
27113
|
+
}
|
|
27114
|
+
const right = this.expr(COMPARE + 1);
|
|
27115
|
+
left = right.isNull ? this.nullTest(left, !not) : this.over(
|
|
27116
|
+
[left, right],
|
|
27117
|
+
!not && (left.column || right.column) ? { usable: true } : {}
|
|
27118
|
+
);
|
|
27119
|
+
} else if (word === "ISNULL" || word === "NOTNULL") {
|
|
27120
|
+
this.at++;
|
|
27121
|
+
left = this.nullTest(left, word === "ISNULL");
|
|
27122
|
+
} else if (negated && verb === "NULL") {
|
|
27123
|
+
this.at += 2;
|
|
27124
|
+
left = this.nullTest(left, false);
|
|
27125
|
+
} else if (LIKE_WORDS.has(verb)) {
|
|
27126
|
+
this.at += negated + 1;
|
|
27127
|
+
const args = [this.expr(COMPARE + 1), left];
|
|
27128
|
+
if (this.takeWord("ESCAPE")) args.push(this.expr(ESCAPE + 1));
|
|
27129
|
+
left = this.over(args, { hasFunc: true });
|
|
27130
|
+
if (negated) left = this.over([left]);
|
|
27131
|
+
} else if (verb === "BETWEEN") {
|
|
27132
|
+
this.at += negated + 1;
|
|
27133
|
+
const low = this.expr(NOT);
|
|
27134
|
+
this.expectWord("AND");
|
|
27135
|
+
const high = this.expr(COMPARE + 1);
|
|
27136
|
+
left = this.over([left, low, high]);
|
|
27137
|
+
if (negated) left = this.over([left]);
|
|
27138
|
+
} else if (verb === "IN") {
|
|
27139
|
+
this.at += negated + 1;
|
|
27140
|
+
left = this.inList(left, negated === 1);
|
|
27141
|
+
} else return left;
|
|
27142
|
+
}
|
|
27143
|
+
}
|
|
27144
|
+
/**
|
|
27145
|
+
* IS NULL (`isNull`) or IS NOT NULL: on a literal, the answer itself; on
|
|
27146
|
+
* a column, a term the planner can use when it is IS NULL.
|
|
27147
|
+
*/
|
|
27148
|
+
nullTest(left, isNull) {
|
|
27149
|
+
if (left.literal)
|
|
27150
|
+
return this.leaf(
|
|
27151
|
+
isNull ? { isFalse: true, literal: true } : { literal: true }
|
|
27152
|
+
);
|
|
27153
|
+
return this.over([left], isNull && left.column ? { usable: true } : {});
|
|
27154
|
+
}
|
|
27155
|
+
inList(left, negated) {
|
|
27156
|
+
this.expectOp("(");
|
|
27157
|
+
if (this.takeOp(")"))
|
|
27158
|
+
return left.hasFunc ? this.over([left]) : this.leaf(negated ? {} : { isFalse: true });
|
|
27159
|
+
let node;
|
|
27160
|
+
if (this.startsSelect()) {
|
|
27161
|
+
node = this.holding(left, this.select());
|
|
27162
|
+
} else {
|
|
27163
|
+
const items = this.list();
|
|
27164
|
+
const [only] = items;
|
|
27165
|
+
const usable = left.column ? { usable: true } : {};
|
|
27166
|
+
if (items.length === 1 && only?.select)
|
|
27167
|
+
node = this.holding(left, only.select);
|
|
27168
|
+
else if (items.length === 1 && only?.constant)
|
|
27169
|
+
node = this.over([left, this.over([only])], usable);
|
|
27170
|
+
else node = this.over([left, ...items], usable);
|
|
27171
|
+
}
|
|
27172
|
+
this.expectOp(")");
|
|
27173
|
+
return negated ? this.over([node]) : node;
|
|
27174
|
+
}
|
|
27175
|
+
list() {
|
|
27176
|
+
const items = [this.expr()];
|
|
27177
|
+
while (this.takeOp(",")) items.push(this.expr());
|
|
27178
|
+
return items;
|
|
27179
|
+
}
|
|
27180
|
+
prefix() {
|
|
27181
|
+
const token = this.peek();
|
|
27182
|
+
this.at++;
|
|
27183
|
+
switch (token.kind) {
|
|
27184
|
+
case "literal":
|
|
27185
|
+
return this.leaf(
|
|
27186
|
+
token.integer === 0 ? { isFalse: true, literal: true } : { literal: true }
|
|
27187
|
+
);
|
|
27188
|
+
case "param":
|
|
27189
|
+
return this.leaf();
|
|
27190
|
+
case "name":
|
|
27191
|
+
return this.column();
|
|
27192
|
+
case "end":
|
|
27193
|
+
throw new Unreadable("no expression");
|
|
27194
|
+
case "op":
|
|
27195
|
+
if (token.text === "(") {
|
|
27196
|
+
if (this.startsSelect()) {
|
|
27197
|
+
const select = this.select();
|
|
27198
|
+
this.expectOp(")");
|
|
27199
|
+
return { ...this.holding(null, select), select };
|
|
27200
|
+
}
|
|
27201
|
+
const items = this.list();
|
|
27202
|
+
this.expectOp(")");
|
|
27203
|
+
if (items.length === 1 && items[0]) return items[0];
|
|
27204
|
+
throw new Unreadable("a row value");
|
|
27205
|
+
}
|
|
27206
|
+
if (token.text === "-" || token.text === "+") {
|
|
27207
|
+
const operand = this.expr(UNARY);
|
|
27208
|
+
return this.over([operand], operand.literal ? { literal: true } : {});
|
|
27209
|
+
}
|
|
27210
|
+
if (token.text === "~") return this.over([this.expr(UNARY)]);
|
|
27211
|
+
throw new Unreadable(`an unexpected ${token.text}`);
|
|
27212
|
+
case "word":
|
|
27213
|
+
break;
|
|
27214
|
+
}
|
|
27215
|
+
switch (token.upper) {
|
|
27216
|
+
case "NULL":
|
|
27217
|
+
return this.leaf({ isNull: true });
|
|
27218
|
+
case "TRUE":
|
|
27219
|
+
case "FALSE":
|
|
27220
|
+
return this.leaf();
|
|
27221
|
+
case "NOT":
|
|
27222
|
+
return this.over([this.expr(NOT)]);
|
|
27223
|
+
case "EXISTS": {
|
|
27224
|
+
this.expectOp("(");
|
|
27225
|
+
const select = this.select();
|
|
27226
|
+
this.expectOp(")");
|
|
27227
|
+
return this.holding(null, select);
|
|
27228
|
+
}
|
|
27229
|
+
case "CAST": {
|
|
27230
|
+
this.expectOp("(");
|
|
27231
|
+
const value = this.expr();
|
|
27232
|
+
this.expectWord("AS");
|
|
27233
|
+
this.typeName();
|
|
27234
|
+
this.expectOp(")");
|
|
27235
|
+
return this.unchecked([value]);
|
|
27236
|
+
}
|
|
27237
|
+
case "CASE": {
|
|
27238
|
+
const parts = [];
|
|
27239
|
+
if (!this.isWord("WHEN")) parts.push(this.expr());
|
|
27240
|
+
while (this.takeWord("WHEN")) {
|
|
27241
|
+
parts.push(this.expr());
|
|
27242
|
+
this.expectWord("THEN");
|
|
27243
|
+
parts.push(this.expr());
|
|
27244
|
+
}
|
|
27245
|
+
if (this.takeWord("ELSE")) parts.push(this.expr());
|
|
27246
|
+
this.expectWord("END");
|
|
27247
|
+
return this.over(parts);
|
|
27248
|
+
}
|
|
27249
|
+
default:
|
|
27250
|
+
break;
|
|
27251
|
+
}
|
|
27252
|
+
if (!this.takeOp("(")) return this.column();
|
|
27253
|
+
let args = [];
|
|
27254
|
+
if (!this.takeOp("*") && !this.isOp(")")) {
|
|
27255
|
+
if (!this.takeWord("DISTINCT")) this.takeWord("ALL");
|
|
27256
|
+
args = this.list();
|
|
27257
|
+
}
|
|
27258
|
+
this.expectOp(")");
|
|
27259
|
+
if (this.isWord("FILTER") || this.isWord("OVER"))
|
|
27260
|
+
throw new Unreadable("a window function");
|
|
27261
|
+
return this.over(args, {
|
|
27262
|
+
hasFunc: true,
|
|
27263
|
+
constant: !NOT_CONSTANT.has(token.text.toLowerCase()) && args.every((arg) => arg.constant)
|
|
27264
|
+
});
|
|
27265
|
+
}
|
|
27266
|
+
/**
|
|
27267
|
+
* `a`, `a.b` or `a.b.c`, its first name just read: TK_ID, and a TK_DOT a
|
|
27268
|
+
* dot — a.b.c is DOT(a, DOT(b, c)).
|
|
27269
|
+
*/
|
|
27270
|
+
column() {
|
|
27271
|
+
let node = this.leaf({ constant: false, column: true });
|
|
27272
|
+
while (this.isOp(".") && this.isName(1)) {
|
|
27273
|
+
this.at += 2;
|
|
27274
|
+
node = this.over([this.leaf({ constant: false }), node], {
|
|
27275
|
+
column: true
|
|
27276
|
+
});
|
|
27277
|
+
}
|
|
27278
|
+
return node;
|
|
27279
|
+
}
|
|
27280
|
+
typeName() {
|
|
27281
|
+
do
|
|
27282
|
+
this.name();
|
|
27283
|
+
while (this.isName());
|
|
27284
|
+
if (this.takeOp("(")) {
|
|
27285
|
+
while (!this.takeOp(")")) {
|
|
27286
|
+
if (this.peek().kind === "end") throw new Unreadable("no )");
|
|
27287
|
+
this.at++;
|
|
27288
|
+
}
|
|
27289
|
+
}
|
|
27290
|
+
}
|
|
27291
|
+
/**
|
|
27292
|
+
* A WHERE, and the chain an OR in it may bring when each of its branches
|
|
27293
|
+
* could be read through an index — a column compared, or an AND holding
|
|
27294
|
+
* one (exprAnalyzeOrTerm) — whether or not the table has that index: the
|
|
27295
|
+
* other terms the planner can use, without a subquery, ANDed one by one
|
|
27296
|
+
* from the first, under one more AND (wherecode.c, WHERE_MULTI_OR).
|
|
27297
|
+
*/
|
|
27298
|
+
where() {
|
|
27299
|
+
const where = this.expr();
|
|
27300
|
+
if (!this.planner) return where;
|
|
27301
|
+
const terms = split(where, "and");
|
|
27302
|
+
for (const or of terms) {
|
|
27303
|
+
if (!or.or || !split(or, "or").every(
|
|
27304
|
+
(branch) => branch.usable || split(branch, "and").some((term) => term.usable)
|
|
27305
|
+
))
|
|
27306
|
+
continue;
|
|
27307
|
+
let chain = 0;
|
|
27308
|
+
for (const term of terms)
|
|
27309
|
+
if (term !== or && (term.usable || term.or) && !term.hasSubquery)
|
|
27310
|
+
chain = chain === 0 ? term.height : Math.max(chain, term.height) + 1;
|
|
27311
|
+
if (chain > 0) this.deepest = Math.max(this.deepest, chain + 1);
|
|
27312
|
+
}
|
|
27313
|
+
return where;
|
|
27314
|
+
}
|
|
27315
|
+
// --- statements ---
|
|
27316
|
+
startsSelect() {
|
|
27317
|
+
return this.isWord("SELECT") || this.isWord("VALUES") || this.isWord("WITH");
|
|
27318
|
+
}
|
|
27319
|
+
/** A SELECT, compound or not, with its ORDER BY and LIMIT. */
|
|
27320
|
+
select() {
|
|
27321
|
+
if (this.isWord("WITH")) throw new Unreadable("WITH");
|
|
27322
|
+
const whole = { height: 0, resolved: [], from: [] };
|
|
27323
|
+
const counted = (node) => {
|
|
27324
|
+
whole.height = Math.max(whole.height, node.height);
|
|
27325
|
+
whole.resolved.push(node);
|
|
27326
|
+
};
|
|
27327
|
+
do {
|
|
27328
|
+
if (this.takeWord("VALUES")) {
|
|
27329
|
+
do {
|
|
27330
|
+
this.expectOp("(");
|
|
27331
|
+
for (const item of this.list()) counted(item);
|
|
27332
|
+
this.expectOp(")");
|
|
27333
|
+
} while (this.takeOp(","));
|
|
27334
|
+
continue;
|
|
27335
|
+
}
|
|
27336
|
+
this.expectWord("SELECT");
|
|
27337
|
+
if (!this.takeWord("DISTINCT")) this.takeWord("ALL");
|
|
27338
|
+
const stars = [];
|
|
27339
|
+
do {
|
|
27340
|
+
if (this.takeOp("*")) stars.push(this.leaf());
|
|
27341
|
+
else if (this.isName() && this.isOp(".", 1) && this.isOp("*", 2)) {
|
|
27342
|
+
this.at += 3;
|
|
27343
|
+
stars.push(this.over([this.leaf(), this.leaf()]));
|
|
27344
|
+
} else {
|
|
27345
|
+
counted(this.expr());
|
|
27346
|
+
this.alias();
|
|
27347
|
+
}
|
|
27348
|
+
} while (this.takeOp(","));
|
|
27349
|
+
const sources = this.takeWord("FROM") ? this.from(whole) : 0;
|
|
27350
|
+
for (const star of stars) {
|
|
27351
|
+
whole.height = Math.max(whole.height, star.height);
|
|
27352
|
+
whole.resolved.push(
|
|
27353
|
+
sources > 1 ? this.made({ height: 3, constant: false, subqueries: [] }) : star
|
|
27354
|
+
);
|
|
27355
|
+
}
|
|
27356
|
+
if (this.takeWord("WHERE")) counted(this.where());
|
|
27357
|
+
if (this.takeWord("GROUP")) {
|
|
27358
|
+
this.expectWord("BY");
|
|
27359
|
+
for (const item of this.list()) counted(item);
|
|
27360
|
+
if (this.takeWord("HAVING")) counted(this.expr());
|
|
27361
|
+
}
|
|
27362
|
+
} while (this.compound());
|
|
27363
|
+
if (this.takeWord("ORDER")) {
|
|
27364
|
+
this.expectWord("BY");
|
|
27365
|
+
do {
|
|
27366
|
+
counted(this.expr());
|
|
27367
|
+
if (!this.takeWord("ASC")) this.takeWord("DESC");
|
|
27368
|
+
if (this.takeWord("NULLS") && !this.takeWord("FIRST"))
|
|
27369
|
+
this.expectWord("LAST");
|
|
27370
|
+
} while (this.takeOp(","));
|
|
27371
|
+
}
|
|
27372
|
+
if (this.takeWord("LIMIT")) {
|
|
27373
|
+
const parts = [this.expr()];
|
|
27374
|
+
if (this.takeWord("OFFSET") || this.takeOp(",")) parts.push(this.expr());
|
|
27375
|
+
counted(this.over(parts));
|
|
27376
|
+
}
|
|
27377
|
+
return whole;
|
|
27378
|
+
}
|
|
27379
|
+
compound() {
|
|
27380
|
+
if (this.takeWord("UNION")) {
|
|
27381
|
+
this.takeWord("ALL");
|
|
27382
|
+
return true;
|
|
27383
|
+
}
|
|
27384
|
+
return this.takeWord("INTERSECT") || this.takeWord("EXCEPT");
|
|
27385
|
+
}
|
|
27386
|
+
alias() {
|
|
27387
|
+
if (this.takeWord("AS")) {
|
|
27388
|
+
this.name();
|
|
27389
|
+
return;
|
|
27390
|
+
}
|
|
27391
|
+
const token = this.peek();
|
|
27392
|
+
if (token.kind === "name" || token.kind === "word" && !CLAUSE_WORDS2.has(token.upper))
|
|
27393
|
+
this.at++;
|
|
27394
|
+
}
|
|
27395
|
+
/** The FROM items, into `into`; how many there are. */
|
|
27396
|
+
from(into) {
|
|
27397
|
+
let sources = 0;
|
|
27398
|
+
do {
|
|
27399
|
+
sources++;
|
|
27400
|
+
if (this.takeOp("(")) {
|
|
27401
|
+
if (!this.startsSelect()) throw new Unreadable("a parenthesized join");
|
|
27402
|
+
into.from.push(this.select());
|
|
27403
|
+
this.expectOp(")");
|
|
27404
|
+
} else {
|
|
27405
|
+
this.qualifiedName();
|
|
27406
|
+
if (this.takeOp("(")) {
|
|
27407
|
+
if (!this.isOp(")"))
|
|
27408
|
+
for (const arg of this.list()) {
|
|
27409
|
+
into.resolved.push(arg);
|
|
27410
|
+
this.deepest = Math.max(this.deepest, arg.height + 2);
|
|
27411
|
+
}
|
|
27412
|
+
this.expectOp(")");
|
|
27413
|
+
}
|
|
27414
|
+
}
|
|
27415
|
+
this.alias();
|
|
27416
|
+
if (this.takeWord("INDEXED")) {
|
|
27417
|
+
this.expectWord("BY");
|
|
27418
|
+
this.name();
|
|
27419
|
+
} else if (this.isWord("NOT") && this.isWord("INDEXED", 1)) this.at += 2;
|
|
27420
|
+
if (this.isWord("ON") || this.isWord("USING"))
|
|
27421
|
+
throw new Unreadable("a join constraint");
|
|
27422
|
+
} while (this.join());
|
|
27423
|
+
return sources;
|
|
27424
|
+
}
|
|
27425
|
+
join() {
|
|
27426
|
+
if (this.takeOp(",")) return true;
|
|
27427
|
+
const start = this.at;
|
|
27428
|
+
this.takeWord("NATURAL");
|
|
27429
|
+
if (this.takeWord("LEFT") || this.takeWord("RIGHT") || this.takeWord("FULL"))
|
|
27430
|
+
this.takeWord("OUTER");
|
|
27431
|
+
else if (!this.takeWord("INNER")) this.takeWord("CROSS");
|
|
27432
|
+
if (this.takeWord("JOIN")) return true;
|
|
27433
|
+
this.at = start;
|
|
27434
|
+
return false;
|
|
27435
|
+
}
|
|
27436
|
+
names() {
|
|
27437
|
+
this.expectOp("(");
|
|
27438
|
+
do
|
|
27439
|
+
this.name();
|
|
27440
|
+
while (this.takeOp(","));
|
|
27441
|
+
this.expectOp(")");
|
|
27442
|
+
}
|
|
27443
|
+
returning(into) {
|
|
27444
|
+
if (!this.takeWord("RETURNING")) return;
|
|
27445
|
+
do {
|
|
27446
|
+
if (this.takeOp("*")) into.push(this.leaf());
|
|
27447
|
+
else {
|
|
27448
|
+
into.push(this.expr());
|
|
27449
|
+
this.alias();
|
|
27450
|
+
}
|
|
27451
|
+
} while (this.takeOp(","));
|
|
27452
|
+
}
|
|
27453
|
+
assignments(into) {
|
|
27454
|
+
do {
|
|
27455
|
+
if (this.isOp("(")) this.names();
|
|
27456
|
+
else this.name();
|
|
27457
|
+
this.expectOp("=");
|
|
27458
|
+
into.push(this.expr());
|
|
27459
|
+
} while (this.takeOp(","));
|
|
27460
|
+
}
|
|
27461
|
+
/** The statement: each of its expressions resolved from 0. */
|
|
27462
|
+
statement() {
|
|
27463
|
+
const top = { height: 0, resolved: [], from: [] };
|
|
27464
|
+
if (this.startsSelect()) {
|
|
27465
|
+
top.from.push(this.select());
|
|
27466
|
+
} else if (this.takeWord("INSERT") || this.takeWord("REPLACE")) {
|
|
27467
|
+
if (this.takeWord("OR")) this.name();
|
|
27468
|
+
this.expectWord("INTO");
|
|
27469
|
+
this.qualifiedName();
|
|
27470
|
+
if (this.takeWord("AS")) this.name();
|
|
27471
|
+
if (this.isOp("(")) this.names();
|
|
27472
|
+
if (this.takeWord("DEFAULT")) this.expectWord("VALUES");
|
|
27473
|
+
else top.from.push(this.select());
|
|
27474
|
+
while (this.takeWord("ON")) {
|
|
27475
|
+
this.expectWord("CONFLICT");
|
|
27476
|
+
if (this.takeOp("(")) {
|
|
27477
|
+
top.resolved.push(...this.list());
|
|
27478
|
+
this.expectOp(")");
|
|
27479
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.expr());
|
|
27480
|
+
}
|
|
27481
|
+
this.expectWord("DO");
|
|
27482
|
+
if (this.takeWord("NOTHING")) continue;
|
|
27483
|
+
this.expectWord("UPDATE");
|
|
27484
|
+
this.expectWord("SET");
|
|
27485
|
+
this.assignments(top.resolved);
|
|
27486
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.expr());
|
|
27487
|
+
}
|
|
27488
|
+
this.returning(top.resolved);
|
|
27489
|
+
} else if (this.takeWord("UPDATE")) {
|
|
27490
|
+
if (this.takeWord("OR")) this.name();
|
|
27491
|
+
this.qualifiedName();
|
|
27492
|
+
this.alias();
|
|
27493
|
+
if (this.takeWord("INDEXED")) {
|
|
27494
|
+
this.expectWord("BY");
|
|
27495
|
+
this.name();
|
|
27496
|
+
}
|
|
27497
|
+
this.expectWord("SET");
|
|
27498
|
+
this.assignments(top.resolved);
|
|
27499
|
+
if (this.isWord("FROM")) throw new Unreadable("UPDATE \u2026 FROM");
|
|
27500
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.where());
|
|
27501
|
+
this.returning(top.resolved);
|
|
27502
|
+
} else if (this.takeWord("DELETE")) {
|
|
27503
|
+
this.expectWord("FROM");
|
|
27504
|
+
this.qualifiedName();
|
|
27505
|
+
this.alias();
|
|
27506
|
+
if (this.takeWord("INDEXED")) {
|
|
27507
|
+
this.expectWord("BY");
|
|
27508
|
+
this.name();
|
|
27509
|
+
}
|
|
27510
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.where());
|
|
27511
|
+
this.returning(top.resolved);
|
|
27512
|
+
} else throw new Unreadable("not a statement naro compiles");
|
|
27513
|
+
return top;
|
|
27514
|
+
}
|
|
27515
|
+
};
|
|
27516
|
+
function split(node, at) {
|
|
27517
|
+
const found = [];
|
|
27518
|
+
const pending = [node];
|
|
27519
|
+
for (let next = pending.pop(); next; next = pending.pop()) {
|
|
27520
|
+
const sides = next[at];
|
|
27521
|
+
if (sides) pending.push(sides[1], sides[0]);
|
|
27522
|
+
else found.push(next);
|
|
27523
|
+
}
|
|
27524
|
+
return found;
|
|
27525
|
+
}
|
|
27526
|
+
function nodeNeed(node, base2) {
|
|
27527
|
+
const at = base2 + node.height;
|
|
27528
|
+
let need = at;
|
|
27529
|
+
for (const select of node.subqueries)
|
|
27530
|
+
need = Math.max(need, selectNeed(select, at));
|
|
27531
|
+
return need;
|
|
27532
|
+
}
|
|
27533
|
+
function selectNeed(select, base2) {
|
|
27534
|
+
let need = base2;
|
|
27535
|
+
for (const node of select.resolved)
|
|
27536
|
+
need = Math.max(need, nodeNeed(node, base2));
|
|
27537
|
+
for (const inner of select.from)
|
|
27538
|
+
need = Math.max(need, selectNeed(inner, base2));
|
|
27539
|
+
return need;
|
|
27540
|
+
}
|
|
27541
|
+
function expressionDepth(sql, { planner = true } = {}) {
|
|
27542
|
+
try {
|
|
27543
|
+
const reader = new Reader(tokenize3(sql), planner);
|
|
27544
|
+
const top = reader.statement();
|
|
27545
|
+
if (!reader.atEnd()) return void 0;
|
|
27546
|
+
return Math.max(reader.deepest, selectNeed(top, 0));
|
|
27547
|
+
} catch (error51) {
|
|
27548
|
+
if (error51 instanceof Unreadable) return void 0;
|
|
27549
|
+
throw error51;
|
|
27550
|
+
}
|
|
27551
|
+
}
|
|
27552
|
+
|
|
27553
|
+
// ../../packages/data-core/src/policy-index.ts
|
|
27554
|
+
var INDEX_SQL = `SELECT t.name AS table_name, il.name AS index_name, il."partial" AS partial, ix.name AS column_name, ix.coll AS collation FROM pragma_table_list AS t JOIN pragma_index_list(t.name, t.schema) AS il JOIN pragma_index_xinfo(il.name, t.schema) AS ix WHERE t.schema = 'main' AND t.type = 'table' AND ${systemTablePredicate("t.name")} AND ix.seqno = 0`;
|
|
27555
|
+
function indexesFromRows(rows) {
|
|
27556
|
+
const tables = /* @__PURE__ */ new Map();
|
|
27557
|
+
for (const row of rows) {
|
|
27558
|
+
const table = row.table_name;
|
|
27559
|
+
const column2 = row.column_name;
|
|
27560
|
+
if (typeof table !== "string" || typeof column2 !== "string") continue;
|
|
27561
|
+
if (Number(row.partial) !== 0) continue;
|
|
27562
|
+
const key = asciiLower(table);
|
|
27563
|
+
const columns = tables.get(key) ?? /* @__PURE__ */ new Map();
|
|
27564
|
+
tables.set(key, columns);
|
|
27565
|
+
const collations = columns.get(asciiLower(column2)) ?? [];
|
|
27566
|
+
collations.push(
|
|
27567
|
+
typeof row.collation === "string" ? row.collation.toUpperCase() : "BINARY"
|
|
27568
|
+
);
|
|
27569
|
+
columns.set(asciiLower(column2), collations);
|
|
27570
|
+
}
|
|
27571
|
+
return tables;
|
|
27572
|
+
}
|
|
27573
|
+
var STORAGE_OBJECTS_INDEXES = /* @__PURE__ */ new Map(
|
|
27574
|
+
[
|
|
27575
|
+
["id", ["BINARY"]],
|
|
27576
|
+
["bucket_id", ["BINARY"]]
|
|
27577
|
+
]
|
|
27578
|
+
);
|
|
27579
|
+
function resolve8(scopes, qualifier, name) {
|
|
27580
|
+
const column2 = asciiLower(name);
|
|
27581
|
+
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
27582
|
+
const scope = scopes[index];
|
|
27583
|
+
if (qualifier !== null && !scope.names.includes(asciiLower(qualifier)))
|
|
27584
|
+
continue;
|
|
27585
|
+
const found = scope.table.byName.get(column2);
|
|
27586
|
+
if (found) return { column: found, scope: index };
|
|
27587
|
+
if (qualifier !== null) return void 0;
|
|
27588
|
+
}
|
|
27589
|
+
return void 0;
|
|
27590
|
+
}
|
|
27591
|
+
function realtimeTopic2(scopes) {
|
|
27592
|
+
const [root] = scopes;
|
|
27593
|
+
if (root?.table.name !== REALTIME_MESSAGES) return void 0;
|
|
27594
|
+
const column2 = root.table.byName.get("topic");
|
|
27595
|
+
return column2 ? { column: column2, scope: 0 } : void 0;
|
|
27596
|
+
}
|
|
27597
|
+
function valueScopes(scopes, value) {
|
|
27598
|
+
switch (value.kind) {
|
|
27599
|
+
case "column": {
|
|
27600
|
+
const found = resolve8(scopes, value.qualifier, value.name);
|
|
27601
|
+
return found ? [found.scope] : [];
|
|
27602
|
+
}
|
|
27603
|
+
case "func":
|
|
26305
27604
|
return value.args.flatMap((arg) => valueScopes(scopes, arg));
|
|
26306
27605
|
case "cast":
|
|
26307
27606
|
return valueScopes(scopes, value.operand);
|
|
@@ -26352,12 +27651,9 @@ function readsOutside(scopes, expr, depth, snapshot) {
|
|
|
26352
27651
|
}
|
|
26353
27652
|
}
|
|
26354
27653
|
function subqueryScopes2(scopes, subquery, snapshot) {
|
|
26355
|
-
const table =
|
|
27654
|
+
const table = resolveSubqueryTable(snapshot, subquery.table);
|
|
26356
27655
|
if (!table) return void 0;
|
|
26357
|
-
return [
|
|
26358
|
-
...scopes,
|
|
26359
|
-
{ table, names: [asciiLower(subquery.alias ?? table.name)] }
|
|
26360
|
-
];
|
|
27656
|
+
return [...scopes, { table, names: [asciiLower(subqueryName(subquery))] }];
|
|
26361
27657
|
}
|
|
26362
27658
|
function conjuncts(expr) {
|
|
26363
27659
|
if (expr === null) return [];
|
|
@@ -26371,24 +27667,27 @@ function columnAffinityOf(column2) {
|
|
|
26371
27667
|
if (affinity2 === "BLOB") return "blob";
|
|
26372
27668
|
return "numeric";
|
|
26373
27669
|
}
|
|
26374
|
-
function operandColumn(scopes,
|
|
27670
|
+
function operandColumn(scopes, operand) {
|
|
27671
|
+
const value = comparedValue(operand);
|
|
26375
27672
|
if (value.kind === "realtime") return realtimeTopic2(scopes);
|
|
26376
27673
|
if (value.kind !== "column") return void 0;
|
|
26377
27674
|
return resolve8(scopes, value.qualifier, value.name);
|
|
26378
27675
|
}
|
|
26379
27676
|
function valueAffinity(scopes, value) {
|
|
26380
|
-
if (value.kind === "cast") return "text";
|
|
27677
|
+
if (comparedValue(value).kind === "cast") return "text";
|
|
26381
27678
|
const found = operandColumn(scopes, value);
|
|
26382
27679
|
return found ? columnAffinityOf(found.column) : "none";
|
|
26383
27680
|
}
|
|
26384
27681
|
function valueCollation(scopes, value) {
|
|
26385
|
-
|
|
27682
|
+
const compared = comparedValue(value);
|
|
27683
|
+
if (compared.kind === "cast") return valueCollation(scopes, compared.operand);
|
|
26386
27684
|
const found = operandColumn(scopes, value);
|
|
26387
27685
|
return found ? found.column.collation : void 0;
|
|
26388
27686
|
}
|
|
26389
27687
|
function waysIn(scopes, where) {
|
|
26390
27688
|
const inner = scopes.length - 1;
|
|
26391
|
-
const own2 = (
|
|
27689
|
+
const own2 = (operand) => {
|
|
27690
|
+
const value = comparedValue(operand);
|
|
26392
27691
|
if (value.kind !== "column") return void 0;
|
|
26393
27692
|
const found = resolve8(scopes, value.qualifier, value.name);
|
|
26394
27693
|
return found?.scope === inner ? found.column : void 0;
|
|
@@ -26436,7 +27735,7 @@ function isRowid(table, column2) {
|
|
|
26436
27735
|
function blocked(table, indexes, way) {
|
|
26437
27736
|
if (isRowid(table, way.column))
|
|
26438
27737
|
return way.affinity === "text" ? "affinity" : void 0;
|
|
26439
|
-
const collations = indexes.get(asciiLower(table.name))?.get(asciiLower(way.column.name));
|
|
27738
|
+
const collations = (table.name === STORAGE_OBJECTS_TABLE ? STORAGE_OBJECTS_INDEXES : indexes.get(asciiLower(table.name)))?.get(asciiLower(way.column.name));
|
|
26440
27739
|
if (!collations || collations.length === 0) return "none";
|
|
26441
27740
|
const wanted = way.collation === void 0 ? way.column.collation : way.collation;
|
|
26442
27741
|
if (wanted !== null && !collations.includes(wanted)) return "collation";
|
|
@@ -26447,9 +27746,17 @@ function blocked(table, indexes, way) {
|
|
|
26447
27746
|
}
|
|
26448
27747
|
function problemMessage(policy, outer, table, ways, why) {
|
|
26449
27748
|
const runs = outer.name === REALTIME_MESSAGES ? "runs once for every channel join it checks" : `runs once for every row of ${tableLabel(outer)} it looks at`;
|
|
26450
|
-
const start = `Policy ${policy.name} on ${tableLabel(outer)}: its subquery on ${table
|
|
27749
|
+
const start = `Policy ${policy.name} on ${tableLabel(outer)}: its subquery on ${tableLabel(table)} ${runs}`;
|
|
26451
27750
|
const override = "or send it with allow_unindexed=1 (naro policy create --allow-unindexed) to accept a scan of every row";
|
|
26452
27751
|
const [way] = ways;
|
|
27752
|
+
if (table.name === STORAGE_OBJECTS_TABLE) {
|
|
27753
|
+
const column3 = way ? `${STORAGE_OBJECTS}.${way.column.name}` : "";
|
|
27754
|
+
if (way && why === "collation")
|
|
27755
|
+
return `${start}, and it compares ${column3} in collation ${String(way.collation ?? way.column.collation)}, which its index (BINARY) cannot search, so each run reads all of ${STORAGE_OBJECTS}. Write ${column3} on the left of = so that the comparison is BINARY \u2014 ${override}.`;
|
|
27756
|
+
if (way && why === "affinity")
|
|
27757
|
+
return `${start}, and it compares ${column3} with a number, which its index (text) cannot search, so each run reads all of ${STORAGE_OBJECTS}. Compare it with text, as in <value>::text \u2014 ${override}.`;
|
|
27758
|
+
return `${start}, and no condition of its WHERE compares ${STORAGE_OBJECTS}.bucket_id or ${STORAGE_OBJECTS}.id with = to a value from outside it, so each run reads all of ${STORAGE_OBJECTS}. Add bucket_id = '<bucket>' to it \u2014 ${override}.`;
|
|
27759
|
+
}
|
|
26453
27760
|
if (!way)
|
|
26454
27761
|
return `${start}, and no condition of its WHERE compares a column of ${table.name} with = to a value from outside it, so each run reads all of ${table.name}. Compare a column of ${table.name} with = (or IN) and index that column \u2014 ${override}.`;
|
|
26455
27762
|
const column2 = way.column.name;
|
|
@@ -26489,12 +27796,12 @@ function unindexedSubqueries(snapshot, indexes, policies) {
|
|
|
26489
27796
|
const ways = waysIn(inner, expr.subquery.where);
|
|
26490
27797
|
const reasons = ways.map((way) => blocked(table, indexes, way));
|
|
26491
27798
|
if (!reasons.includes(void 0)) {
|
|
26492
|
-
const order = ["none", "collation", "affinity"];
|
|
27799
|
+
const order = table.name === STORAGE_OBJECTS_TABLE ? ["collation", "affinity", "none"] : ["none", "collation", "affinity"];
|
|
26493
27800
|
const best = order.map((reason) => reasons.indexOf(reason)).find((index) => index >= 0);
|
|
26494
27801
|
const way = best === void 0 ? void 0 : ways[best];
|
|
26495
27802
|
found.push({
|
|
26496
27803
|
policy,
|
|
26497
|
-
table: table
|
|
27804
|
+
table: tableLabel(table),
|
|
26498
27805
|
message: problemMessage(
|
|
26499
27806
|
policy,
|
|
26500
27807
|
outer,
|
|
@@ -26688,6 +27995,14 @@ function privateChannelAccess(input) {
|
|
|
26688
27995
|
return { ok: true, statement: { sql, params } };
|
|
26689
27996
|
}
|
|
26690
27997
|
|
|
27998
|
+
// ../../packages/data-core/src/storage-transfer.ts
|
|
27999
|
+
function moveToPending(to) {
|
|
28000
|
+
return JSON.stringify({ moveTo: to });
|
|
28001
|
+
}
|
|
28002
|
+
function moveFromPending(from) {
|
|
28003
|
+
return JSON.stringify({ moveFrom: from });
|
|
28004
|
+
}
|
|
28005
|
+
|
|
26691
28006
|
// ../../packages/data-core/src/storage-sql.ts
|
|
26692
28007
|
var STORAGE_NONCE_MAX_DIGITS = 64;
|
|
26693
28008
|
function nonceOf(nonce) {
|
|
@@ -26817,6 +28132,73 @@ function claimOverwrite(i) {
|
|
|
26817
28132
|
[[i.lockId, i.expiresAt, i.pending, i.bucket, i.name], room.values]
|
|
26818
28133
|
);
|
|
26819
28134
|
}
|
|
28135
|
+
var MOVE_KEPT_COLUMNS = [
|
|
28136
|
+
"id",
|
|
28137
|
+
"bucket_id",
|
|
28138
|
+
"owner",
|
|
28139
|
+
"owner_id",
|
|
28140
|
+
"version",
|
|
28141
|
+
"created_at",
|
|
28142
|
+
"updated_at",
|
|
28143
|
+
"metadata"
|
|
28144
|
+
];
|
|
28145
|
+
function moveCheckRow(i) {
|
|
28146
|
+
return {
|
|
28147
|
+
row: { name: i.to },
|
|
28148
|
+
stored: { alias: STORAGE_OBJECT_ALIAS, columns: MOVE_KEPT_COLUMNS }
|
|
28149
|
+
};
|
|
28150
|
+
}
|
|
28151
|
+
function claimMove(i) {
|
|
28152
|
+
const nonce = nonceOf(i.nonce);
|
|
28153
|
+
const slotNonce = nonceOf(i.slotNonce);
|
|
28154
|
+
const source = compose(
|
|
28155
|
+
[
|
|
28156
|
+
`UPDATE ${STORAGE_OBJECTS_TABLE} AS o SET lock_id = ?, lock_expires_at = ?, pending = ? WHERE o.id = ? AND o.name = ? AND CASE WHEN NOT (`,
|
|
28157
|
+
i.selectUsing,
|
|
28158
|
+
`) THEN ${marker("hidden", nonce)} WHEN NOT EXISTS (SELECT 1 FROM ${STORAGE_BUCKETS_TABLE} AS b WHERE b.id = o.bucket_id AND b.${LIVE_BUCKET}) THEN ${marker("bucket", nonce)} WHEN o.lock_id IS NOT NULL OR o.version IS NOT ? THEN ${marker("busy", nonce)} WHEN NOT (`,
|
|
28159
|
+
i.updateUsing,
|
|
28160
|
+
`) THEN ${marker("denied", nonce)} WHEN (`,
|
|
28161
|
+
i.check,
|
|
28162
|
+
// The check is 1 or fails with its marker; anything else is no
|
|
28163
|
+
// check that held — refused as the update policy refuses.
|
|
28164
|
+
`) <> 1 THEN ${marker("denied", nonce)} WHEN ${PROJECT_BYTES} + o.size > ? THEN ${marker("quota", nonce)} ELSE 1 END RETURNING id, size`
|
|
28165
|
+
],
|
|
28166
|
+
[
|
|
28167
|
+
[i.lockId, i.expiresAt, moveToPending(i.to), i.id, i.from],
|
|
28168
|
+
[i.version],
|
|
28169
|
+
[i.limitBytes]
|
|
28170
|
+
]
|
|
28171
|
+
);
|
|
28172
|
+
const slot = compose(
|
|
28173
|
+
[
|
|
28174
|
+
`INSERT INTO ${STORAGE_OBJECTS_TABLE} (id, bucket_id, name, owner, owner_id, metadata, version, parent, size, status, lock_id, lock_expires_at, pending) SELECT ?, s.bucket_id, ?, NULL, NULL, '{}', NULL, ?, s.size, 'uploading', s.lock_id, s.lock_expires_at, ? FROM ${STORAGE_OBJECTS_TABLE} AS s WHERE s.id = ? AND s.lock_id = ? AND CASE WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} AS d WHERE d.bucket_id = s.bucket_id AND d.name = ? AND d.status = 'ready') THEN ${marker("exists", slotNonce)} WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} AS d WHERE d.bucket_id = s.bucket_id AND d.name = ?) THEN ${marker("busy", slotNonce)} ELSE 1 END RETURNING id`
|
|
28175
|
+
],
|
|
28176
|
+
[
|
|
28177
|
+
[
|
|
28178
|
+
i.slotId,
|
|
28179
|
+
i.to,
|
|
28180
|
+
objectParent(i.to),
|
|
28181
|
+
moveFromPending(i.from),
|
|
28182
|
+
i.id,
|
|
28183
|
+
i.lockId,
|
|
28184
|
+
i.to,
|
|
28185
|
+
i.to
|
|
28186
|
+
]
|
|
28187
|
+
]
|
|
28188
|
+
);
|
|
28189
|
+
return [source, slot];
|
|
28190
|
+
}
|
|
28191
|
+
function copySourceGuard(i) {
|
|
28192
|
+
const nonce = nonceOf(i.nonce);
|
|
28193
|
+
return compose(
|
|
28194
|
+
[
|
|
28195
|
+
`SELECT CASE WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} AS o WHERE o.id = ? AND o.name = ? AND (`,
|
|
28196
|
+
i.using,
|
|
28197
|
+
`)) THEN 1 ELSE ${marker("source", nonce)} END AS ok`
|
|
28198
|
+
],
|
|
28199
|
+
[[i.id, i.name]]
|
|
28200
|
+
);
|
|
28201
|
+
}
|
|
26820
28202
|
function claimDelete(i) {
|
|
26821
28203
|
return compose(
|
|
26822
28204
|
[
|
|
@@ -26848,6 +28230,16 @@ function readObjects(i) {
|
|
|
26848
28230
|
[[i.bucket, JSON.stringify([...new Set(i.names)])]]
|
|
26849
28231
|
);
|
|
26850
28232
|
}
|
|
28233
|
+
function readTransferSource(i) {
|
|
28234
|
+
return compose(
|
|
28235
|
+
[
|
|
28236
|
+
`SELECT o.id, o.version, o.size, o.metadata FROM ${STORAGE_OBJECTS_TABLE} AS o WHERE o.bucket_id = ? AND o.name = ? AND (`,
|
|
28237
|
+
i.using,
|
|
28238
|
+
")"
|
|
28239
|
+
],
|
|
28240
|
+
[[i.bucket, i.name]]
|
|
28241
|
+
);
|
|
28242
|
+
}
|
|
26851
28243
|
var SORT_COLUMNS = {
|
|
26852
28244
|
name: "name",
|
|
26853
28245
|
created_at: "created_at",
|
|
@@ -26939,7 +28331,7 @@ function storageUsing(input) {
|
|
|
26939
28331
|
assertFragmentAlias(alias, "storageUsing");
|
|
26940
28332
|
const on = target(snapshot);
|
|
26941
28333
|
if ("ok" in on) return on;
|
|
26942
|
-
const status =
|
|
28334
|
+
const status = storageReadySql(alias);
|
|
26943
28335
|
if (context.role === "service")
|
|
26944
28336
|
return { ok: true, fragment: { sql: status, params: [] } };
|
|
26945
28337
|
try {
|
|
@@ -27029,12 +28421,12 @@ var CONTEXTS = [
|
|
|
27029
28421
|
userId: "naro-policy-check",
|
|
27030
28422
|
email: "check@naro.invalid",
|
|
27031
28423
|
emailVerified: true
|
|
27032
|
-
}
|
|
28424
|
+
},
|
|
28425
|
+
{ role: "authenticated", userId: "naro-policy-check" }
|
|
27033
28426
|
];
|
|
27034
28427
|
function probes(table, context, owners) {
|
|
27035
28428
|
const writable = table.columns.filter((column2) => !column2.generated);
|
|
27036
|
-
const
|
|
27037
|
-
const filters = first ? [{ operator: "is", column: first.name, value: null }] : [];
|
|
28429
|
+
const filters = leastFilters(table.columns[0]?.name);
|
|
27038
28430
|
const row = Object.fromEntries(
|
|
27039
28431
|
writable.map((column2) => [
|
|
27040
28432
|
column2.name,
|
|
@@ -27042,7 +28434,7 @@ function probes(table, context, owners) {
|
|
|
27042
28434
|
])
|
|
27043
28435
|
);
|
|
27044
28436
|
const queries = [
|
|
27045
|
-
{ version: 1, action: "select", table: table.name, columns: "*" },
|
|
28437
|
+
{ version: 1, action: "select", table: table.name, columns: "*", filters },
|
|
27046
28438
|
{ version: 1, action: "insert", table: table.name, rows: [row] },
|
|
27047
28439
|
{
|
|
27048
28440
|
version: 1,
|
|
@@ -27065,16 +28457,35 @@ function probes(table, context, owners) {
|
|
|
27065
28457
|
}
|
|
27066
28458
|
var STORAGE_PROBE_ALIAS = "o";
|
|
27067
28459
|
var STORAGE_PROBE_NONCE = "f".repeat(STORAGE_NONCE_MAX_DIGITS);
|
|
27068
|
-
var
|
|
27069
|
-
|
|
27070
|
-
|
|
27071
|
-
|
|
27072
|
-
|
|
28460
|
+
var UNVERIFIED = {
|
|
28461
|
+
role: "authenticated",
|
|
28462
|
+
userId: "naro-policy-check",
|
|
28463
|
+
emailVerified: false
|
|
28464
|
+
};
|
|
28465
|
+
function depthContexts(contexts, claims) {
|
|
28466
|
+
if (claims) return [...contexts, UNVERIFIED];
|
|
28467
|
+
return contexts.filter(
|
|
28468
|
+
(context) => context.role !== "authenticated" || context.emailVerified === true
|
|
28469
|
+
);
|
|
28470
|
+
}
|
|
28471
|
+
function readsClaims(policies) {
|
|
28472
|
+
return policies.some(
|
|
28473
|
+
(policy) => [policy.using, policy.check].some(
|
|
28474
|
+
(text2) => text2 !== null && /auth\s*\.\s*jwt\s*\(/i.test(text2)
|
|
28475
|
+
)
|
|
28476
|
+
);
|
|
28477
|
+
}
|
|
28478
|
+
function callerName(context) {
|
|
28479
|
+
if (context.role !== "authenticated") return context.role;
|
|
28480
|
+
if (context.emailVerified === true) return "authenticated";
|
|
28481
|
+
return context.emailVerified === false ? "authenticated with no verified email" : "authenticated with no email claims";
|
|
28482
|
+
}
|
|
28483
|
+
function storageRefusals(snapshot, table, policies, measuring, measured) {
|
|
27073
28484
|
const refusals = [];
|
|
27074
28485
|
const row = Object.fromEntries(
|
|
27075
28486
|
table.columns.map((column2) => [column2.name, ""])
|
|
27076
28487
|
);
|
|
27077
|
-
for (const context of
|
|
28488
|
+
for (const context of /* @__PURE__ */ new Set([...CONTEXTS, ...measuring])) {
|
|
27078
28489
|
const using = (commands) => {
|
|
27079
28490
|
const result = storageUsing({
|
|
27080
28491
|
snapshot,
|
|
@@ -27110,25 +28521,33 @@ function storageRefusals(snapshot, table, policies) {
|
|
|
27110
28521
|
overwriteCheckRow({ metadata: "", now: "", version: "" }),
|
|
27111
28522
|
true
|
|
27112
28523
|
);
|
|
27113
|
-
|
|
28524
|
+
const moveCheck = check2("update", moveCheckRow({ to: "" }), true);
|
|
28525
|
+
for (const { label: label2, statement: statement2 } of storageStatements(
|
|
27114
28526
|
select,
|
|
27115
28527
|
update,
|
|
27116
28528
|
remove,
|
|
27117
28529
|
[insertCheck, upsertCheck],
|
|
27118
|
-
updateCheck
|
|
28530
|
+
updateCheck,
|
|
28531
|
+
moveCheck
|
|
27119
28532
|
)) {
|
|
27120
28533
|
const problem = storageStatementProblem(statement2, "select");
|
|
27121
28534
|
if (problem) refusals.push(problem);
|
|
28535
|
+
else if (measuring.includes(context))
|
|
28536
|
+
measured(label2, statement2.sql, callerName(context));
|
|
27122
28537
|
}
|
|
27123
28538
|
}
|
|
27124
28539
|
return refusals;
|
|
27125
28540
|
}
|
|
27126
|
-
function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
28541
|
+
function storageStatements(select, update, remove, insertChecks, updateCheck, moveCheck) {
|
|
27127
28542
|
const statements = [];
|
|
28543
|
+
const add = (label2, ...made) => {
|
|
28544
|
+
for (const statement2 of made) statements.push({ label: label2, statement: statement2 });
|
|
28545
|
+
};
|
|
27128
28546
|
const value = "";
|
|
27129
|
-
for (const insertCheck of insertChecks)
|
|
28547
|
+
for (const [index, insertCheck] of insertChecks.entries())
|
|
27130
28548
|
if (insertCheck)
|
|
27131
|
-
|
|
28549
|
+
add(
|
|
28550
|
+
index === 0 ? "an upload's claim" : "an upsert's claim",
|
|
27132
28551
|
claimUpload({
|
|
27133
28552
|
id: value,
|
|
27134
28553
|
bucket: value,
|
|
@@ -27144,7 +28563,8 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27144
28563
|
})
|
|
27145
28564
|
);
|
|
27146
28565
|
if (select && update && updateCheck)
|
|
27147
|
-
|
|
28566
|
+
add(
|
|
28567
|
+
"an overwrite's claim",
|
|
27148
28568
|
claimOverwrite({
|
|
27149
28569
|
bucket: value,
|
|
27150
28570
|
name: value,
|
|
@@ -27159,8 +28579,28 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27159
28579
|
nonce: STORAGE_PROBE_NONCE
|
|
27160
28580
|
})
|
|
27161
28581
|
);
|
|
28582
|
+
if (select && update && moveCheck)
|
|
28583
|
+
add(
|
|
28584
|
+
"a move's claim",
|
|
28585
|
+
...claimMove({
|
|
28586
|
+
id: value,
|
|
28587
|
+
version: value,
|
|
28588
|
+
from: value,
|
|
28589
|
+
to: value,
|
|
28590
|
+
slotId: value,
|
|
28591
|
+
lockId: value,
|
|
28592
|
+
expiresAt: value,
|
|
28593
|
+
limitBytes: 0,
|
|
28594
|
+
selectUsing: select,
|
|
28595
|
+
updateUsing: update,
|
|
28596
|
+
check: moveCheck,
|
|
28597
|
+
nonce: STORAGE_PROBE_NONCE,
|
|
28598
|
+
slotNonce: STORAGE_PROBE_NONCE
|
|
28599
|
+
})
|
|
28600
|
+
);
|
|
27162
28601
|
if (remove)
|
|
27163
|
-
|
|
28602
|
+
add(
|
|
28603
|
+
"a delete's claim",
|
|
27164
28604
|
claimDelete({
|
|
27165
28605
|
bucket: value,
|
|
27166
28606
|
names: [],
|
|
@@ -27169,13 +28609,27 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27169
28609
|
expiresAt: value
|
|
27170
28610
|
})
|
|
27171
28611
|
);
|
|
27172
|
-
if (select)
|
|
27173
|
-
|
|
27174
|
-
|
|
27175
|
-
|
|
27176
|
-
|
|
27177
|
-
|
|
27178
|
-
|
|
28612
|
+
if (select) {
|
|
28613
|
+
add("a read", readObject2({ bucket: value, name: value, using: select }));
|
|
28614
|
+
add(
|
|
28615
|
+
"a read of several objects",
|
|
28616
|
+
readObjects({ bucket: value, names: [], using: select })
|
|
28617
|
+
);
|
|
28618
|
+
add(
|
|
28619
|
+
"a move's or a copy's read",
|
|
28620
|
+
readTransferSource({ bucket: value, name: value, using: select })
|
|
28621
|
+
);
|
|
28622
|
+
add(
|
|
28623
|
+
"a copy's guard",
|
|
28624
|
+
copySourceGuard({
|
|
28625
|
+
id: value,
|
|
28626
|
+
name: value,
|
|
28627
|
+
using: select,
|
|
28628
|
+
nonce: STORAGE_PROBE_NONCE
|
|
28629
|
+
})
|
|
28630
|
+
);
|
|
28631
|
+
add(
|
|
28632
|
+
"a listing",
|
|
27179
28633
|
listObjects({
|
|
27180
28634
|
bucket: value,
|
|
27181
28635
|
prefix: "p",
|
|
@@ -27186,6 +28640,7 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27186
28640
|
using: select
|
|
27187
28641
|
})
|
|
27188
28642
|
);
|
|
28643
|
+
}
|
|
27189
28644
|
return statements;
|
|
27190
28645
|
}
|
|
27191
28646
|
var REALTIME_PROBE_CHANNEL = "naro-policy-check";
|
|
@@ -27198,13 +28653,13 @@ function joinReason(reason) {
|
|
|
27198
28653
|
return JOIN_STATEMENT;
|
|
27199
28654
|
return reason;
|
|
27200
28655
|
}
|
|
27201
|
-
function realtimeRefusals(snapshot, policies) {
|
|
28656
|
+
function realtimeRefusals(snapshot, policies, measuring, measured) {
|
|
27202
28657
|
const refusals = [];
|
|
27203
28658
|
const applying = (command) => policies.filter(
|
|
27204
28659
|
(policy) => asciiLower(policy.table) !== REALTIME_MESSAGES || policy.command === command || policy.command === "all"
|
|
27205
28660
|
);
|
|
27206
28661
|
for (const set2 of [policies, applying("select"), applying("insert")])
|
|
27207
|
-
for (const context of
|
|
28662
|
+
for (const context of /* @__PURE__ */ new Set([...CONTEXTS, ...measuring])) {
|
|
27208
28663
|
const plan = privateChannelAccess({
|
|
27209
28664
|
snapshot,
|
|
27210
28665
|
context,
|
|
@@ -27212,7 +28667,11 @@ function realtimeRefusals(snapshot, policies) {
|
|
|
27212
28667
|
channel: REALTIME_PROBE_CHANNEL,
|
|
27213
28668
|
nonce: REALTIME_PROBE_NONCE
|
|
27214
28669
|
});
|
|
27215
|
-
if (plan.ok)
|
|
28670
|
+
if (plan.ok) {
|
|
28671
|
+
if ("statement" in plan && measuring.includes(context))
|
|
28672
|
+
measured("a join", plan.statement.sql, callerName(context));
|
|
28673
|
+
continue;
|
|
28674
|
+
}
|
|
27216
28675
|
const reason = joinReason(plan.reason);
|
|
27217
28676
|
refusals.push(
|
|
27218
28677
|
reason === void 0 ? { error: plan.error } : { error: plan.error, reason }
|
|
@@ -27254,9 +28713,48 @@ function namedInRefusal(message, policies) {
|
|
|
27254
28713
|
}
|
|
27255
28714
|
return found;
|
|
27256
28715
|
}
|
|
27257
|
-
function
|
|
28716
|
+
function probeLabel(query) {
|
|
28717
|
+
switch (query.action) {
|
|
28718
|
+
case "select":
|
|
28719
|
+
return "a select";
|
|
28720
|
+
case "insert":
|
|
28721
|
+
return query.returning === void 0 ? "an insert" : "an insert with returning";
|
|
28722
|
+
case "update":
|
|
28723
|
+
return "an update";
|
|
28724
|
+
case "delete":
|
|
28725
|
+
return "a delete";
|
|
28726
|
+
}
|
|
28727
|
+
}
|
|
28728
|
+
function inlinedTables(sql, probed) {
|
|
28729
|
+
const names = /* @__PURE__ */ new Set();
|
|
28730
|
+
for (const [, written] of sql.matchAll(
|
|
28731
|
+
/FROM ("(?:[^"]|"")*"|[A-Za-z_][A-Za-z0-9_$]*) AS "?_naro_s\d+"?/g
|
|
28732
|
+
)) {
|
|
28733
|
+
if (written === void 0) continue;
|
|
28734
|
+
const name = written.startsWith('"') ? written.slice(1, -1).replaceAll('""', '"') : written;
|
|
28735
|
+
const label2 = name === STORAGE_OBJECTS_TABLE ? STORAGE_OBJECTS : name;
|
|
28736
|
+
if (label2 !== probed) names.add(label2);
|
|
28737
|
+
}
|
|
28738
|
+
return [...names];
|
|
28739
|
+
}
|
|
28740
|
+
function listed(names) {
|
|
28741
|
+
if (names.length < 2) return names.join("");
|
|
28742
|
+
return `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
28743
|
+
}
|
|
28744
|
+
function tooDeepMessage({
|
|
28745
|
+
table,
|
|
28746
|
+
label: label2,
|
|
28747
|
+
caller,
|
|
28748
|
+
depth,
|
|
28749
|
+
inlined
|
|
28750
|
+
}) {
|
|
28751
|
+
const others = inlined.length === 0 ? "" : `, with those of ${listed(inlined)} that they read,`;
|
|
28752
|
+
return `The policies of ${table}${others} nest an expression more than ${String(D1_EXPRESSION_DEPTH)} deep into one statement: ${label2} as ${caller} is ${String(depth)} deep.`;
|
|
28753
|
+
}
|
|
28754
|
+
function problems(snapshot, policies, options, claims) {
|
|
27258
28755
|
const found = /* @__PURE__ */ new Map();
|
|
27259
28756
|
const loaded = loadNamed(policies);
|
|
28757
|
+
const measuring = depthContexts(CONTEXTS, claims ?? readsClaims(loaded));
|
|
27260
28758
|
for (const policy of loaded)
|
|
27261
28759
|
if (policy.invalid !== null)
|
|
27262
28760
|
found.set(`read:${policy.table}:${policy.name}`, {
|
|
@@ -27318,24 +28816,30 @@ function problems(snapshot, policies, options) {
|
|
|
27318
28816
|
}
|
|
27319
28817
|
}
|
|
27320
28818
|
const probe = loadNamed(
|
|
27321
|
-
[...tables.values()].
|
|
27322
|
-
|
|
27323
|
-
|
|
27324
|
-
|
|
27325
|
-
|
|
27326
|
-
|
|
27327
|
-
|
|
27328
|
-
|
|
27329
|
-
|
|
27330
|
-
|
|
28819
|
+
[...tables.values()].flatMap((table) => {
|
|
28820
|
+
const roles = POLICY_ROLES.filter(
|
|
28821
|
+
(role) => applicablePolicies(loaded, table.name, "select", role).permissive.length === 0
|
|
28822
|
+
);
|
|
28823
|
+
return roles.length === 0 ? [] : [
|
|
28824
|
+
{
|
|
28825
|
+
table: table.name,
|
|
28826
|
+
name: "naro_policy_check",
|
|
28827
|
+
permissive: true,
|
|
28828
|
+
command: "select",
|
|
28829
|
+
roles,
|
|
28830
|
+
using: "true",
|
|
28831
|
+
check: null,
|
|
28832
|
+
owner: null
|
|
28833
|
+
}
|
|
28834
|
+
];
|
|
28835
|
+
})
|
|
27331
28836
|
);
|
|
27332
|
-
const compiled = [...loaded, ...probe];
|
|
27333
28837
|
const refused = (probed, error51, reason) => {
|
|
27334
28838
|
if (error51.code !== "POLICY_INVALID") return;
|
|
27335
|
-
const message = reason === void 0 ? error51.message : `${error51.message.replace(/\.$/, "")}: ${reason}.`;
|
|
28839
|
+
const message = reason === STORAGE_UNAVAILABLE_REASON ? STORAGE_UNAVAILABLE_MESSAGE : reason === void 0 ? error51.message : `${error51.message.replace(/\.$/, "")}: ${reason}.`;
|
|
27336
28840
|
const named = namedInRefusal(error51.message, loaded);
|
|
27337
28841
|
const blamed2 = named === void 0 ? probed : tableLabelOf(snapshot, named.table);
|
|
27338
|
-
const key = `${blamed2}:${message}`;
|
|
28842
|
+
const key = reason === STORAGE_UNAVAILABLE_REASON ? `${blamed2}:${named?.name ?? ""}:${message}` : `${blamed2}:${message}`;
|
|
27339
28843
|
if (!found.has(key))
|
|
27340
28844
|
found.set(key, {
|
|
27341
28845
|
problem: {
|
|
@@ -27347,12 +28851,29 @@ function problems(snapshot, policies, options) {
|
|
|
27347
28851
|
policy: ""
|
|
27348
28852
|
});
|
|
27349
28853
|
};
|
|
28854
|
+
const deepest = /* @__PURE__ */ new Map();
|
|
28855
|
+
const measured = (table) => (label2, sql, caller) => {
|
|
28856
|
+
const depth = expressionDepth(sql);
|
|
28857
|
+
const key = `depth:${table}:${label2}:${caller}`;
|
|
28858
|
+
if (depth !== void 0 && depth > (deepest.get(key)?.depth ?? D1_EXPRESSION_DEPTH))
|
|
28859
|
+
deepest.set(key, {
|
|
28860
|
+
table,
|
|
28861
|
+
label: label2,
|
|
28862
|
+
caller,
|
|
28863
|
+
depth,
|
|
28864
|
+
inlined: inlinedTables(sql, table)
|
|
28865
|
+
});
|
|
28866
|
+
};
|
|
27350
28867
|
for (const table of tables.values()) {
|
|
27351
28868
|
const owners = new Set(
|
|
27352
28869
|
policiesOn(loaded, table.name).flatMap(
|
|
27353
28870
|
(policy) => policy.owner ? [asciiLower(policy.owner)] : []
|
|
27354
28871
|
)
|
|
27355
28872
|
);
|
|
28873
|
+
const compiled = [
|
|
28874
|
+
...loaded,
|
|
28875
|
+
...probe.filter((policy) => policy.table === table.name)
|
|
28876
|
+
];
|
|
27356
28877
|
for (const context of CONTEXTS)
|
|
27357
28878
|
for (const query of probes(table, context, owners)) {
|
|
27358
28879
|
const decision = decideQuery(context, query, compiled, table.name);
|
|
@@ -27363,13 +28884,27 @@ function problems(snapshot, policies, options) {
|
|
|
27363
28884
|
};
|
|
27364
28885
|
if (!result.ok) refused(table.name, result.error, result.reason);
|
|
27365
28886
|
}
|
|
28887
|
+
for (const context of measuring)
|
|
28888
|
+
for (const query of probes(table, context, owners)) {
|
|
28889
|
+
const real = decideQuery(context, query, loaded, table.name);
|
|
28890
|
+
const made = real.allow ? compile(query, snapshot, real, context) : void 0;
|
|
28891
|
+
if (made?.ok)
|
|
28892
|
+
for (const statement2 of made.statements)
|
|
28893
|
+
measured(table.name)(
|
|
28894
|
+
probeLabel(query),
|
|
28895
|
+
statement2.sql,
|
|
28896
|
+
callerName(context)
|
|
28897
|
+
);
|
|
28898
|
+
}
|
|
27366
28899
|
}
|
|
27367
28900
|
if (policiesOn(loaded, STORAGE_OBJECTS).length > 0) {
|
|
27368
28901
|
if (snapshot.storage)
|
|
27369
28902
|
for (const { error: error51, reason } of storageRefusals(
|
|
27370
28903
|
snapshot,
|
|
27371
28904
|
snapshot.storage,
|
|
27372
|
-
loaded
|
|
28905
|
+
loaded,
|
|
28906
|
+
measuring,
|
|
28907
|
+
measured(STORAGE_OBJECTS)
|
|
27373
28908
|
))
|
|
27374
28909
|
refused(STORAGE_OBJECTS, error51, reason);
|
|
27375
28910
|
else
|
|
@@ -27383,8 +28918,22 @@ function problems(snapshot, policies, options) {
|
|
|
27383
28918
|
});
|
|
27384
28919
|
}
|
|
27385
28920
|
if (policiesOn(loaded, REALTIME_MESSAGES).length > 0)
|
|
27386
|
-
for (const { error: error51, reason } of realtimeRefusals(
|
|
28921
|
+
for (const { error: error51, reason } of realtimeRefusals(
|
|
28922
|
+
snapshot,
|
|
28923
|
+
loaded,
|
|
28924
|
+
measuring,
|
|
28925
|
+
measured(REALTIME_MESSAGES)
|
|
28926
|
+
))
|
|
27387
28927
|
refused(REALTIME_MESSAGES, error51, reason);
|
|
28928
|
+
for (const [key, tooDeep] of deepest)
|
|
28929
|
+
found.set(key, {
|
|
28930
|
+
problem: {
|
|
28931
|
+
table: tooDeep.table,
|
|
28932
|
+
message: tooDeepMessage(tooDeep),
|
|
28933
|
+
kind: "compile"
|
|
28934
|
+
},
|
|
28935
|
+
policy: ""
|
|
28936
|
+
});
|
|
27388
28937
|
if (options.indexes && options.allowUnindexed !== true)
|
|
27389
28938
|
for (const problem of unindexedSubqueries(
|
|
27390
28939
|
snapshot,
|
|
@@ -27501,16 +29050,16 @@ function readCreate(stream, start) {
|
|
|
27501
29050
|
let check2 = null;
|
|
27502
29051
|
let position = start.position;
|
|
27503
29052
|
if (stream.acceptKeyword("using")) {
|
|
27504
|
-
const
|
|
27505
|
-
using =
|
|
27506
|
-
position =
|
|
29053
|
+
const clause2 = readClause(stream);
|
|
29054
|
+
using = clause2.text;
|
|
29055
|
+
position = clause2.position;
|
|
27507
29056
|
}
|
|
27508
29057
|
if (isKeyword(stream.peek(), "with")) {
|
|
27509
29058
|
stream.next();
|
|
27510
29059
|
stream.expectKeyword("check");
|
|
27511
|
-
const
|
|
27512
|
-
check2 =
|
|
27513
|
-
position =
|
|
29060
|
+
const clause2 = readClause(stream);
|
|
29061
|
+
check2 = clause2.text;
|
|
29062
|
+
position = clause2.position;
|
|
27514
29063
|
}
|
|
27515
29064
|
if (!roles.ok) throw new Error("unreachable");
|
|
27516
29065
|
readEnd(stream);
|
|
@@ -27702,10 +29251,10 @@ function isBlankStart(char) {
|
|
|
27702
29251
|
function isBlank(char) {
|
|
27703
29252
|
return isBlankStart(char) || char === "\v";
|
|
27704
29253
|
}
|
|
27705
|
-
function
|
|
29254
|
+
function isWordStart2(char) {
|
|
27706
29255
|
return /[A-Za-z_]/.test(char) || char.charCodeAt(0) >= 128;
|
|
27707
29256
|
}
|
|
27708
|
-
function
|
|
29257
|
+
function isWordPart2(char) {
|
|
27709
29258
|
return /[A-Za-z0-9_$]/.test(char) || char.charCodeAt(0) >= 128;
|
|
27710
29259
|
}
|
|
27711
29260
|
function skipBlanks(text2, from) {
|
|
@@ -27759,9 +29308,9 @@ function headTokens(text2) {
|
|
|
27759
29308
|
index = close + 1;
|
|
27760
29309
|
} else if ((char === "x" || char === "X") && text2.charAt(index + 1) === "'") {
|
|
27761
29310
|
break;
|
|
27762
|
-
} else if (
|
|
29311
|
+
} else if (isWordStart2(char)) {
|
|
27763
29312
|
let end = index + 1;
|
|
27764
|
-
while (end < text2.length &&
|
|
29313
|
+
while (end < text2.length && isWordPart2(text2.charAt(end))) end++;
|
|
27765
29314
|
tokens.push({ kind: "word", text: text2.slice(index, end) });
|
|
27766
29315
|
index = end;
|
|
27767
29316
|
} else if (char === "." && !/[0-9]/.test(text2.charAt(index + 1))) {
|
|
@@ -27777,6 +29326,10 @@ var NO_NAME = {
|
|
|
27777
29326
|
kind: "unreadable",
|
|
27778
29327
|
reason: "the table name could not be read"
|
|
27779
29328
|
};
|
|
29329
|
+
var NO_COLUMN = {
|
|
29330
|
+
kind: "unreadable",
|
|
29331
|
+
reason: "the column name could not be read"
|
|
29332
|
+
};
|
|
27780
29333
|
var OTHER_SCHEMA = {
|
|
27781
29334
|
kind: "unreadable",
|
|
27782
29335
|
reason: "a table in another schema"
|
|
@@ -27802,8 +29355,9 @@ var Head = class {
|
|
|
27802
29355
|
return token.kind === "word" || token.kind === "name" ? token.text : void 0;
|
|
27803
29356
|
}
|
|
27804
29357
|
/**
|
|
27805
|
-
* SQLite's `fullname`. `main.` is the database a migration writes
|
|
27806
|
-
*
|
|
29358
|
+
* SQLite's `fullname`. `main.` is the database a migration writes: the
|
|
29359
|
+
* name goes without it, and `main` says it was written (known-issues
|
|
29360
|
+
* #226); `temp.` (in any case, quoted or not, as sqlite3FindDbName
|
|
27807
29361
|
* matches it) is a table that goes with the connection; `public.` is
|
|
27808
29362
|
* what a Postgres migration writes, said in its own words; any other
|
|
27809
29363
|
* schema is one D1 does not have.
|
|
@@ -27812,17 +29366,21 @@ var Head = class {
|
|
|
27812
29366
|
const first = this.nameAt(at);
|
|
27813
29367
|
if (first === void 0) return { ok: false, head: NO_NAME };
|
|
27814
29368
|
if (this.token(at + 1).kind !== "dot")
|
|
27815
|
-
return { ok: true, name: first, next: at + 1 };
|
|
29369
|
+
return { ok: true, name: first, next: at + 1, main: false };
|
|
27816
29370
|
const second = this.nameAt(at + 2);
|
|
27817
29371
|
if (second === void 0) return { ok: false, head: NO_NAME };
|
|
27818
29372
|
const schema = asciiLower(first);
|
|
27819
|
-
if (schema === "main")
|
|
29373
|
+
if (schema === "main")
|
|
29374
|
+
return { ok: true, name: second, next: at + 3, main: true };
|
|
27820
29375
|
let head = OTHER_SCHEMA;
|
|
27821
29376
|
if (schema === "temp") head = OTHER;
|
|
27822
29377
|
else if (schema === "public") head = PUBLIC_SCHEMA;
|
|
27823
29378
|
return { ok: false, head, next: at + 3 };
|
|
27824
29379
|
}
|
|
27825
29380
|
};
|
|
29381
|
+
function inMain(read) {
|
|
29382
|
+
return read.main ? { main: true } : {};
|
|
29383
|
+
}
|
|
27826
29384
|
function readCreate2(head) {
|
|
27827
29385
|
if (head.is(1, "policy")) return { kind: "create-policy" };
|
|
27828
29386
|
if (head.is(1, "temp") || head.is(1, "temporary")) return OTHER;
|
|
@@ -27846,9 +29404,24 @@ function readDrop2(head) {
|
|
|
27846
29404
|
at += 2;
|
|
27847
29405
|
}
|
|
27848
29406
|
const table = head.table(at);
|
|
27849
|
-
return table.ok ? { kind: "drop-table", table: table.name } : table.head;
|
|
29407
|
+
return table.ok ? { kind: "drop-table", table: table.name, ...inMain(table) } : table.head;
|
|
27850
29408
|
}
|
|
27851
29409
|
var RLS_ACTIONS = ["enable", "disable", "force", "no"];
|
|
29410
|
+
function readRenameColumn(head, table, at) {
|
|
29411
|
+
let from = at;
|
|
29412
|
+
if (head.is(from, "column") && head.nameAt(from + 1) !== void 0 && head.is(from + 2, "to"))
|
|
29413
|
+
from += 1;
|
|
29414
|
+
const column2 = head.nameAt(from);
|
|
29415
|
+
if (column2 === void 0 || !head.is(from + 1, "to")) return NO_COLUMN;
|
|
29416
|
+
const to = head.nameAt(from + 2);
|
|
29417
|
+
return to === void 0 ? NO_COLUMN : {
|
|
29418
|
+
kind: "rename-column",
|
|
29419
|
+
table: table.name,
|
|
29420
|
+
column: column2,
|
|
29421
|
+
to,
|
|
29422
|
+
...inMain(table)
|
|
29423
|
+
};
|
|
29424
|
+
}
|
|
27852
29425
|
function readAlter(head) {
|
|
27853
29426
|
if (head.is(1, "policy")) return { kind: "alter-policy" };
|
|
27854
29427
|
if (!head.is(1, "table")) return OTHER;
|
|
@@ -27858,9 +29431,18 @@ function readAlter(head) {
|
|
|
27858
29431
|
if (head.is(action, "rename") && head.is(action + 1, "to")) {
|
|
27859
29432
|
if (!sqlite.ok) return sqlite.head;
|
|
27860
29433
|
const to = head.table(action + 2);
|
|
27861
|
-
return to.ok ? {
|
|
29434
|
+
return to.ok ? {
|
|
29435
|
+
kind: "rename-table",
|
|
29436
|
+
table: sqlite.name,
|
|
29437
|
+
to: to.name,
|
|
29438
|
+
...inMain(sqlite)
|
|
29439
|
+
} : to.head;
|
|
29440
|
+
}
|
|
29441
|
+
if (head.is(action, "rename")) {
|
|
29442
|
+
if (!sqlite.ok) return sqlite.head;
|
|
29443
|
+
return readRenameColumn(head, sqlite, action + 1);
|
|
27862
29444
|
}
|
|
27863
|
-
if (head.is(action, "
|
|
29445
|
+
if (head.is(action, "add") || head.is(action, "drop"))
|
|
27864
29446
|
return sqlite.ok ? { kind: "alter-table", table: sqlite.name } : sqlite.head;
|
|
27865
29447
|
}
|
|
27866
29448
|
let at = 2;
|
|
@@ -27885,6 +29467,130 @@ function readSqlHead(text2) {
|
|
|
27885
29467
|
return OTHER;
|
|
27886
29468
|
}
|
|
27887
29469
|
|
|
29470
|
+
// ../../packages/data-core/src/system-sql.ts
|
|
29471
|
+
async function convertLegacyPolicies(db, found) {
|
|
29472
|
+
const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
|
|
29473
|
+
if (rows.length === 0) return [];
|
|
29474
|
+
const read = rows.map((row) => ({
|
|
29475
|
+
policy: policyFromRow(row),
|
|
29476
|
+
asRead: legacyRowAsRead(row)
|
|
29477
|
+
}));
|
|
29478
|
+
const inForce = new Set(
|
|
29479
|
+
legacyInForce(
|
|
29480
|
+
read.flatMap(({ policy }) => policy ? [policy] : []),
|
|
29481
|
+
await readTableNames(db)
|
|
29482
|
+
)
|
|
29483
|
+
);
|
|
29484
|
+
const statements = [];
|
|
29485
|
+
for (const { policy, asRead } of read) {
|
|
29486
|
+
if (!policy || !asRead) continue;
|
|
29487
|
+
if (inForce.has(policy)) {
|
|
29488
|
+
for (const named of namedFromLegacy(policy))
|
|
29489
|
+
statements.push({
|
|
29490
|
+
sql: POLICY_SQL.insertConverted,
|
|
29491
|
+
params: [...policyValues(named), ...asRead]
|
|
29492
|
+
});
|
|
29493
|
+
found.unreadable.push(...unreadableConversions(policy));
|
|
29494
|
+
}
|
|
29495
|
+
statements.push({ sql: POLICY_SQL.markConverted, params: asRead });
|
|
29496
|
+
}
|
|
29497
|
+
return statements;
|
|
29498
|
+
}
|
|
29499
|
+
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')))";
|
|
29500
|
+
var STORAGE_SUBQUERIES_VERSION = 7;
|
|
29501
|
+
var STORAGE_SUBQUERIES_NAME = "storage_subquery_policies";
|
|
29502
|
+
var STORAGE_SUBQUERIES_SQL = `SELECT version FROM _naro_migrations WHERE version = ${String(STORAGE_SUBQUERIES_VERSION)} AND name = '${STORAGE_SUBQUERIES_NAME}'`;
|
|
29503
|
+
var SYSTEM_MIGRATIONS = [
|
|
29504
|
+
{
|
|
29505
|
+
version: 1,
|
|
29506
|
+
name: "system_tables",
|
|
29507
|
+
statements: [
|
|
29508
|
+
MIGRATIONS_TABLE,
|
|
29509
|
+
"CREATE TABLE IF NOT EXISTS _naro_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')))",
|
|
29510
|
+
// Spec §11.2, as written there.
|
|
29511
|
+
"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')))"
|
|
29512
|
+
]
|
|
29513
|
+
},
|
|
29514
|
+
{
|
|
29515
|
+
// Spec P2-2 §3.1: an action whose rule column says 'condition' reads its
|
|
29516
|
+
// condition from these. A worker from before them reads 'condition' as
|
|
29517
|
+
// an unknown rule — none.
|
|
29518
|
+
version: 2,
|
|
29519
|
+
name: "policy_conditions",
|
|
29520
|
+
statements: [],
|
|
29521
|
+
addColumns: ["select", "insert", "update", "delete"].map((action) => ({
|
|
29522
|
+
table: "_naro_policies",
|
|
29523
|
+
column: `${action}_condition`,
|
|
29524
|
+
definition: "TEXT"
|
|
29525
|
+
}))
|
|
29526
|
+
},
|
|
29527
|
+
{
|
|
29528
|
+
// Spec P2-6 §11: the Naro worker's sign-in handoff. A provider sign-in's
|
|
29529
|
+
// flow (state → the app's redirect and the browser's PKCE challenge), the
|
|
29530
|
+
// one-time codes a redirect hands the app, the sends the per-address
|
|
29531
|
+
// email limit counts, and (adversarial review I3) the unconfirmed
|
|
29532
|
+
// accounts someone else also tried to sign up as. Times are epoch
|
|
29533
|
+
// milliseconds.
|
|
29534
|
+
version: 3,
|
|
29535
|
+
name: "auth_handoff",
|
|
29536
|
+
statements: [
|
|
29537
|
+
"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)",
|
|
29538
|
+
"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)",
|
|
29539
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_email_log (address_hash TEXT NOT NULL, sent_at INTEGER NOT NULL)",
|
|
29540
|
+
"CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
|
|
29541
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
|
|
29542
|
+
]
|
|
29543
|
+
},
|
|
29544
|
+
{
|
|
29545
|
+
// P2-7 §9: named policies, many per table. The old rows are converted
|
|
29546
|
+
// in the same batch and their rules set to a word old workers read as
|
|
29547
|
+
// `none`.
|
|
29548
|
+
version: 4,
|
|
29549
|
+
name: "named_policies",
|
|
29550
|
+
statements: [NAMED_POLICY_TABLE_SQL],
|
|
29551
|
+
compute: convertLegacyPolicies
|
|
29552
|
+
},
|
|
29553
|
+
{
|
|
29554
|
+
// Storage (first-release design §6): the buckets and the objects' index
|
|
29555
|
+
// with their counter triggers. Every backend gets them, Storage on or
|
|
29556
|
+
// not — what turns it on is the worker's R2 binding. A worker from
|
|
29557
|
+
// before them never reads them.
|
|
29558
|
+
version: 5,
|
|
29559
|
+
name: "storage",
|
|
29560
|
+
statements: [...STORAGE_TABLES_SQL]
|
|
29561
|
+
},
|
|
29562
|
+
{
|
|
29563
|
+
// Storage 3-1b (known-issues #107, first-release design §14): the mark
|
|
29564
|
+
// a bucket's removal sets before it empties the bucket. The claims and
|
|
29565
|
+
// the worker's bucket reads take a marked bucket as not there. A worker
|
|
29566
|
+
// from before it never reads the column.
|
|
29567
|
+
version: 6,
|
|
29568
|
+
name: "storage_bucket_deleting",
|
|
29569
|
+
statements: [],
|
|
29570
|
+
addColumns: [STORAGE_BUCKET_DELETING_COLUMN]
|
|
29571
|
+
},
|
|
29572
|
+
{
|
|
29573
|
+
// Storage 3-1j (plan correction (p)): no schema — the mark that a worker
|
|
29574
|
+
// which reads storage.objects in a policy's subquery has migrated this
|
|
29575
|
+
// database. Only the Naro worker's own `internal/migrate` applies system
|
|
29576
|
+
// migrations, so the row is there once such a worker has run, and stays:
|
|
29577
|
+
// every later worker reads them too — as long as the worker bundle does
|
|
29578
|
+
// not go back. Deploying web from before 3-1j makes the next upgrade
|
|
29579
|
+
// put an older worker back, and a run that lost its lease may upload
|
|
29580
|
+
// one late; such a policy then answers 500 POLICY_INVALID (closed, no
|
|
29581
|
+
// rows) until it is dropped or the newer worker is uploaded again
|
|
29582
|
+
// (3-1j 최종 리뷰). Migration 5 alone cannot tell —
|
|
29583
|
+
// backends migrated by 3-1a to 3-1i have it, and their worker refuses
|
|
29584
|
+
// such a policy ("A policy cannot read storage.objects in a subquery
|
|
29585
|
+
// yet") and answers every request of its table 500 POLICY_INVALID until
|
|
29586
|
+
// the next upgrade. The control plane takes one only where this row is
|
|
29587
|
+
// (`STORAGE_SUBQUERIES_SQL`, `buildPolicyBatch`).
|
|
29588
|
+
version: STORAGE_SUBQUERIES_VERSION,
|
|
29589
|
+
name: STORAGE_SUBQUERIES_NAME,
|
|
29590
|
+
statements: []
|
|
29591
|
+
}
|
|
29592
|
+
];
|
|
29593
|
+
|
|
27888
29594
|
// ../../packages/data-core/src/policy-migration.ts
|
|
27889
29595
|
var GRANT_REFUSAL = "naro has no GRANT or REVOKE: every table is behind row-level security, so write a policy instead (CREATE POLICY \u2026).";
|
|
27890
29596
|
function disableRefusal(table) {
|
|
@@ -27902,6 +29608,7 @@ function tablesChanged(head) {
|
|
|
27902
29608
|
case "create-table":
|
|
27903
29609
|
case "drop-table":
|
|
27904
29610
|
case "alter-table":
|
|
29611
|
+
case "rename-column":
|
|
27905
29612
|
return [asciiLower(head.table)];
|
|
27906
29613
|
case "rename-table":
|
|
27907
29614
|
return [asciiLower(head.table), asciiLower(head.to)];
|
|
@@ -27929,18 +29636,28 @@ function tablesInScope(policy) {
|
|
|
27929
29636
|
}
|
|
27930
29637
|
return tables;
|
|
27931
29638
|
}
|
|
29639
|
+
function readsStorageObjects(policy) {
|
|
29640
|
+
return [policy.using, policy.check].some((text2) => {
|
|
29641
|
+
if (text2 === null) return false;
|
|
29642
|
+
const parsed = parseExpression(text2);
|
|
29643
|
+
return parsed.ok && subqueriesOf(parsed.expr).some(
|
|
29644
|
+
(subquery) => isStorageObjects(subquery.table)
|
|
29645
|
+
);
|
|
29646
|
+
});
|
|
29647
|
+
}
|
|
27932
29648
|
function planPolicyMigration(statements) {
|
|
27933
29649
|
const count2 = statements.length;
|
|
27934
29650
|
const planned = [];
|
|
27935
29651
|
let hasPolicyStatements = false;
|
|
27936
29652
|
let touchesTables = false;
|
|
29653
|
+
let renamesColumns = false;
|
|
27937
29654
|
const heads = statements.map((text2) => readSqlHead(text2));
|
|
27938
29655
|
const changes = heads.map(tablesChanged);
|
|
27939
29656
|
const changedAfter = (index, tables) => changes.slice(index + 1).some(
|
|
27940
29657
|
(changed) => changed === "any" || changed.some((table) => tables.has(table))
|
|
27941
29658
|
);
|
|
27942
29659
|
for (const [index, text2] of statements.entries()) {
|
|
27943
|
-
const
|
|
29660
|
+
const refuse2 = (message) => ({
|
|
27944
29661
|
ok: false,
|
|
27945
29662
|
index,
|
|
27946
29663
|
message: `Statement ${String(index + 1)} of ${String(count2)}: ${message}`
|
|
@@ -27949,15 +29666,19 @@ function planPolicyMigration(statements) {
|
|
|
27949
29666
|
switch (head.kind) {
|
|
27950
29667
|
case "grant":
|
|
27951
29668
|
case "revoke":
|
|
27952
|
-
return
|
|
29669
|
+
return refuse2(GRANT_REFUSAL);
|
|
27953
29670
|
case "unreadable":
|
|
27954
|
-
return
|
|
29671
|
+
return refuse2(`${head.reason}.`);
|
|
27955
29672
|
case "create-table":
|
|
27956
29673
|
case "drop-table":
|
|
27957
29674
|
case "rename-table":
|
|
27958
29675
|
touchesTables = true;
|
|
27959
29676
|
planned.push({ kind: "verbatim", text: text2, head });
|
|
27960
29677
|
continue;
|
|
29678
|
+
case "rename-column":
|
|
29679
|
+
renamesColumns = true;
|
|
29680
|
+
planned.push({ kind: "verbatim", text: text2, head });
|
|
29681
|
+
continue;
|
|
27961
29682
|
case "alter-table":
|
|
27962
29683
|
case "other":
|
|
27963
29684
|
planned.push({ kind: "verbatim", text: text2, head });
|
|
@@ -27967,17 +29688,17 @@ function planPolicyMigration(statements) {
|
|
|
27967
29688
|
}
|
|
27968
29689
|
const parsed = parseMigrationPolicyStatement(text2);
|
|
27969
29690
|
if (!parsed.ok)
|
|
27970
|
-
return
|
|
29691
|
+
return refuse2(
|
|
27971
29692
|
`${parsed.reason} at character ${String(parsed.position)}.`
|
|
27972
29693
|
);
|
|
27973
29694
|
const statement2 = parsed.statement;
|
|
27974
29695
|
const table = statement2.kind === "create" ? statement2.policy.table : statement2.table;
|
|
27975
29696
|
if (!isStorageObjects(table) && isSystemTable(table))
|
|
27976
|
-
return
|
|
29697
|
+
return refuse2(`${relationMissing(table)}.`);
|
|
27977
29698
|
switch (statement2.kind) {
|
|
27978
29699
|
case "create":
|
|
27979
29700
|
if (readsBareSubqueryColumn(statement2.policy) && changedAfter(index, tablesInScope(statement2.policy)))
|
|
27980
|
-
return
|
|
29701
|
+
return refuse2(LATER_TABLE_CHANGE);
|
|
27981
29702
|
hasPolicyStatements = true;
|
|
27982
29703
|
planned.push({ kind: "create-policy", policy: statement2.policy });
|
|
27983
29704
|
break;
|
|
@@ -28005,14 +29726,19 @@ function planPolicyMigration(statements) {
|
|
|
28005
29726
|
break;
|
|
28006
29727
|
case "rls":
|
|
28007
29728
|
if (statement2.action === "disable")
|
|
28008
|
-
return
|
|
29729
|
+
return refuse2(disableRefusal(table));
|
|
28009
29730
|
planned.push({ kind: "noop" });
|
|
28010
29731
|
break;
|
|
28011
29732
|
}
|
|
28012
29733
|
}
|
|
28013
29734
|
return {
|
|
28014
29735
|
ok: true,
|
|
28015
|
-
plan: {
|
|
29736
|
+
plan: {
|
|
29737
|
+
statements: planned,
|
|
29738
|
+
hasPolicyStatements,
|
|
29739
|
+
touchesTables,
|
|
29740
|
+
renamesColumns
|
|
29741
|
+
}
|
|
28016
29742
|
};
|
|
28017
29743
|
}
|
|
28018
29744
|
function policyMigrationSql(policy) {
|
|
@@ -28024,8 +29750,10 @@ COMMENT ON POLICY ${quotePolicyName(policy.name)} ON ${policyTableSql(policy.tab
|
|
|
28024
29750
|
function unreadablePolicyComment(policy) {
|
|
28025
29751
|
return `-- Policy ${commentText(quotePolicyName(policy.name))} on ${commentText(policyTableSql(policy.table))} cannot be read (${commentText(policy.invalid)}); the requests it applies to are refused until it is dropped and created again.`;
|
|
28026
29752
|
}
|
|
29753
|
+
var STORED_POLICIES_SQL = `SELECT table_name, name, kind, command, roles, using_expr, check_expr, owner_column, updated_at FROM ${NAMED_POLICY_TABLE} ORDER BY table_name, name`;
|
|
28027
29754
|
var SYSTEM_PREDICATE = systemTablePredicate("name");
|
|
28028
29755
|
var STORAGE_EXISTS = `EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${sqlStringLiteral(STORAGE_OBJECTS_TABLE)})`;
|
|
29756
|
+
var STORAGE_SUBQUERIES_READY = `EXISTS (${STORAGE_SUBQUERIES_SQL})`;
|
|
28029
29757
|
|
|
28030
29758
|
// ../../packages/data-core/src/realtime-limits.ts
|
|
28031
29759
|
var HOBBY_REALTIME_LIMITS = Object.freeze({
|
|
@@ -28114,107 +29842,6 @@ var MULTIPART_COMPLETE_LOCK_MS = 15 * 60 * 1e3;
|
|
|
28114
29842
|
var UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
28115
29843
|
var UPLOAD_ID = new RegExp(`^(${UUID})\\.(${UUID})$`);
|
|
28116
29844
|
|
|
28117
|
-
// ../../packages/data-core/src/system-sql.ts
|
|
28118
|
-
async function convertLegacyPolicies(db, found) {
|
|
28119
|
-
const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
|
|
28120
|
-
if (rows.length === 0) return [];
|
|
28121
|
-
const read = rows.map((row) => ({
|
|
28122
|
-
policy: policyFromRow(row),
|
|
28123
|
-
asRead: legacyRowAsRead(row)
|
|
28124
|
-
}));
|
|
28125
|
-
const inForce = new Set(
|
|
28126
|
-
legacyInForce(
|
|
28127
|
-
read.flatMap(({ policy }) => policy ? [policy] : []),
|
|
28128
|
-
await readTableNames(db)
|
|
28129
|
-
)
|
|
28130
|
-
);
|
|
28131
|
-
const statements = [];
|
|
28132
|
-
for (const { policy, asRead } of read) {
|
|
28133
|
-
if (!policy || !asRead) continue;
|
|
28134
|
-
if (inForce.has(policy)) {
|
|
28135
|
-
for (const named of namedFromLegacy(policy))
|
|
28136
|
-
statements.push({
|
|
28137
|
-
sql: POLICY_SQL.insertConverted,
|
|
28138
|
-
params: [...policyValues(named), ...asRead]
|
|
28139
|
-
});
|
|
28140
|
-
found.unreadable.push(...unreadableConversions(policy));
|
|
28141
|
-
}
|
|
28142
|
-
statements.push({ sql: POLICY_SQL.markConverted, params: asRead });
|
|
28143
|
-
}
|
|
28144
|
-
return statements;
|
|
28145
|
-
}
|
|
28146
|
-
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')))";
|
|
28147
|
-
var SYSTEM_MIGRATIONS = [
|
|
28148
|
-
{
|
|
28149
|
-
version: 1,
|
|
28150
|
-
name: "system_tables",
|
|
28151
|
-
statements: [
|
|
28152
|
-
MIGRATIONS_TABLE,
|
|
28153
|
-
"CREATE TABLE IF NOT EXISTS _naro_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')))",
|
|
28154
|
-
// Spec §11.2, as written there.
|
|
28155
|
-
"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')))"
|
|
28156
|
-
]
|
|
28157
|
-
},
|
|
28158
|
-
{
|
|
28159
|
-
// Spec P2-2 §3.1: an action whose rule column says 'condition' reads its
|
|
28160
|
-
// condition from these. A worker from before them reads 'condition' as
|
|
28161
|
-
// an unknown rule — none.
|
|
28162
|
-
version: 2,
|
|
28163
|
-
name: "policy_conditions",
|
|
28164
|
-
statements: [],
|
|
28165
|
-
addColumns: ["select", "insert", "update", "delete"].map((action) => ({
|
|
28166
|
-
table: "_naro_policies",
|
|
28167
|
-
column: `${action}_condition`,
|
|
28168
|
-
definition: "TEXT"
|
|
28169
|
-
}))
|
|
28170
|
-
},
|
|
28171
|
-
{
|
|
28172
|
-
// Spec P2-6 §11: the Naro worker's sign-in handoff. A provider sign-in's
|
|
28173
|
-
// flow (state → the app's redirect and the browser's PKCE challenge), the
|
|
28174
|
-
// one-time codes a redirect hands the app, the sends the per-address
|
|
28175
|
-
// email limit counts, and (adversarial review I3) the unconfirmed
|
|
28176
|
-
// accounts someone else also tried to sign up as. Times are epoch
|
|
28177
|
-
// milliseconds.
|
|
28178
|
-
version: 3,
|
|
28179
|
-
name: "auth_handoff",
|
|
28180
|
-
statements: [
|
|
28181
|
-
"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)",
|
|
28182
|
-
"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)",
|
|
28183
|
-
"CREATE TABLE IF NOT EXISTS _naro_auth_email_log (address_hash TEXT NOT NULL, sent_at INTEGER NOT NULL)",
|
|
28184
|
-
"CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
|
|
28185
|
-
"CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
|
|
28186
|
-
]
|
|
28187
|
-
},
|
|
28188
|
-
{
|
|
28189
|
-
// P2-7 §9: named policies, many per table. The old rows are converted
|
|
28190
|
-
// in the same batch and their rules set to a word old workers read as
|
|
28191
|
-
// `none`.
|
|
28192
|
-
version: 4,
|
|
28193
|
-
name: "named_policies",
|
|
28194
|
-
statements: [NAMED_POLICY_TABLE_SQL],
|
|
28195
|
-
compute: convertLegacyPolicies
|
|
28196
|
-
},
|
|
28197
|
-
{
|
|
28198
|
-
// Storage (first-release design §6): the buckets and the objects' index
|
|
28199
|
-
// with their counter triggers. Every backend gets them, Storage on or
|
|
28200
|
-
// not — what turns it on is the worker's R2 binding. A worker from
|
|
28201
|
-
// before them never reads them.
|
|
28202
|
-
version: 5,
|
|
28203
|
-
name: "storage",
|
|
28204
|
-
statements: [...STORAGE_TABLES_SQL]
|
|
28205
|
-
},
|
|
28206
|
-
{
|
|
28207
|
-
// Storage 3-1b (known-issues #107, first-release design §14): the mark
|
|
28208
|
-
// a bucket's removal sets before it empties the bucket. The claims and
|
|
28209
|
-
// the worker's bucket reads take a marked bucket as not there. A worker
|
|
28210
|
-
// from before it never reads the column.
|
|
28211
|
-
version: 6,
|
|
28212
|
-
name: "storage_bucket_deleting",
|
|
28213
|
-
statements: [],
|
|
28214
|
-
addColumns: [STORAGE_BUCKET_DELETING_COLUMN]
|
|
28215
|
-
}
|
|
28216
|
-
];
|
|
28217
|
-
|
|
28218
29845
|
// ../../packages/data-core/src/write-quota.ts
|
|
28219
29846
|
var WRITE_QUOTA_KEY = "write_quota";
|
|
28220
29847
|
var WRITE_QUOTA_SQL = {
|
|
@@ -28460,12 +30087,12 @@ import {
|
|
|
28460
30087
|
rmSync as rmSync2,
|
|
28461
30088
|
writeFileSync as writeFileSync2
|
|
28462
30089
|
} from "fs";
|
|
28463
|
-
import { join as
|
|
30090
|
+
import { join as join23 } from "path";
|
|
28464
30091
|
function linkDir(dir) {
|
|
28465
|
-
return
|
|
30092
|
+
return join23(dir, ".naro");
|
|
28466
30093
|
}
|
|
28467
30094
|
function linkFile(dir) {
|
|
28468
|
-
return
|
|
30095
|
+
return join23(linkDir(dir), "project.json");
|
|
28469
30096
|
}
|
|
28470
30097
|
function readProjectLink(dir) {
|
|
28471
30098
|
try {
|
|
@@ -28487,7 +30114,7 @@ function writeProjectLink(dir, link) {
|
|
|
28487
30114
|
writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
|
|
28488
30115
|
`);
|
|
28489
30116
|
writeFileSync2(
|
|
28490
|
-
|
|
30117
|
+
join23(linkDir(dir), "README.txt"),
|
|
28491
30118
|
"This directory links the project to Naro and should not be committed.\n"
|
|
28492
30119
|
);
|
|
28493
30120
|
}
|
|
@@ -28497,9 +30124,9 @@ function removeProjectLink(dir) {
|
|
|
28497
30124
|
return true;
|
|
28498
30125
|
}
|
|
28499
30126
|
function ensureGitignoreHasNaro(dir) {
|
|
28500
|
-
const gitignorePath =
|
|
30127
|
+
const gitignorePath = join23(dir, ".gitignore");
|
|
28501
30128
|
const hasGitignore = existsSync4(gitignorePath);
|
|
28502
|
-
if (!hasGitignore && !existsSync4(
|
|
30129
|
+
if (!hasGitignore && !existsSync4(join23(dir, ".git"))) return false;
|
|
28503
30130
|
const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
|
|
28504
30131
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
|
|
28505
30132
|
if (covered) return false;
|
|
@@ -28952,15 +30579,15 @@ function policiesFromFiles(files) {
|
|
|
28952
30579
|
policies = policies.filter((policy) => !sameTable(policy.table, table));
|
|
28953
30580
|
};
|
|
28954
30581
|
for (const [file2, sql] of files.entries()) {
|
|
28955
|
-
const
|
|
28956
|
-
if (!
|
|
30582
|
+
const split2 = splitStatements(sql);
|
|
30583
|
+
if (!split2.ok)
|
|
28957
30584
|
return {
|
|
28958
30585
|
policies,
|
|
28959
30586
|
tables,
|
|
28960
|
-
refused: { file: file2, reason: `sql could not be parsed: ${
|
|
30587
|
+
refused: { file: file2, reason: `sql could not be parsed: ${split2.reason}.` }
|
|
28961
30588
|
};
|
|
28962
30589
|
const planned = planPolicyMigration(
|
|
28963
|
-
|
|
30590
|
+
split2.statements.map((statement2) => statement2.text)
|
|
28964
30591
|
);
|
|
28965
30592
|
if (!planned.ok)
|
|
28966
30593
|
return {
|
|
@@ -28968,9 +30595,20 @@ function policiesFromFiles(files) {
|
|
|
28968
30595
|
tables,
|
|
28969
30596
|
refused: { file: file2, reason: planned.message }
|
|
28970
30597
|
};
|
|
28971
|
-
for (const statement2 of planned.plan.statements) {
|
|
30598
|
+
for (const [index, statement2] of planned.plan.statements.entries()) {
|
|
28972
30599
|
if (statement2.kind === "create-policy") {
|
|
28973
30600
|
const { table, name } = statement2.policy;
|
|
30601
|
+
if (policies.some(
|
|
30602
|
+
(policy) => sameTable(policy.table, table) && policy.name === name
|
|
30603
|
+
))
|
|
30604
|
+
return {
|
|
30605
|
+
policies,
|
|
30606
|
+
tables,
|
|
30607
|
+
refused: {
|
|
30608
|
+
file: file2,
|
|
30609
|
+
reason: `statement ${String(index + 1)} of ${String(planned.plan.statements.length)} failed and the migration was rolled back: policy "${name}" for table "${table}" already exists`
|
|
30610
|
+
}
|
|
30611
|
+
};
|
|
28974
30612
|
policies.push({ table, name });
|
|
28975
30613
|
} else if (statement2.kind === "drop-policy") {
|
|
28976
30614
|
policies = policies.filter(
|
|
@@ -28988,6 +30626,10 @@ function policiesFromFiles(files) {
|
|
|
28988
30626
|
if (!tables.has(asciiLower(head.to))) forget(head.to);
|
|
28989
30627
|
tables.delete(asciiLower(head.table));
|
|
28990
30628
|
tables.add(asciiLower(head.to));
|
|
30629
|
+
if (!sameTable(head.table, STORAGE_OBJECTS) && !sameTable(head.table, REALTIME_MESSAGES))
|
|
30630
|
+
policies = policies.map(
|
|
30631
|
+
(policy) => sameTable(policy.table, head.table) ? { ...policy, table: head.to } : policy
|
|
30632
|
+
);
|
|
28991
30633
|
}
|
|
28992
30634
|
}
|
|
28993
30635
|
}
|
|
@@ -29013,6 +30655,31 @@ function policiesLostByReset(input) {
|
|
|
29013
30655
|
withTables: gone.filter((policy) => !remade.has(asciiLower(policy.table)))
|
|
29014
30656
|
};
|
|
29015
30657
|
}
|
|
30658
|
+
function storageRefusalOfFiles(files, backend) {
|
|
30659
|
+
for (const [file2, sql] of files.entries()) {
|
|
30660
|
+
const split2 = splitStatements(sql);
|
|
30661
|
+
if (!split2.ok) return void 0;
|
|
30662
|
+
const planned = planPolicyMigration(
|
|
30663
|
+
split2.statements.map((statement2) => statement2.text)
|
|
30664
|
+
);
|
|
30665
|
+
if (!planned.ok) return void 0;
|
|
30666
|
+
const count2 = planned.plan.statements.length;
|
|
30667
|
+
for (const [index, statement2] of planned.plan.statements.entries()) {
|
|
30668
|
+
let needs;
|
|
30669
|
+
if (statement2.kind === "create-policy")
|
|
30670
|
+
needs = readsStorageObjects(statement2.policy) ? "subqueries" : sameTable(statement2.policy.table, STORAGE_OBJECTS) ? "storage" : void 0;
|
|
30671
|
+
else if (statement2.kind === "drop-policy" && !statement2.ifExists || statement2.kind === "owner")
|
|
30672
|
+
needs = sameTable(statement2.table, STORAGE_OBJECTS) ? "storage" : void 0;
|
|
30673
|
+
const missing = needs === "subqueries" ? !backend.storage || !backend.storageSubqueries : needs === "storage" && !backend.storage;
|
|
30674
|
+
if (needs !== void 0 && missing)
|
|
30675
|
+
return { file: file2, statement: index + 1, count: count2, needs };
|
|
30676
|
+
}
|
|
30677
|
+
}
|
|
30678
|
+
return void 0;
|
|
30679
|
+
}
|
|
30680
|
+
function resetStorageOutdatedMessage(file2, at) {
|
|
30681
|
+
return `${file2} needs storage.objects (statement ${String(at.statement)} of ${String(at.count)}), which is not available on this backend yet, so the reset would stop there with the schema already dropped. Nothing was changed. Run \`naro backend enable\` to update it, then try again.`;
|
|
30682
|
+
}
|
|
29016
30683
|
function resetPoliciesLostMessage(lost) {
|
|
29017
30684
|
return `These ${String(lost.length)} policies are not in your migration files and will be gone after the reset: ${lost.map((policy) => `${policy.table}.${policy.name}`).join(", ")}. To keep them, add this to a migration first:`;
|
|
29018
30685
|
}
|
|
@@ -29091,28 +30758,33 @@ function usageStatus(report, currentMonth, requests) {
|
|
|
29091
30758
|
|
|
29092
30759
|
// src/env-consent.ts
|
|
29093
30760
|
import { existsSync as existsSync6 } from "fs";
|
|
29094
|
-
import { join as
|
|
30761
|
+
import { join as join24 } from "path";
|
|
29095
30762
|
var DOTENV_FILES = [".env.local", ".env.production", ".env"];
|
|
29096
|
-
var
|
|
30763
|
+
var PUBLIC_PREFIX = "NEXT_PUBLIC_";
|
|
30764
|
+
function maskSecretValue(value) {
|
|
30765
|
+
return value.length <= 8 ? "****" : `${value.slice(0, 4)}****`;
|
|
30766
|
+
}
|
|
29097
30767
|
function maskEnvValue(key, value) {
|
|
29098
|
-
if (
|
|
30768
|
+
if (key.startsWith(PUBLIC_PREFIX))
|
|
29099
30769
|
return value.length > 60 ? `${value.slice(0, 60)}\u2026` : value;
|
|
29100
|
-
return value
|
|
30770
|
+
return maskSecretValue(value);
|
|
29101
30771
|
}
|
|
29102
30772
|
function findDotenvFile(dir) {
|
|
29103
|
-
for (const name of DOTENV_FILES) if (existsSync6(
|
|
30773
|
+
for (const name of DOTENV_FILES) if (existsSync6(join24(dir, name))) return name;
|
|
29104
30774
|
return null;
|
|
29105
30775
|
}
|
|
29106
|
-
function envAddGuidance(keys) {
|
|
30776
|
+
function envAddGuidance(keys, environment) {
|
|
30777
|
+
const suffix = environment === void 0 ? "" : ` ${environment}`;
|
|
29107
30778
|
return [
|
|
29108
30779
|
" Set them yourself:",
|
|
29109
|
-
...keys.map((key) => ` npx naro-cli env add ${key}`),
|
|
30780
|
+
...keys.map((key) => ` npx naro-cli env add ${key}${suffix}`),
|
|
29110
30781
|
" (values come from a hidden prompt or stdin, never from argv)"
|
|
29111
30782
|
];
|
|
29112
30783
|
}
|
|
29113
30784
|
async function promptForMissingEnv(ctx, keys) {
|
|
29114
30785
|
const guide = (remaining2) => {
|
|
29115
|
-
for (const line of envAddGuidance(remaining2
|
|
30786
|
+
for (const line of envAddGuidance(remaining2, ctx.environment))
|
|
30787
|
+
ctx.output.log(line);
|
|
29116
30788
|
};
|
|
29117
30789
|
const file2 = findDotenvFile(ctx.cwd);
|
|
29118
30790
|
if (!file2) {
|
|
@@ -29150,6 +30822,20 @@ async function promptForMissingEnv(ctx, keys) {
|
|
|
29150
30822
|
return collected;
|
|
29151
30823
|
}
|
|
29152
30824
|
|
|
30825
|
+
// src/env-surface.ts
|
|
30826
|
+
function pullEnv(rows, surface) {
|
|
30827
|
+
const chosen = /* @__PURE__ */ new Map();
|
|
30828
|
+
for (const row of rows) {
|
|
30829
|
+
if (row.target !== "all" && row.target !== surface) continue;
|
|
30830
|
+
const held = chosen.get(row.key);
|
|
30831
|
+
if (!held || held.target === "all" && row.target !== "all")
|
|
30832
|
+
chosen.set(row.key, row);
|
|
30833
|
+
}
|
|
30834
|
+
return Object.fromEntries(
|
|
30835
|
+
[...chosen.values()].map((row) => [row.key, row.value ?? ""])
|
|
30836
|
+
);
|
|
30837
|
+
}
|
|
30838
|
+
|
|
29153
30839
|
// src/git-info.ts
|
|
29154
30840
|
import { execFile as execFile2 } from "child_process";
|
|
29155
30841
|
var SUPPORTED_REMOTE = [
|
|
@@ -29315,9 +31001,9 @@ import {
|
|
|
29315
31001
|
cp as cp2,
|
|
29316
31002
|
lstat,
|
|
29317
31003
|
mkdir as mkdir3,
|
|
29318
|
-
readFile as
|
|
31004
|
+
readFile as readFile14,
|
|
29319
31005
|
readlink,
|
|
29320
|
-
realpath,
|
|
31006
|
+
realpath as realpath3,
|
|
29321
31007
|
rm as rm4,
|
|
29322
31008
|
stat as stat3,
|
|
29323
31009
|
symlink,
|
|
@@ -29328,10 +31014,10 @@ import {
|
|
|
29328
31014
|
basename as basename3,
|
|
29329
31015
|
dirname as dirname5,
|
|
29330
31016
|
isAbsolute as isAbsolute4,
|
|
29331
|
-
join as
|
|
31017
|
+
join as join25,
|
|
29332
31018
|
relative as relative6,
|
|
29333
31019
|
resolve as resolve10,
|
|
29334
|
-
sep as
|
|
31020
|
+
sep as sep5
|
|
29335
31021
|
} from "path";
|
|
29336
31022
|
var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
|
|
29337
31023
|
var ENV_FILE = /^\.env(\.(?!(?:example|sample|template)$)[^\\/]+)?$/i;
|
|
@@ -29369,7 +31055,7 @@ async function committedEnvFiles(sourceDir) {
|
|
|
29369
31055
|
}
|
|
29370
31056
|
async function staysInside(link, text2, root) {
|
|
29371
31057
|
try {
|
|
29372
|
-
return inside(await
|
|
31058
|
+
return inside(await realpath3(link), root);
|
|
29373
31059
|
} catch (error51) {
|
|
29374
31060
|
return error51.code === "ENOENT" && !text2.split(/[\\/]/).includes("..");
|
|
29375
31061
|
}
|
|
@@ -29392,12 +31078,12 @@ async function keptOnly(items, keep, remove) {
|
|
|
29392
31078
|
async function readLocal(path) {
|
|
29393
31079
|
const found = await stat3(path).catch(() => null);
|
|
29394
31080
|
if (!found?.isFile()) return null;
|
|
29395
|
-
return
|
|
31081
|
+
return readFile14(path).catch(() => null);
|
|
29396
31082
|
}
|
|
29397
31083
|
async function realAncestor(dir) {
|
|
29398
31084
|
for (let current = dir; ; current = dirname5(current)) {
|
|
29399
31085
|
try {
|
|
29400
|
-
return await
|
|
31086
|
+
return await realpath3(current);
|
|
29401
31087
|
} catch {
|
|
29402
31088
|
if (dirname5(current) === current)
|
|
29403
31089
|
throw new Error(`No ancestor of ${dir}`);
|
|
@@ -29408,7 +31094,7 @@ function sameText(a, b) {
|
|
|
29408
31094
|
return a.equals(b) || a.toString("utf8").replace(/\r\n/g, "\n") === b.toString("utf8").replace(/\r\n/g, "\n");
|
|
29409
31095
|
}
|
|
29410
31096
|
async function copyWorkingTree(source, checkout) {
|
|
29411
|
-
const sourceDir = await
|
|
31097
|
+
const sourceDir = await realpath3(source);
|
|
29412
31098
|
const inWorkingTree = [];
|
|
29413
31099
|
const absoluteLinks = [];
|
|
29414
31100
|
const linksOutside = [];
|
|
@@ -29427,12 +31113,12 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29427
31113
|
if (entry.isFIFO() || entry.isSocket() || entry.isCharacterDevice() || entry.isBlockDevice())
|
|
29428
31114
|
return false;
|
|
29429
31115
|
if (ENV_FILE.test(basename3(src)) && !entry.isDirectory()) {
|
|
29430
|
-
inWorkingTree.push(relative6(sourceDir, src).split(
|
|
31116
|
+
inWorkingTree.push(relative6(sourceDir, src).split(sep5).join("/"));
|
|
29431
31117
|
return false;
|
|
29432
31118
|
}
|
|
29433
31119
|
if (entry.isSymbolicLink()) {
|
|
29434
31120
|
const target2 = await readlink(src);
|
|
29435
|
-
const path = relative6(sourceDir, src).split(
|
|
31121
|
+
const path = relative6(sourceDir, src).split(sep5).join("/");
|
|
29436
31122
|
if (isAbsolute4(target2)) {
|
|
29437
31123
|
absoluteLinks.push(path);
|
|
29438
31124
|
return false;
|
|
@@ -29450,17 +31136,17 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29450
31136
|
return true;
|
|
29451
31137
|
}
|
|
29452
31138
|
});
|
|
29453
|
-
const root = await
|
|
31139
|
+
const root = await realpath3(checkout);
|
|
29454
31140
|
const strays = await keptOnly(
|
|
29455
31141
|
keptLinks,
|
|
29456
31142
|
async ({ path, text: text2 }) => {
|
|
29457
31143
|
try {
|
|
29458
|
-
return inside(await
|
|
31144
|
+
return inside(await realpath3(join25(checkout, path)), root);
|
|
29459
31145
|
} catch (error51) {
|
|
29460
31146
|
return error51.code === "ENOENT" && climbsOnlyFirst(text2);
|
|
29461
31147
|
}
|
|
29462
31148
|
},
|
|
29463
|
-
({ path }) => unlink(
|
|
31149
|
+
({ path }) => unlink(join25(checkout, path))
|
|
29464
31150
|
);
|
|
29465
31151
|
linksElsewhere.push(...strays.map(({ path }) => path));
|
|
29466
31152
|
const committed = await committedEnvFiles(sourceDir);
|
|
@@ -29474,7 +31160,7 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29474
31160
|
outsideProject.push(path);
|
|
29475
31161
|
continue;
|
|
29476
31162
|
}
|
|
29477
|
-
const target2 =
|
|
31163
|
+
const target2 = join25(checkout, path);
|
|
29478
31164
|
if (!inside(await realAncestor(dirname5(target2)), root)) {
|
|
29479
31165
|
outsideProject.push(path);
|
|
29480
31166
|
continue;
|
|
@@ -29499,16 +31185,16 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29499
31185
|
continue;
|
|
29500
31186
|
}
|
|
29501
31187
|
await writeFile7(target2, content, { flag: "wx" });
|
|
29502
|
-
const local = await readLocal(
|
|
31188
|
+
const local = await readLocal(join25(sourceDir, path));
|
|
29503
31189
|
if (!local || !sameText(local, content)) committedVersion.push(path);
|
|
29504
31190
|
}
|
|
29505
31191
|
const envStrays = await keptOnly(
|
|
29506
31192
|
envLinks,
|
|
29507
31193
|
async (path) => {
|
|
29508
|
-
const real = await
|
|
31194
|
+
const real = await realpath3(join25(checkout, path)).catch(() => null);
|
|
29509
31195
|
return real !== null && inside(real, root);
|
|
29510
31196
|
},
|
|
29511
|
-
(path) => unlink(
|
|
31197
|
+
(path) => unlink(join25(checkout, path))
|
|
29512
31198
|
);
|
|
29513
31199
|
outsideProject.push(...envStrays);
|
|
29514
31200
|
const inCommit = new Set(
|
|
@@ -29625,7 +31311,7 @@ function slugify2(value) {
|
|
|
29625
31311
|
async function projectName(sourceDir) {
|
|
29626
31312
|
try {
|
|
29627
31313
|
const pkg = JSON.parse(
|
|
29628
|
-
await
|
|
31314
|
+
await readFile15(join26(sourceDir, "package.json"), "utf8")
|
|
29629
31315
|
);
|
|
29630
31316
|
if (pkg.name) return pkg.name;
|
|
29631
31317
|
} catch {
|
|
@@ -29635,7 +31321,7 @@ async function projectName(sourceDir) {
|
|
|
29635
31321
|
function resolveBin(name) {
|
|
29636
31322
|
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
29637
31323
|
for (; ; ) {
|
|
29638
|
-
const candidate =
|
|
31324
|
+
const candidate = join26(dir, "node_modules", ".bin", name);
|
|
29639
31325
|
if (existsSync7(candidate)) return candidate;
|
|
29640
31326
|
const parent = dirname6(dir);
|
|
29641
31327
|
if (parent === dir) return name;
|
|
@@ -29702,7 +31388,7 @@ function workingTreeNotices(copy) {
|
|
|
29702
31388
|
}
|
|
29703
31389
|
function localDependencies(sourceDir, say = () => void 0) {
|
|
29704
31390
|
return {
|
|
29705
|
-
createWorkspace: () => mkdtemp2(
|
|
31391
|
+
createWorkspace: () => mkdtemp2(join26(tmpdir2(), "naro-build-")),
|
|
29706
31392
|
removeWorkspace: (path) => rm5(path, { recursive: true, force: true }),
|
|
29707
31393
|
async runCommand(spec, observer) {
|
|
29708
31394
|
if (spec.command === "git" && spec.args[0] === "clone") {
|
|
@@ -29740,6 +31426,18 @@ function localDependencies(sourceDir, say = () => void 0) {
|
|
|
29740
31426
|
async function deployDirectory(sourceDir, options, log2) {
|
|
29741
31427
|
const cpUrl = controlPlaneUrl();
|
|
29742
31428
|
const production = options.production ?? false;
|
|
31429
|
+
if (production && options.environment !== void 0)
|
|
31430
|
+
throw new NaroError(
|
|
31431
|
+
"VALIDATION_FAILED",
|
|
31432
|
+
"environment and production cannot be used together."
|
|
31433
|
+
);
|
|
31434
|
+
const environment = options.environment;
|
|
31435
|
+
if (environment === "production" || environment === "preview")
|
|
31436
|
+
throw new NaroError(
|
|
31437
|
+
"VALIDATION_FAILED",
|
|
31438
|
+
"Pass production: true for production; preview is the default."
|
|
31439
|
+
);
|
|
31440
|
+
const surface = production ? "production" : environment ?? "preview";
|
|
29743
31441
|
const appRoot = options.rootDirectory ? resolve11(sourceDir, options.rootDirectory) : sourceDir;
|
|
29744
31442
|
const framework = detectFramework2(appRoot);
|
|
29745
31443
|
if (!framework.deployable && !options.force) {
|
|
@@ -29762,6 +31460,8 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29762
31460
|
getAccessToken: () => auth.getAccessToken(),
|
|
29763
31461
|
...options.fetch ? { fetch: options.fetch } : {}
|
|
29764
31462
|
});
|
|
31463
|
+
if (environment !== void 0 && options.projectId)
|
|
31464
|
+
await client.environments.get(options.projectId, environment);
|
|
29765
31465
|
const name = slugify2(options.name ?? await projectName(appRoot));
|
|
29766
31466
|
const git2 = await (options.gitInfo ?? detectGitInfo)(sourceDir);
|
|
29767
31467
|
const branch = options.branch ?? git2.branch;
|
|
@@ -29779,7 +31479,7 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29779
31479
|
if (options.projectId) {
|
|
29780
31480
|
try {
|
|
29781
31481
|
const rows = await client.env.list(options.projectId);
|
|
29782
|
-
existing = new Set(
|
|
31482
|
+
existing = new Set(Object.keys(pullEnv(rows, surface)));
|
|
29783
31483
|
} catch {
|
|
29784
31484
|
}
|
|
29785
31485
|
}
|
|
@@ -29791,8 +31491,10 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29791
31491
|
log2(
|
|
29792
31492
|
`\u25B8 ${String(missing.length)} env var(s) needed, not set on this project: ${missing.join(", ")}`
|
|
29793
31493
|
);
|
|
29794
|
-
|
|
29795
|
-
|
|
31494
|
+
const canCollect = environment === void 0 || options.projectId;
|
|
31495
|
+
if (options.onMissingEnv && canCollect)
|
|
31496
|
+
collected = await options.onMissingEnv(missing);
|
|
31497
|
+
else for (const line of envAddGuidance(missing, environment)) log2(line);
|
|
29796
31498
|
}
|
|
29797
31499
|
const missingEnvKeys = missing.filter((key) => !(key in collected));
|
|
29798
31500
|
const envToSend = { ...options.env, ...collected };
|
|
@@ -29801,6 +31503,9 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29801
31503
|
...options.projectId ? { projectId: options.projectId } : { name },
|
|
29802
31504
|
...options.teamSlug ? { teamSlug: options.teamSlug } : {},
|
|
29803
31505
|
production,
|
|
31506
|
+
...options.environment !== void 0 && {
|
|
31507
|
+
environment: options.environment
|
|
31508
|
+
},
|
|
29804
31509
|
...git2.repositoryUrl != null && { repositoryUrl: git2.repositoryUrl },
|
|
29805
31510
|
...branch != null && { branch },
|
|
29806
31511
|
...repositoryRoot != null && { rootDirectory: repositoryRoot },
|
|
@@ -29891,6 +31596,8 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29891
31596
|
status: detail.deployment.status,
|
|
29892
31597
|
url: url2,
|
|
29893
31598
|
production,
|
|
31599
|
+
// A control plane from before named environments sends no environment.
|
|
31600
|
+
environment: ctx.environment ?? null,
|
|
29894
31601
|
missingEnvKeys,
|
|
29895
31602
|
teamSlug: ctx.teamSlug,
|
|
29896
31603
|
projectSlug: ctx.projectSlug,
|
|
@@ -29908,9 +31615,10 @@ async function waitForFinish(client, ctx, log2, intervalMs) {
|
|
|
29908
31615
|
}
|
|
29909
31616
|
function deploymentUrl(detail, ctx, production) {
|
|
29910
31617
|
if (detail.deployment.url) return detail.deployment.url;
|
|
29911
|
-
const surface = ctx.
|
|
31618
|
+
const surface = ctx.environment ? { kind: "environment", slug: ctx.environment, environmentId: null } : production ? PRODUCTION_SURFACE : PREVIEW_SURFACE;
|
|
31619
|
+
const worker = workerNameForSurface(ctx.subdomain ?? ctx.projectId, surface);
|
|
29912
31620
|
const host = new URL(ctx.controlPlaneUrl || controlPlaneUrl()).hostname;
|
|
29913
|
-
return `https://${
|
|
31621
|
+
return `https://${worker}.${host}`;
|
|
29914
31622
|
}
|
|
29915
31623
|
|
|
29916
31624
|
export {
|
|
@@ -29934,6 +31642,8 @@ export {
|
|
|
29934
31642
|
suppliedToken,
|
|
29935
31643
|
resolveAuth,
|
|
29936
31644
|
openBrowser,
|
|
31645
|
+
isReservedEnvTarget,
|
|
31646
|
+
isEnvTargetName,
|
|
29937
31647
|
SPEED_INSIGHTS_METRICS,
|
|
29938
31648
|
SPEED_INSIGHTS_FILTER_KEYS,
|
|
29939
31649
|
SPEED_INSIGHTS_MIN_DEPLOYMENT_SAMPLES,
|
|
@@ -29949,11 +31659,14 @@ export {
|
|
|
29949
31659
|
cellText,
|
|
29950
31660
|
renderRows,
|
|
29951
31661
|
summarizeStatement,
|
|
31662
|
+
parseDotenvDetailed,
|
|
29952
31663
|
serializeDotenv,
|
|
29953
31664
|
MAX_SINGLE_UPLOAD_BYTES,
|
|
31665
|
+
checkObjectName,
|
|
29954
31666
|
parseCacheControl,
|
|
29955
31667
|
STORAGE_OBJECTS_TABLE,
|
|
29956
31668
|
systemTablePredicate,
|
|
31669
|
+
dataError,
|
|
29957
31670
|
SCHEMA_SQL,
|
|
29958
31671
|
tableNames,
|
|
29959
31672
|
snapshotFromRows,
|
|
@@ -29968,6 +31681,7 @@ export {
|
|
|
29968
31681
|
indexesFromRows,
|
|
29969
31682
|
parsePolicyStatement,
|
|
29970
31683
|
splitPolicyStatements,
|
|
31684
|
+
STORAGE_SUBQUERIES_SQL,
|
|
29971
31685
|
planPolicyMigration,
|
|
29972
31686
|
unreadablePolicyComment,
|
|
29973
31687
|
parseRedirectEntry,
|
|
@@ -29990,13 +31704,17 @@ export {
|
|
|
29990
31704
|
RESET_BACKEND_OUTDATED,
|
|
29991
31705
|
buildResetPlan,
|
|
29992
31706
|
policiesLostByReset,
|
|
31707
|
+
storageRefusalOfFiles,
|
|
31708
|
+
resetStorageOutdatedMessage,
|
|
29993
31709
|
resetPoliciesLostMessage,
|
|
29994
31710
|
resetPoliciesWithTablesMessage,
|
|
29995
31711
|
resetFileRefusedMessage,
|
|
29996
31712
|
storedPolicySql,
|
|
29997
31713
|
usageMonthOf,
|
|
29998
31714
|
summarizeTeamUsage,
|
|
31715
|
+
maskEnvValue,
|
|
29999
31716
|
promptForMissingEnv,
|
|
31717
|
+
pullEnv,
|
|
30000
31718
|
detectGitInfo,
|
|
30001
31719
|
parseDotenv2 as parseDotenv,
|
|
30002
31720
|
readProjectEnv2 as readProjectEnv,
|