naro-cli 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +313 -13
- package/dist/{chunk-DEEFHVDZ.js → chunk-SAA66XLD.js} +2082 -410
- package/dist/deploy.d.ts +3 -3
- package/dist/deploy.js +2 -2
- package/dist/index.js +415 -42
- package/package.json +5 -5
|
@@ -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\-._~%!$&()*+,;=:@/[\]]*$/;
|
|
@@ -345,6 +345,8 @@ var DEPLOYMENT_ERROR_CODES = [
|
|
|
345
345
|
"FORBIDDEN",
|
|
346
346
|
"NOT_FOUND",
|
|
347
347
|
"DEPLOYMENT_NOT_PROMOTABLE",
|
|
348
|
+
/** A promotion to another surface rebuilds the commit, and the source was not built from Git (spec 2026-10-18-named-environments §9.1). */
|
|
349
|
+
"PROMOTE_NEEDS_GIT_SOURCE",
|
|
348
350
|
"DEPLOYMENT_NOT_CANCELABLE",
|
|
349
351
|
"SUBDOMAIN_TAKEN",
|
|
350
352
|
"FLAG_EXISTS",
|
|
@@ -511,6 +513,9 @@ function projectPath(teamSlug, projectSlug) {
|
|
|
511
513
|
return `/${teamSlug}/projects/${projectSlug}`;
|
|
512
514
|
}
|
|
513
515
|
|
|
516
|
+
// ../../packages/core/src/platform-mail.ts
|
|
517
|
+
var encoder = new TextEncoder();
|
|
518
|
+
|
|
514
519
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
515
520
|
var external_exports = {};
|
|
516
521
|
__export(external_exports, {
|
|
@@ -15613,7 +15618,7 @@ import { access as access5, readFile as readFile5, writeFile as writeFile4 } fro
|
|
|
15613
15618
|
import { join as join6, resolve as resolve2 } from "path";
|
|
15614
15619
|
|
|
15615
15620
|
// ../../packages/build/src/open-next.ts
|
|
15616
|
-
import { access as access3, readdir, readFile as readFile4 } from "fs/promises";
|
|
15621
|
+
import { access as access3, readdir, readFile as readFile4, realpath } from "fs/promises";
|
|
15617
15622
|
import {
|
|
15618
15623
|
basename,
|
|
15619
15624
|
dirname,
|
|
@@ -15729,7 +15734,33 @@ function contentType(path) {
|
|
|
15729
15734
|
return "application/octet-stream";
|
|
15730
15735
|
}
|
|
15731
15736
|
}
|
|
15732
|
-
async function
|
|
15737
|
+
async function assertInsideOnDisk(appRoot, directory, label2) {
|
|
15738
|
+
let realRoot;
|
|
15739
|
+
let realDirectory;
|
|
15740
|
+
try {
|
|
15741
|
+
[realRoot, realDirectory] = await Promise.all([
|
|
15742
|
+
realpath(appRoot),
|
|
15743
|
+
realpath(directory)
|
|
15744
|
+
]);
|
|
15745
|
+
} catch {
|
|
15746
|
+
return;
|
|
15747
|
+
}
|
|
15748
|
+
const relation = relative(realRoot, realDirectory);
|
|
15749
|
+
if (relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) {
|
|
15750
|
+
throw new DeploymentError(
|
|
15751
|
+
"ARTIFACT_INVALID",
|
|
15752
|
+
`${label2} output leads out of the application root through a symbolic link.`
|
|
15753
|
+
);
|
|
15754
|
+
}
|
|
15755
|
+
if (relation.split(sep).includes(".git")) {
|
|
15756
|
+
throw new DeploymentError(
|
|
15757
|
+
"ARTIFACT_INVALID",
|
|
15758
|
+
`${label2} output leads into .git through a symbolic link.`
|
|
15759
|
+
);
|
|
15760
|
+
}
|
|
15761
|
+
}
|
|
15762
|
+
async function listArtifactFiles(root, label2 = "OpenNext", appRoot) {
|
|
15763
|
+
if (appRoot !== void 0) await assertInsideOnDisk(appRoot, root, label2);
|
|
15733
15764
|
const files = [];
|
|
15734
15765
|
async function visit(directory) {
|
|
15735
15766
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
@@ -15786,7 +15817,6 @@ var OPEN_NEXT_LAYOUT = {
|
|
|
15786
15817
|
outputRoot: ".open-next",
|
|
15787
15818
|
bundleDir: ".open-next/worker-bundle"
|
|
15788
15819
|
};
|
|
15789
|
-
var listFiles = (root) => listArtifactFiles(root);
|
|
15790
15820
|
async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
15791
15821
|
const configPath = await findWranglerConfig(appRoot, wranglerConfigPath);
|
|
15792
15822
|
return readWorkerArtifact(appRoot, identity, {
|
|
@@ -15795,6 +15825,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
|
|
|
15795
15825
|
});
|
|
15796
15826
|
}
|
|
15797
15827
|
async function readWorkerArtifact(appRoot, identity, layout) {
|
|
15828
|
+
const listFiles = (root) => listArtifactFiles(root, "OpenNext", appRoot);
|
|
15798
15829
|
try {
|
|
15799
15830
|
const configPath = await findWranglerConfig(
|
|
15800
15831
|
appRoot,
|
|
@@ -15839,12 +15870,13 @@ async function readWorkerArtifact(appRoot, identity, layout) {
|
|
|
15839
15870
|
);
|
|
15840
15871
|
}
|
|
15841
15872
|
const bundleRoot = confinedPath(appRoot, layout.bundleDir, "Bundle dir");
|
|
15842
|
-
let
|
|
15873
|
+
let bundleExists = true;
|
|
15843
15874
|
try {
|
|
15844
15875
|
await access3(bundleRoot);
|
|
15845
|
-
bundleFiles = await listFiles(bundleRoot);
|
|
15846
15876
|
} catch {
|
|
15877
|
+
bundleExists = false;
|
|
15847
15878
|
}
|
|
15879
|
+
const bundleFiles = bundleExists ? await listFiles(bundleRoot) : null;
|
|
15848
15880
|
const outputFiles = bundleFiles ?? await listFiles(outputRoot);
|
|
15849
15881
|
const entrypointName = bundleFiles ? basename(entrypointPath) : relative(outputRoot, entrypointPath).split(sep).join("/");
|
|
15850
15882
|
const assetsPrefix = `${relative(outputRoot, configuredAssets).split(sep).join("/")}/`;
|
|
@@ -16192,7 +16224,7 @@ async function readVinextArtifact(appRoot, identity) {
|
|
|
16192
16224
|
const entrypointName = metadata.main.split(/[\\/]/).join("/");
|
|
16193
16225
|
const matchers = moduleMatchers(metadata.rules);
|
|
16194
16226
|
const isModule = (name) => matchers.length === 0 || matchers.some((matcher) => matcher.test(name));
|
|
16195
|
-
const serverFiles = await listArtifactFiles(serverRoot, "vinext");
|
|
16227
|
+
const serverFiles = await listArtifactFiles(serverRoot, "vinext", appRoot);
|
|
16196
16228
|
const entrypoint = serverFiles.find((file2) => file2.name === entrypointName);
|
|
16197
16229
|
if (!entrypoint) {
|
|
16198
16230
|
throw new DeploymentError(
|
|
@@ -16206,7 +16238,7 @@ async function readVinextArtifact(appRoot, identity) {
|
|
|
16206
16238
|
modules: serverFiles.filter(
|
|
16207
16239
|
(file2) => file2.name !== entrypointName && file2.name !== WRANGLER_OUTPUT_NAME && !file2.name.endsWith(".map") && isModule(file2.name)
|
|
16208
16240
|
),
|
|
16209
|
-
assets: await listArtifactFiles(clientRoot, "vinext"),
|
|
16241
|
+
assets: await listArtifactFiles(clientRoot, "vinext", appRoot),
|
|
16210
16242
|
compatibilityDate: metadata.compatibility_date,
|
|
16211
16243
|
compatibilityFlags: Array.isArray(metadata.compatibility_flags) ? metadata.compatibility_flags.filter(
|
|
16212
16244
|
(flag) => typeof flag === "string"
|
|
@@ -16879,7 +16911,8 @@ async function runCommand(spec, observer) {
|
|
|
16879
16911
|
}
|
|
16880
16912
|
|
|
16881
16913
|
// ../../packages/build/src/repository.ts
|
|
16882
|
-
import {
|
|
16914
|
+
import { realpath as realpath2 } from "fs/promises";
|
|
16915
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
16883
16916
|
function resolveAppRoot(checkout, configuredRoot) {
|
|
16884
16917
|
const checkoutRoot = resolve3(checkout);
|
|
16885
16918
|
if (!configuredRoot) return checkoutRoot;
|
|
@@ -16891,12 +16924,34 @@ function resolveAppRoot(checkout, configuredRoot) {
|
|
|
16891
16924
|
if (relativeRoot === ".." || relativeRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute2(relativeRoot)) {
|
|
16892
16925
|
throw new Error("Configured root directory escapes repository");
|
|
16893
16926
|
}
|
|
16927
|
+
if (relativeRoot.split(/[\\/]/).includes(".git")) {
|
|
16928
|
+
throw new Error("Configured root directory cannot be inside .git");
|
|
16929
|
+
}
|
|
16894
16930
|
return appRoot;
|
|
16895
16931
|
}
|
|
16932
|
+
async function assertAppRootInsideCheckout(checkout, appRoot) {
|
|
16933
|
+
let realCheckout;
|
|
16934
|
+
let realApp;
|
|
16935
|
+
try {
|
|
16936
|
+
[realCheckout, realApp] = await Promise.all([
|
|
16937
|
+
realpath2(checkout),
|
|
16938
|
+
realpath2(appRoot)
|
|
16939
|
+
]);
|
|
16940
|
+
} catch {
|
|
16941
|
+
return;
|
|
16942
|
+
}
|
|
16943
|
+
const relation = relative2(realCheckout, realApp);
|
|
16944
|
+
if (relation === ".." || relation.startsWith(`..${sep2}`) || isAbsolute2(relation)) {
|
|
16945
|
+
throw new Error("Configured root directory escapes repository");
|
|
16946
|
+
}
|
|
16947
|
+
if (relation.split(sep2).includes(".git")) {
|
|
16948
|
+
throw new Error("Configured root directory cannot be inside .git");
|
|
16949
|
+
}
|
|
16950
|
+
}
|
|
16896
16951
|
|
|
16897
16952
|
// ../../packages/build/src/static-artifact.ts
|
|
16898
16953
|
import { readdir as readdir3, readFile as readFile7 } from "fs/promises";
|
|
16899
|
-
import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve4, sep as
|
|
16954
|
+
import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve4, sep as sep3 } from "path";
|
|
16900
16955
|
var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
|
|
16901
16956
|
var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
|
|
16902
16957
|
var ALLOWED_DOTFILE = ".well-known";
|
|
@@ -16912,11 +16967,16 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16912
16967
|
);
|
|
16913
16968
|
const outputRoot = resolve4(appRoot, outputDirectory);
|
|
16914
16969
|
const relation = relative3(appRoot, outputRoot);
|
|
16915
|
-
if (relation === ".." || relation.startsWith(`..${
|
|
16970
|
+
if (relation === ".." || relation.startsWith(`..${sep3}`) || isAbsolute3(relation))
|
|
16916
16971
|
throw new DeploymentError(
|
|
16917
16972
|
"ARTIFACT_INVALID",
|
|
16918
16973
|
"Output directory escapes the application root."
|
|
16919
16974
|
);
|
|
16975
|
+
if (relation.split(sep3).includes(".git"))
|
|
16976
|
+
throw new DeploymentError(
|
|
16977
|
+
"ARTIFACT_INVALID",
|
|
16978
|
+
"Output directory cannot be inside .git."
|
|
16979
|
+
);
|
|
16920
16980
|
const assets = [];
|
|
16921
16981
|
async function visit(directory) {
|
|
16922
16982
|
const entries = await readdir3(directory, { withFileTypes: true });
|
|
@@ -16933,7 +16993,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16933
16993
|
if (entry.isDirectory()) await visit(path);
|
|
16934
16994
|
else if (entry.isFile()) {
|
|
16935
16995
|
assets.push({
|
|
16936
|
-
name: relative3(outputRoot, path).split(
|
|
16996
|
+
name: relative3(outputRoot, path).split(sep3).join("/"),
|
|
16937
16997
|
data: await readFile7(path),
|
|
16938
16998
|
contentType: contentType(path)
|
|
16939
16999
|
});
|
|
@@ -16941,6 +17001,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16941
17001
|
}
|
|
16942
17002
|
}
|
|
16943
17003
|
try {
|
|
17004
|
+
await assertInsideOnDisk(appRoot, outputRoot, "Static");
|
|
16944
17005
|
await visit(outputRoot);
|
|
16945
17006
|
} catch (error51) {
|
|
16946
17007
|
if (error51 instanceof DeploymentError) throw error51;
|
|
@@ -16980,7 +17041,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16980
17041
|
// ../../packages/build/src/strategy/fingerprint.ts
|
|
16981
17042
|
import { createHash as createHash3 } from "crypto";
|
|
16982
17043
|
import { readFile as readFile8 } from "fs/promises";
|
|
16983
|
-
import { join as join10, relative as relative4, sep as
|
|
17044
|
+
import { join as join10, relative as relative4, sep as sep4 } from "path";
|
|
16984
17045
|
var APP_ROOT_FILES = [
|
|
16985
17046
|
"package.json",
|
|
16986
17047
|
"next.config.js",
|
|
@@ -17035,7 +17096,7 @@ async function hashStrategyInputs(checkout, appRoot) {
|
|
|
17035
17096
|
for (const path of candidates) {
|
|
17036
17097
|
const digest = await hashFile(path);
|
|
17037
17098
|
if (digest === null) continue;
|
|
17038
|
-
entries.push([relative4(checkout, path).split(
|
|
17099
|
+
entries.push([relative4(checkout, path).split(sep4).join("/"), digest]);
|
|
17039
17100
|
}
|
|
17040
17101
|
return entries.toSorted(
|
|
17041
17102
|
([left], [right]) => left < right ? -1 : left > right ? 1 : 0
|
|
@@ -17056,8 +17117,76 @@ async function computeStrategyFingerprint(input) {
|
|
|
17056
17117
|
}
|
|
17057
17118
|
|
|
17058
17119
|
// ../../packages/build/src/strategy/project-facts.ts
|
|
17059
|
-
import { access as access6, readFile as
|
|
17120
|
+
import { access as access6, readFile as readFile10 } from "fs/promises";
|
|
17121
|
+
import { join as join12 } from "path";
|
|
17122
|
+
|
|
17123
|
+
// ../../packages/build/src/strategy/user-adapter.ts
|
|
17124
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
17060
17125
|
import { join as join11 } from "path";
|
|
17126
|
+
var ADAPTER_PATH = /\badapterPath\b/;
|
|
17127
|
+
var EXPERIMENTAL_ADAPTER_PATH = /\bexperimental\s*:\s*\{[^{}]*\badapterPath\b/;
|
|
17128
|
+
function adapterKeyFromConfigSource(source) {
|
|
17129
|
+
const code = stripComments(source);
|
|
17130
|
+
if (!ADAPTER_PATH.test(code)) return null;
|
|
17131
|
+
return EXPERIMENTAL_ADAPTER_PATH.test(code) ? "experimental.adapterPath" : "adapterPath";
|
|
17132
|
+
}
|
|
17133
|
+
function keysNextReads(nextVersion) {
|
|
17134
|
+
const version2 = parseNextVersion(nextVersion);
|
|
17135
|
+
if (!version2) return ["adapterPath", "experimental.adapterPath"];
|
|
17136
|
+
if (version2.major < 15 || version2.major === 15 && version2.minor < 4)
|
|
17137
|
+
return [];
|
|
17138
|
+
if (version2.major > 16 || version2.major === 16 && version2.minor >= 2)
|
|
17139
|
+
return ["adapterPath", "experimental.adapterPath"];
|
|
17140
|
+
return ["experimental.adapterPath"];
|
|
17141
|
+
}
|
|
17142
|
+
function userAdapterOf(input) {
|
|
17143
|
+
const keys = keysNextReads(input.nextVersion);
|
|
17144
|
+
if (keys.length === 0) return null;
|
|
17145
|
+
const config2 = input.facts.adapterConfig;
|
|
17146
|
+
if (config2 && keys.includes(config2.key))
|
|
17147
|
+
return { source: "next-config", file: config2.file, key: config2.key };
|
|
17148
|
+
if (input.env.NEXT_ADAPTER_PATH) return { source: "env" };
|
|
17149
|
+
return null;
|
|
17150
|
+
}
|
|
17151
|
+
async function adapterInBuildOutput(appRoot, nextVersion) {
|
|
17152
|
+
const keys = keysNextReads(nextVersion);
|
|
17153
|
+
if (keys.length === 0) return null;
|
|
17154
|
+
let manifest2;
|
|
17155
|
+
try {
|
|
17156
|
+
const parsed = JSON.parse(
|
|
17157
|
+
await readFile9(
|
|
17158
|
+
join11(appRoot, ".next", "required-server-files.json"),
|
|
17159
|
+
"utf8"
|
|
17160
|
+
)
|
|
17161
|
+
);
|
|
17162
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
17163
|
+
manifest2 = parsed;
|
|
17164
|
+
} catch {
|
|
17165
|
+
return null;
|
|
17166
|
+
}
|
|
17167
|
+
const topLevel = manifest2.config?.adapterPath;
|
|
17168
|
+
if (keys.includes("adapterPath") && typeof topLevel === "string" && topLevel)
|
|
17169
|
+
return { source: "build-output", key: "adapterPath" };
|
|
17170
|
+
const experimental = manifest2.config?.experimental?.adapterPath;
|
|
17171
|
+
if (keys.includes("experimental.adapterPath") && typeof experimental === "string" && experimental)
|
|
17172
|
+
return { source: "build-output", key: "experimental.adapterPath" };
|
|
17173
|
+
return null;
|
|
17174
|
+
}
|
|
17175
|
+
function describeUserAdapter(adapter) {
|
|
17176
|
+
switch (adapter.source) {
|
|
17177
|
+
case "next-config":
|
|
17178
|
+
return `${adapter.file} ${adapter.key}`;
|
|
17179
|
+
case "env":
|
|
17180
|
+
return "NEXT_ADAPTER_PATH";
|
|
17181
|
+
case "build-output":
|
|
17182
|
+
return `${adapter.key} in the Next.js build output`;
|
|
17183
|
+
}
|
|
17184
|
+
}
|
|
17185
|
+
function openNextAdapterWarning(adapter) {
|
|
17186
|
+
return `P2 opennext-cloudflare: The project sets its own Next.js adapter (${describeUserAdapter(adapter)}); its modifyConfig and onBuildComplete also run in the OpenNext build.`;
|
|
17187
|
+
}
|
|
17188
|
+
|
|
17189
|
+
// ../../packages/build/src/strategy/project-facts.ts
|
|
17061
17190
|
var SOURCE_EXTENSIONS2 = ["ts", "tsx", "js", "jsx", "mjs", "mts"];
|
|
17062
17191
|
var SOURCE_DIRECTORIES = ["", "src"];
|
|
17063
17192
|
var VITE_CONFIGS = [
|
|
@@ -17078,7 +17207,7 @@ async function findSourceFile(appRoot, basename5) {
|
|
|
17078
17207
|
for (const dir of SOURCE_DIRECTORIES) {
|
|
17079
17208
|
for (const ext of SOURCE_EXTENSIONS2) {
|
|
17080
17209
|
const rel = dir ? `${dir}/${basename5}.${ext}` : `${basename5}.${ext}`;
|
|
17081
|
-
if (await exists4(
|
|
17210
|
+
if (await exists4(join12(appRoot, rel))) return rel;
|
|
17082
17211
|
}
|
|
17083
17212
|
}
|
|
17084
17213
|
return null;
|
|
@@ -17098,19 +17227,28 @@ function matcherRoutesFromSource(source) {
|
|
|
17098
17227
|
}
|
|
17099
17228
|
async function readIfExists(path) {
|
|
17100
17229
|
try {
|
|
17101
|
-
return await
|
|
17230
|
+
return await readFile10(path, "utf8");
|
|
17102
17231
|
} catch {
|
|
17103
17232
|
return null;
|
|
17104
17233
|
}
|
|
17105
17234
|
}
|
|
17235
|
+
async function findAdapterConfig(appRoot) {
|
|
17236
|
+
for (const file2 of NEXT_CONFIG_FILES) {
|
|
17237
|
+
const source = await readIfExists(join12(appRoot, file2));
|
|
17238
|
+
if (source === null) continue;
|
|
17239
|
+
const key = adapterKeyFromConfigSource(source);
|
|
17240
|
+
return key ? { file: file2, key } : null;
|
|
17241
|
+
}
|
|
17242
|
+
return null;
|
|
17243
|
+
}
|
|
17106
17244
|
async function collectProjectFacts(appRoot) {
|
|
17107
17245
|
const proxyFile = await findSourceFile(appRoot, "proxy");
|
|
17108
17246
|
const middlewareFile = await findSourceFile(appRoot, "middleware");
|
|
17109
17247
|
const routeSourceFile = middlewareFile ?? proxyFile;
|
|
17110
|
-
const routeSource = routeSourceFile ? await readIfExists(
|
|
17248
|
+
const routeSource = routeSourceFile ? await readIfExists(join12(appRoot, routeSourceFile)) : null;
|
|
17111
17249
|
let usesVinext = false;
|
|
17112
17250
|
let nextSpec = null;
|
|
17113
|
-
const packageJsonSource = await readIfExists(
|
|
17251
|
+
const packageJsonSource = await readIfExists(join12(appRoot, "package.json"));
|
|
17114
17252
|
if (packageJsonSource) {
|
|
17115
17253
|
try {
|
|
17116
17254
|
const packageJson = JSON.parse(packageJsonSource);
|
|
@@ -17121,7 +17259,7 @@ async function collectProjectFacts(appRoot) {
|
|
|
17121
17259
|
nextSpec = dependency("next");
|
|
17122
17260
|
if (dependency("vinext") !== null) {
|
|
17123
17261
|
for (const name of VITE_CONFIGS) {
|
|
17124
|
-
const config2 = await readIfExists(
|
|
17262
|
+
const config2 = await readIfExists(join12(appRoot, name));
|
|
17125
17263
|
if (config2 && /\bvinext\s*\(/.test(config2) && /@cloudflare\/vite-plugin/.test(config2)) {
|
|
17126
17264
|
usesVinext = true;
|
|
17127
17265
|
break;
|
|
@@ -17136,7 +17274,8 @@ async function collectProjectFacts(appRoot) {
|
|
|
17136
17274
|
middlewareFile,
|
|
17137
17275
|
matcherRoutes: routeSource ? matcherRoutesFromSource(routeSource) : [],
|
|
17138
17276
|
usesVinext,
|
|
17139
|
-
nextSpec
|
|
17277
|
+
nextSpec,
|
|
17278
|
+
adapterConfig: await findAdapterConfig(appRoot)
|
|
17140
17279
|
};
|
|
17141
17280
|
}
|
|
17142
17281
|
|
|
@@ -17523,11 +17662,11 @@ function defaultSmokeProbes(matcherRoutes) {
|
|
|
17523
17662
|
|
|
17524
17663
|
// ../../packages/build/src/strategy/strategies/opennext-aws.ts
|
|
17525
17664
|
import { writeFile as writeFile6 } from "fs/promises";
|
|
17526
|
-
import { join as
|
|
17665
|
+
import { join as join14 } from "path";
|
|
17527
17666
|
|
|
17528
17667
|
// ../../packages/build/src/strategy/strategies/shared.ts
|
|
17529
|
-
import { access as access7, readFile as
|
|
17530
|
-
import { dirname as dirname2, join as
|
|
17668
|
+
import { access as access7, readFile as readFile11, rm, writeFile as writeFile5 } from "fs/promises";
|
|
17669
|
+
import { dirname as dirname2, join as join13, resolve as resolve5 } from "path";
|
|
17531
17670
|
function capturingObserver(observer) {
|
|
17532
17671
|
const lines = [];
|
|
17533
17672
|
return {
|
|
@@ -17584,7 +17723,7 @@ async function findProjectBin(appRoot, checkout, name) {
|
|
|
17584
17723
|
let dir = resolve5(appRoot);
|
|
17585
17724
|
const stop = resolve5(checkout);
|
|
17586
17725
|
for (; ; ) {
|
|
17587
|
-
const candidate =
|
|
17726
|
+
const candidate = join13(dir, "node_modules", ".bin", name);
|
|
17588
17727
|
if (await pathExists2(candidate)) return candidate;
|
|
17589
17728
|
if (dir === stop) return null;
|
|
17590
17729
|
const parent = dirname2(dir);
|
|
@@ -17598,7 +17737,7 @@ function dotenvQuote(value) {
|
|
|
17598
17737
|
return `"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`;
|
|
17599
17738
|
}
|
|
17600
17739
|
async function withDevVars(wranglerConfigPath, env, run) {
|
|
17601
|
-
const path =
|
|
17740
|
+
const path = join13(dirname2(wranglerConfigPath), ".dev.vars");
|
|
17602
17741
|
const keys = Object.keys(env).filter(
|
|
17603
17742
|
(key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
|
|
17604
17743
|
);
|
|
@@ -17631,7 +17770,7 @@ async function bundleWorker(context, layout, observer, errorCode2) {
|
|
|
17631
17770
|
cwd: layout.appRoot,
|
|
17632
17771
|
env: {
|
|
17633
17772
|
CI: "1",
|
|
17634
|
-
WRANGLER_LOG_PATH:
|
|
17773
|
+
WRANGLER_LOG_PATH: join13(
|
|
17635
17774
|
layout.appRoot,
|
|
17636
17775
|
layout.outputRoot,
|
|
17637
17776
|
"wrangler.log"
|
|
@@ -17664,12 +17803,12 @@ async function smokeWorker(context, layout, observer) {
|
|
|
17664
17803
|
"--show-interactive-dev-session=false",
|
|
17665
17804
|
"--log-level=warn",
|
|
17666
17805
|
"--persist-to",
|
|
17667
|
-
|
|
17806
|
+
join13(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
|
|
17668
17807
|
],
|
|
17669
17808
|
cwd: layout.appRoot,
|
|
17670
17809
|
env: {
|
|
17671
17810
|
CI: "1",
|
|
17672
|
-
WRANGLER_LOG_PATH:
|
|
17811
|
+
WRANGLER_LOG_PATH: join13(
|
|
17673
17812
|
layout.appRoot,
|
|
17674
17813
|
layout.outputRoot,
|
|
17675
17814
|
"wrangler-dev.log"
|
|
@@ -17685,10 +17824,10 @@ async function smokeWorker(context, layout, observer) {
|
|
|
17685
17824
|
);
|
|
17686
17825
|
}
|
|
17687
17826
|
async function approvePnpmBuilds(checkout, packages) {
|
|
17688
|
-
const path =
|
|
17827
|
+
const path = join13(checkout, "pnpm-workspace.yaml");
|
|
17689
17828
|
let source = "";
|
|
17690
17829
|
try {
|
|
17691
|
-
source = await
|
|
17830
|
+
source = await readFile11(path, "utf8");
|
|
17692
17831
|
} catch {
|
|
17693
17832
|
}
|
|
17694
17833
|
let next = source;
|
|
@@ -17781,7 +17920,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17781
17920
|
} catch (error51) {
|
|
17782
17921
|
return failed("prepare", describeError(error51));
|
|
17783
17922
|
}
|
|
17784
|
-
const configPath =
|
|
17923
|
+
const configPath = join14(fork.appRoot, CONFIG_NAME);
|
|
17785
17924
|
try {
|
|
17786
17925
|
await writeFile6(
|
|
17787
17926
|
configPath,
|
|
@@ -17810,7 +17949,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17810
17949
|
observer
|
|
17811
17950
|
);
|
|
17812
17951
|
if (!build.ok) return failed("build", describeFailure(build));
|
|
17813
|
-
const serverFunction =
|
|
17952
|
+
const serverFunction = join14(fork.appRoot, SERVER_FUNCTION);
|
|
17814
17953
|
if (!await pathExists2(serverFunction))
|
|
17815
17954
|
return failed("bundle", `OpenNext AWS produced no ${SERVER_FUNCTION}.`);
|
|
17816
17955
|
const smoke = await context.ports.smoke.run(
|
|
@@ -17832,7 +17971,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17832
17971
|
status: "success",
|
|
17833
17972
|
artifact: {
|
|
17834
17973
|
kind: "opennext-aws",
|
|
17835
|
-
outputRoot:
|
|
17974
|
+
outputRoot: join14(fork.appRoot, ".open-next"),
|
|
17836
17975
|
serverFunction
|
|
17837
17976
|
},
|
|
17838
17977
|
notes: [
|
|
@@ -17844,8 +17983,8 @@ function createOpenNextAwsStrategy() {
|
|
|
17844
17983
|
}
|
|
17845
17984
|
|
|
17846
17985
|
// ../../packages/build/src/strategy/strategies/opennext-cloudflare.ts
|
|
17847
|
-
import { readFile as
|
|
17848
|
-
import { join as
|
|
17986
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
17987
|
+
import { join as join15 } from "path";
|
|
17849
17988
|
var PROXY_TO_MIDDLEWARE_VARIANT = "proxy-to-middleware";
|
|
17850
17989
|
var NODE_MIDDLEWARE_REFUSAL = /Node\.js middleware is not (?:currently )?supported/i;
|
|
17851
17990
|
var NODE_MIDDLEWARE_EXPERIMENTAL = /Node\.js middleware support is experimental/i;
|
|
@@ -17853,8 +17992,8 @@ var NODE_MIDDLEWARE_NOT_DEPLOYED = 'Node.js-runtime middleware (runtime: "nodejs
|
|
|
17853
17992
|
async function buildDeclaresNodeMiddleware(appRoot) {
|
|
17854
17993
|
try {
|
|
17855
17994
|
const manifest2 = JSON.parse(
|
|
17856
|
-
await
|
|
17857
|
-
|
|
17995
|
+
await readFile12(
|
|
17996
|
+
join15(appRoot, ".next", "server", "functions-config-manifest.json"),
|
|
17858
17997
|
"utf8"
|
|
17859
17998
|
)
|
|
17860
17999
|
);
|
|
@@ -17864,7 +18003,7 @@ async function buildDeclaresNodeMiddleware(appRoot) {
|
|
|
17864
18003
|
}
|
|
17865
18004
|
}
|
|
17866
18005
|
function createOpenNextCloudflareStrategy(options) {
|
|
17867
|
-
async function attempt(context, roots, observer, ownBuild) {
|
|
18006
|
+
async function attempt(context, roots, observer, ownBuild, adapterNotice) {
|
|
17868
18007
|
let platform;
|
|
17869
18008
|
try {
|
|
17870
18009
|
platform = await options.createPlatformBuildConfig(
|
|
@@ -17902,6 +18041,13 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17902
18041
|
outcome: ownBuild ? appBuildFailed(describeFailure(nextBuild)) : failed("build", describeFailure(nextBuild))
|
|
17903
18042
|
};
|
|
17904
18043
|
}
|
|
18044
|
+
if (!adapterNotice.warned) {
|
|
18045
|
+
const adapter = userAdapterOf(context) ?? await adapterInBuildOutput(roots.appRoot, context.nextVersion);
|
|
18046
|
+
if (adapter) {
|
|
18047
|
+
adapterNotice.warned = true;
|
|
18048
|
+
await warn(observer, openNextAdapterWarning(adapter));
|
|
18049
|
+
}
|
|
18050
|
+
}
|
|
17905
18051
|
if (await buildDeclaresNodeMiddleware(roots.appRoot)) {
|
|
17906
18052
|
return {
|
|
17907
18053
|
ok: false,
|
|
@@ -17997,6 +18143,7 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17997
18143
|
target: "cloudflare",
|
|
17998
18144
|
supports: () => ({ supported: true }),
|
|
17999
18145
|
async evaluate(context, observer) {
|
|
18146
|
+
const adapterNotice = { warned: false };
|
|
18000
18147
|
const rememberedRewrite = context.pinnedVariant === PROXY_TO_MIDDLEWARE_VARIANT && context.facts.proxyFile !== null;
|
|
18001
18148
|
if (rememberedRewrite) {
|
|
18002
18149
|
await info(
|
|
@@ -18008,7 +18155,13 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
18008
18155
|
observer,
|
|
18009
18156
|
"P2 opennext-cloudflare: building the project as written."
|
|
18010
18157
|
);
|
|
18011
|
-
const first = await attempt(
|
|
18158
|
+
const first = await attempt(
|
|
18159
|
+
context,
|
|
18160
|
+
context.workspace,
|
|
18161
|
+
observer,
|
|
18162
|
+
true,
|
|
18163
|
+
adapterNotice
|
|
18164
|
+
);
|
|
18012
18165
|
if (first.ok) {
|
|
18013
18166
|
return {
|
|
18014
18167
|
status: "success",
|
|
@@ -18040,7 +18193,13 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
18040
18193
|
observer,
|
|
18041
18194
|
`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.`
|
|
18042
18195
|
);
|
|
18043
|
-
const second = await attempt(
|
|
18196
|
+
const second = await attempt(
|
|
18197
|
+
context,
|
|
18198
|
+
fork,
|
|
18199
|
+
observer,
|
|
18200
|
+
rememberedRewrite,
|
|
18201
|
+
adapterNotice
|
|
18202
|
+
);
|
|
18044
18203
|
if (!second.ok) {
|
|
18045
18204
|
if (!rememberedRewrite || second.outcome.status !== "failed" || !second.outcome.appBuild)
|
|
18046
18205
|
return second.outcome;
|
|
@@ -18052,7 +18211,8 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
18052
18211
|
context,
|
|
18053
18212
|
context.workspace,
|
|
18054
18213
|
observer,
|
|
18055
|
-
true
|
|
18214
|
+
true,
|
|
18215
|
+
adapterNotice
|
|
18056
18216
|
);
|
|
18057
18217
|
if (asWritten.ok) {
|
|
18058
18218
|
await info(
|
|
@@ -18121,7 +18281,7 @@ function createAwsMiddlewareCloudflareAppStrategy() {
|
|
|
18121
18281
|
}
|
|
18122
18282
|
|
|
18123
18283
|
// ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
|
|
18124
|
-
import { join as
|
|
18284
|
+
import { join as join16 } from "path";
|
|
18125
18285
|
var VINEXT_MIN_NEXT_MAJOR = 16;
|
|
18126
18286
|
var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
|
|
18127
18287
|
var VINEXT_OUTPUT = {
|
|
@@ -18277,7 +18437,7 @@ function createVinextCloudflareStrategy(options) {
|
|
|
18277
18437
|
context,
|
|
18278
18438
|
{
|
|
18279
18439
|
appRoot: fork.appRoot,
|
|
18280
|
-
wranglerConfigPath:
|
|
18440
|
+
wranglerConfigPath: join16(
|
|
18281
18441
|
fork.appRoot,
|
|
18282
18442
|
"dist",
|
|
18283
18443
|
"server",
|
|
@@ -18302,7 +18462,7 @@ function createVinextCloudflareStrategy(options) {
|
|
|
18302
18462
|
// ../../packages/build/src/strategy/workspace.ts
|
|
18303
18463
|
import { execFile } from "child_process";
|
|
18304
18464
|
import { cp, mkdir as mkdir2, readdir as readdir4, rm as rm2 } from "fs/promises";
|
|
18305
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
18465
|
+
import { basename as basename2, dirname as dirname3, join as join17, relative as relative5, resolve as resolve6 } from "path";
|
|
18306
18466
|
import { promisify } from "util";
|
|
18307
18467
|
var execFileAsync = promisify(execFile);
|
|
18308
18468
|
var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
|
|
@@ -18325,14 +18485,14 @@ async function copyInstalledPackages(source, target2) {
|
|
|
18325
18485
|
const walk2 = async (dir) => {
|
|
18326
18486
|
for (const entry of await readdir4(dir, { withFileTypes: true })) {
|
|
18327
18487
|
if (!entry.isDirectory() || entry.name === ".git") continue;
|
|
18328
|
-
const path =
|
|
18488
|
+
const path = join17(dir, entry.name);
|
|
18329
18489
|
if (entry.name === "node_modules") installs.push(path);
|
|
18330
18490
|
else await walk2(path);
|
|
18331
18491
|
}
|
|
18332
18492
|
};
|
|
18333
18493
|
await walk2(origin);
|
|
18334
18494
|
for (const install of installs)
|
|
18335
|
-
await copyTree(install,
|
|
18495
|
+
await copyTree(install, join17(target2, relative5(origin, install)));
|
|
18336
18496
|
}
|
|
18337
18497
|
async function copyTree(from, to) {
|
|
18338
18498
|
await mkdir2(dirname3(to), { recursive: true });
|
|
@@ -18347,10 +18507,11 @@ async function copyTree(from, to) {
|
|
|
18347
18507
|
await cp(from, to, { recursive: true, verbatimSymlinks: true });
|
|
18348
18508
|
}
|
|
18349
18509
|
function forkedAppRoot(checkout, appRoot, forkCheckout) {
|
|
18350
|
-
return
|
|
18510
|
+
return join17(forkCheckout, relative5(checkout, appRoot));
|
|
18351
18511
|
}
|
|
18352
18512
|
|
|
18353
18513
|
// ../../packages/build/src/local-build-executor.ts
|
|
18514
|
+
var PINNED_COMMIT_SHA = /^[0-9a-f]{40}$/;
|
|
18354
18515
|
function concurrencyFromEnvironment() {
|
|
18355
18516
|
const raw = Number(process.env.NARO_RESOLVER_CONCURRENCY ?? "1");
|
|
18356
18517
|
return Number.isInteger(raw) && raw > 0 ? raw : 1;
|
|
@@ -18369,7 +18530,7 @@ async function withRouteList(artifact, list, appRoot, observer) {
|
|
|
18369
18530
|
}
|
|
18370
18531
|
}
|
|
18371
18532
|
var defaultLocalBuildDependencies = {
|
|
18372
|
-
createWorkspace: () => mkdtemp(
|
|
18533
|
+
createWorkspace: () => mkdtemp(join18(tmpdir(), "next-deploy-build-")),
|
|
18373
18534
|
removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
|
|
18374
18535
|
runCommand,
|
|
18375
18536
|
detectPackageManager,
|
|
@@ -18403,8 +18564,8 @@ async function readInstalledNextVersion(appRoot, checkout) {
|
|
|
18403
18564
|
const stop = resolve7(checkout);
|
|
18404
18565
|
for (; ; ) {
|
|
18405
18566
|
try {
|
|
18406
|
-
const source = await
|
|
18407
|
-
|
|
18567
|
+
const source = await readFile13(
|
|
18568
|
+
join18(dir, "node_modules", "next", "package.json"),
|
|
18408
18569
|
"utf8"
|
|
18409
18570
|
);
|
|
18410
18571
|
const version2 = JSON.parse(source).version;
|
|
@@ -18495,10 +18656,72 @@ var LocalBuildExecutor = class {
|
|
|
18495
18656
|
constructor(dependencies = defaultLocalBuildDependencies) {
|
|
18496
18657
|
this.dependencies = dependencies;
|
|
18497
18658
|
}
|
|
18659
|
+
/**
|
|
18660
|
+
* Checks out one commit, whichever ref reaches it: GitHub serves any
|
|
18661
|
+
* reachable commit by SHA, which `clone --branch` cannot ask for. The URL
|
|
18662
|
+
* carries the installation token and is only ever an argument — runCommand
|
|
18663
|
+
* logs a command's output (redacted), never the command line, and a failed
|
|
18664
|
+
* command's message names the command, not its arguments.
|
|
18665
|
+
*
|
|
18666
|
+
* The commit is an argument of `git fetch`, so it is checked before any git
|
|
18667
|
+
* command runs: a value git could read as an option (`--upload-pack=…`) or a
|
|
18668
|
+
* ref name must not get that far. The build container checks it on the way
|
|
18669
|
+
* in; the local runner passes claim's input straight through, so this is the
|
|
18670
|
+
* check that covers it.
|
|
18671
|
+
*/
|
|
18672
|
+
async checkoutPinnedCommit(commitSha, repositoryUrl, workspace, checkout, observer) {
|
|
18673
|
+
if (!PINNED_COMMIT_SHA.test(commitSha)) {
|
|
18674
|
+
throw new DeploymentError(
|
|
18675
|
+
"CLONE_FAILED",
|
|
18676
|
+
"The pinned commit is not a 40-character lowercase hex SHA."
|
|
18677
|
+
);
|
|
18678
|
+
}
|
|
18679
|
+
const git2 = (args) => this.dependencies.runCommand(
|
|
18680
|
+
{
|
|
18681
|
+
command: "git",
|
|
18682
|
+
args,
|
|
18683
|
+
cwd: workspace,
|
|
18684
|
+
timeoutMs: 12e4,
|
|
18685
|
+
stage: "CLONING",
|
|
18686
|
+
errorCode: "CLONE_FAILED"
|
|
18687
|
+
},
|
|
18688
|
+
observer
|
|
18689
|
+
);
|
|
18690
|
+
await git2(["init", "--quiet", checkout]);
|
|
18691
|
+
await git2(["-C", checkout, "remote", "add", "origin", "--", repositoryUrl]);
|
|
18692
|
+
try {
|
|
18693
|
+
await git2([
|
|
18694
|
+
"-C",
|
|
18695
|
+
checkout,
|
|
18696
|
+
"fetch",
|
|
18697
|
+
"--quiet",
|
|
18698
|
+
"--depth",
|
|
18699
|
+
"1",
|
|
18700
|
+
"origin",
|
|
18701
|
+
commitSha
|
|
18702
|
+
]);
|
|
18703
|
+
} catch (error51) {
|
|
18704
|
+
if (!(error51 instanceof CommandExecutionError)) throw error51;
|
|
18705
|
+
if (error51.timedOut) throw error51;
|
|
18706
|
+
throw new DeploymentError(
|
|
18707
|
+
"CLONE_FAILED",
|
|
18708
|
+
`Could not fetch commit ${commitSha.slice(0, 7)}. It may have been removed from the repository.`,
|
|
18709
|
+
{ cause: error51 }
|
|
18710
|
+
);
|
|
18711
|
+
}
|
|
18712
|
+
await git2([
|
|
18713
|
+
"-C",
|
|
18714
|
+
checkout,
|
|
18715
|
+
"checkout",
|
|
18716
|
+
"--quiet",
|
|
18717
|
+
"--detach",
|
|
18718
|
+
"FETCH_HEAD"
|
|
18719
|
+
]);
|
|
18720
|
+
}
|
|
18498
18721
|
async build(input, observer, cache) {
|
|
18499
18722
|
const workspace = await this.dependencies.createWorkspace();
|
|
18500
|
-
const checkout =
|
|
18501
|
-
const pmCacheDir =
|
|
18723
|
+
const checkout = join18(workspace, "checkout");
|
|
18724
|
+
const pmCacheDir = join18(workspace, ".pm-store");
|
|
18502
18725
|
let cacheSeq = 0;
|
|
18503
18726
|
const commandAvailable = this.dependencies.commandAvailable ?? hasCommand;
|
|
18504
18727
|
const compressor = "zstd";
|
|
@@ -18507,7 +18730,7 @@ var LocalBuildExecutor = class {
|
|
|
18507
18730
|
transport: cache,
|
|
18508
18731
|
fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
|
|
18509
18732
|
runCommand: this.dependencies.runCommand,
|
|
18510
|
-
tmpFile: (suffix) =>
|
|
18733
|
+
tmpFile: (suffix) => join18(workspace, `cache-${String(cacheSeq++)}${suffix}`),
|
|
18511
18734
|
// One latch for the whole build so a disabled cache warns once.
|
|
18512
18735
|
report: { unavailable: false }
|
|
18513
18736
|
} : null;
|
|
@@ -18520,27 +18743,37 @@ var LocalBuildExecutor = class {
|
|
|
18520
18743
|
message: `Build cache disabled: ${compressor} is not installed. Install it to reuse dependencies between deploys.`
|
|
18521
18744
|
});
|
|
18522
18745
|
}
|
|
18523
|
-
|
|
18524
|
-
|
|
18525
|
-
|
|
18526
|
-
|
|
18527
|
-
|
|
18528
|
-
|
|
18529
|
-
|
|
18530
|
-
|
|
18531
|
-
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
|
|
18535
|
-
|
|
18536
|
-
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
|
|
18541
|
-
|
|
18542
|
-
|
|
18543
|
-
|
|
18746
|
+
if (input.commitSha !== void 0) {
|
|
18747
|
+
await this.checkoutPinnedCommit(
|
|
18748
|
+
input.commitSha,
|
|
18749
|
+
input.repositoryUrl,
|
|
18750
|
+
workspace,
|
|
18751
|
+
checkout,
|
|
18752
|
+
observer
|
|
18753
|
+
);
|
|
18754
|
+
} else {
|
|
18755
|
+
await this.dependencies.runCommand(
|
|
18756
|
+
{
|
|
18757
|
+
command: "git",
|
|
18758
|
+
args: [
|
|
18759
|
+
"clone",
|
|
18760
|
+
"--depth",
|
|
18761
|
+
"1",
|
|
18762
|
+
"--branch",
|
|
18763
|
+
input.branch,
|
|
18764
|
+
"--single-branch",
|
|
18765
|
+
"--",
|
|
18766
|
+
input.repositoryUrl,
|
|
18767
|
+
checkout
|
|
18768
|
+
],
|
|
18769
|
+
cwd: workspace,
|
|
18770
|
+
timeoutMs: 12e4,
|
|
18771
|
+
stage: "CLONING",
|
|
18772
|
+
errorCode: "CLONE_FAILED"
|
|
18773
|
+
},
|
|
18774
|
+
observer
|
|
18775
|
+
);
|
|
18776
|
+
}
|
|
18544
18777
|
const revision = await this.dependencies.runCommand(
|
|
18545
18778
|
{
|
|
18546
18779
|
command: "git",
|
|
@@ -18559,6 +18792,12 @@ var LocalBuildExecutor = class {
|
|
|
18559
18792
|
"Git returned an invalid commit SHA."
|
|
18560
18793
|
);
|
|
18561
18794
|
}
|
|
18795
|
+
if (input.commitSha !== void 0 && gitCommitSha !== input.commitSha) {
|
|
18796
|
+
throw new DeploymentError(
|
|
18797
|
+
"CLONE_FAILED",
|
|
18798
|
+
`Checked out ${gitCommitSha.slice(0, 7)} instead of the pinned commit ${input.commitSha.slice(0, 7)}.`
|
|
18799
|
+
);
|
|
18800
|
+
}
|
|
18562
18801
|
const reportedSha = gitCommitSha.length === 40 ? gitCommitSha : null;
|
|
18563
18802
|
if (!reportedSha)
|
|
18564
18803
|
await info2(
|
|
@@ -18586,6 +18825,7 @@ var LocalBuildExecutor = class {
|
|
|
18586
18825
|
checkout,
|
|
18587
18826
|
input.rootDirectory
|
|
18588
18827
|
);
|
|
18828
|
+
await assertAppRootInsideCheckout(checkout, appRoot);
|
|
18589
18829
|
const stacks = await import("./stack-PDV3NPWC.js");
|
|
18590
18830
|
const stack = await (this.dependencies.detectStack ?? stacks.detectStack)(
|
|
18591
18831
|
appRoot
|
|
@@ -18698,7 +18938,7 @@ var LocalBuildExecutor = class {
|
|
|
18698
18938
|
}
|
|
18699
18939
|
}
|
|
18700
18940
|
await notifyStatus(observer, "BUILDING");
|
|
18701
|
-
const buildCacheDir = resolved.preset.cachePath ?
|
|
18941
|
+
const buildCacheDir = resolved.preset.cachePath ? join18(appRoot, resolved.preset.cachePath) : null;
|
|
18702
18942
|
const buildCacheKey = sanitizeCacheKey(input.branch);
|
|
18703
18943
|
if (cachePorts && buildCacheDir) {
|
|
18704
18944
|
await tryRestore(
|
|
@@ -18722,13 +18962,13 @@ var LocalBuildExecutor = class {
|
|
|
18722
18962
|
entries: fingerprintEntries,
|
|
18723
18963
|
nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown"
|
|
18724
18964
|
});
|
|
18725
|
-
const pristine =
|
|
18965
|
+
const pristine = join18(workspace, "pristine");
|
|
18726
18966
|
const fork = this.dependencies.forkWorkspace ?? defaultFork;
|
|
18727
18967
|
await fork(checkout, pristine);
|
|
18728
18968
|
let forks = 0;
|
|
18729
18969
|
const resolverConcurrency = this.dependencies.resolverConcurrency ?? 1;
|
|
18730
18970
|
const forkWorkspace = async (label2) => {
|
|
18731
|
-
const target2 =
|
|
18971
|
+
const target2 = join18(
|
|
18732
18972
|
workspace,
|
|
18733
18973
|
"forks",
|
|
18734
18974
|
`${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
|
|
@@ -18973,6 +19213,14 @@ var NaroError = class extends Error {
|
|
|
18973
19213
|
if (options?.details !== void 0) this.details = options.details;
|
|
18974
19214
|
}
|
|
18975
19215
|
};
|
|
19216
|
+
var PROMOTE_TARGET_UNSUPPORTED_MESSAGE = "This control plane cannot promote to an environment yet. Deploy the latest web first, then try again.";
|
|
19217
|
+
function promoteTargetUnsupported(cause) {
|
|
19218
|
+
return new NaroError(
|
|
19219
|
+
"PROMOTE_TARGET_UNSUPPORTED",
|
|
19220
|
+
PROMOTE_TARGET_UNSUPPORTED_MESSAGE,
|
|
19221
|
+
cause !== void 0 ? { cause } : void 0
|
|
19222
|
+
);
|
|
19223
|
+
}
|
|
18976
19224
|
function isNaroError(error51) {
|
|
18977
19225
|
return error51 instanceof NaroError || error51 instanceof Error && error51.name === "NaroError" && typeof error51.code === "string";
|
|
18978
19226
|
}
|
|
@@ -19016,12 +19264,12 @@ async function errorFromResponse(response, context) {
|
|
|
19016
19264
|
// ../../packages/sdk/src/session.ts
|
|
19017
19265
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
19018
19266
|
import { homedir } from "os";
|
|
19019
|
-
import { join as
|
|
19267
|
+
import { join as join19 } from "path";
|
|
19020
19268
|
function credentialsDir(home = homedir()) {
|
|
19021
|
-
return
|
|
19269
|
+
return join19(home, ".naro");
|
|
19022
19270
|
}
|
|
19023
19271
|
function credentialsFile(home = homedir()) {
|
|
19024
|
-
return
|
|
19272
|
+
return join19(credentialsDir(home), "credentials.json");
|
|
19025
19273
|
}
|
|
19026
19274
|
function saveSession(session, home = homedir()) {
|
|
19027
19275
|
mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
|
|
@@ -19078,7 +19326,7 @@ var STYLES = `
|
|
|
19078
19326
|
*{box-sizing:border-box}
|
|
19079
19327
|
:root{
|
|
19080
19328
|
--bg:#000;--surface:#0a0a0a;--surface-2:#111;--border:#232323;
|
|
19081
|
-
--text:#ededed;--muted:#a1a1a1;--faint:#
|
|
19329
|
+
--text:#ededed;--muted:#a1a1a1;--faint:#808080;
|
|
19082
19330
|
--sans:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
|
19083
19331
|
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
19084
19332
|
}
|
|
@@ -19542,6 +19790,8 @@ function deploymentInfo(row) {
|
|
|
19542
19790
|
errorMessage: row.error_message,
|
|
19543
19791
|
gitCommitSha: row.git_commit_sha,
|
|
19544
19792
|
gitCommitMessage: row.git_commit_message,
|
|
19793
|
+
pinnedCommitSha: row.pinned_commit_sha ?? null,
|
|
19794
|
+
rebuiltFromDeploymentId: row.rebuilt_from_deployment_id ?? null,
|
|
19545
19795
|
createdAt: row.created_at,
|
|
19546
19796
|
startedAt: row.started_at,
|
|
19547
19797
|
finishedAt: row.finished_at
|
|
@@ -19793,16 +20043,65 @@ var DeploymentsApi = class {
|
|
|
19793
20043
|
return { current, target: production.slice(1).find(differs) ?? null };
|
|
19794
20044
|
}
|
|
19795
20045
|
/**
|
|
19796
|
-
*
|
|
19797
|
-
*
|
|
19798
|
-
* (older production build → production).
|
|
20046
|
+
* Promotes an existing READY deployment: with no `target` (or
|
|
20047
|
+
* `production`) it goes to production — `naro promote` (preview →
|
|
20048
|
+
* production) and `naro rollback` (older production build → production).
|
|
20049
|
+
* A deployment from an environment is rebuilt from its commit with
|
|
20050
|
+
* production's variables (`mode: "rebuild"`); any other is published as
|
|
20051
|
+
* stored (`mode: "reupload"`). With an environment `target` the control
|
|
20052
|
+
* plane likewise rebuilds the deployment's commit with that environment's
|
|
20053
|
+
* variables or publishes the stored build.
|
|
20054
|
+
*
|
|
20055
|
+
* No `target` sends no body, exactly as clients before targets did. A
|
|
20056
|
+
* control plane from before targets ignores `target` and promotes to
|
|
20057
|
+
* production, so an environment target is first checked with
|
|
20058
|
+
* `promoteCheck`: a 405 there (no such route) stops with
|
|
20059
|
+
* `PROMOTE_TARGET_UNSUPPORTED` before anything is promoted. The target is
|
|
20060
|
+
* trimmed once, as the control plane trims it, so `" production"` is
|
|
20061
|
+
* production and asks no preview.
|
|
20062
|
+
*/
|
|
20063
|
+
async promote(projectId, deploymentId, options = {}) {
|
|
20064
|
+
const target2 = options.target?.trim();
|
|
20065
|
+
if (target2 !== void 0 && target2 !== "production") {
|
|
20066
|
+
try {
|
|
20067
|
+
await this.promoteCheck(projectId, deploymentId, target2);
|
|
20068
|
+
} catch (error51) {
|
|
20069
|
+
if (isNaroError(error51) && error51.status === 405) {
|
|
20070
|
+
throw promoteTargetUnsupported(error51);
|
|
20071
|
+
}
|
|
20072
|
+
throw error51;
|
|
20073
|
+
}
|
|
20074
|
+
}
|
|
20075
|
+
const body = await this.client.request(this.promotePath(projectId, deploymentId), {
|
|
20076
|
+
method: "POST",
|
|
20077
|
+
...target2 !== void 0 ? { body: { target: target2 } } : {},
|
|
20078
|
+
context: "Promote deployment"
|
|
20079
|
+
});
|
|
20080
|
+
return {
|
|
20081
|
+
...deploymentInfo(body.deployment),
|
|
20082
|
+
mode: body.mode ?? "reupload"
|
|
20083
|
+
};
|
|
20084
|
+
}
|
|
20085
|
+
/**
|
|
20086
|
+
* What promoting to `target` would do, changing nothing: how it gets its
|
|
20087
|
+
* build, the migrations the target's database has not applied (null when
|
|
20088
|
+
* they cannot be compared) and the warnings to show first. Answers 405 from
|
|
20089
|
+
* a control plane that predates promote targets.
|
|
19799
20090
|
*/
|
|
19800
|
-
async
|
|
20091
|
+
async promoteCheck(projectId, deploymentId, target2) {
|
|
20092
|
+
const query = target2 !== void 0 ? `?target=${encodeURIComponent(target2)}` : "";
|
|
19801
20093
|
const body = await this.client.request(
|
|
19802
|
-
|
|
19803
|
-
{
|
|
20094
|
+
`${this.promotePath(projectId, deploymentId)}${query}`,
|
|
20095
|
+
{ context: "Check promotion" }
|
|
19804
20096
|
);
|
|
19805
|
-
return
|
|
20097
|
+
return {
|
|
20098
|
+
mode: body.mode ?? "reupload",
|
|
20099
|
+
pendingMigrations: body.pendingMigrations ?? null,
|
|
20100
|
+
warnings: body.warnings ?? []
|
|
20101
|
+
};
|
|
20102
|
+
}
|
|
20103
|
+
promotePath(projectId, deploymentId) {
|
|
20104
|
+
return `/api/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}/promote`;
|
|
19806
20105
|
}
|
|
19807
20106
|
/**
|
|
19808
20107
|
* Stops an in-flight build (QUEUED through BUILDING) and marks the row
|
|
@@ -19848,6 +20147,8 @@ var DeploymentsApi = class {
|
|
|
19848
20147
|
}
|
|
19849
20148
|
}
|
|
19850
20149
|
};
|
|
20150
|
+
var STATIC_ASSETS_BINDING = "ASSETS";
|
|
20151
|
+
var MANAGED_DATABASE_BINDING = "DB";
|
|
19851
20152
|
var EnvApi = class {
|
|
19852
20153
|
constructor(client) {
|
|
19853
20154
|
this.client = client;
|
|
@@ -19877,6 +20178,37 @@ var EnvApi = class {
|
|
|
19877
20178
|
}
|
|
19878
20179
|
);
|
|
19879
20180
|
}
|
|
20181
|
+
/**
|
|
20182
|
+
* The names the env route refuses as a key — `409 VALIDATION_FAILED`
|
|
20183
|
+
* `<KEY> is already used by a storage binding.` — read the way it reads
|
|
20184
|
+
* them: `ASSETS` (the static files), every binding of the project's storage
|
|
20185
|
+
* and, on a project using the managed database (`database_provider` is
|
|
20186
|
+
* `d1`), `DB`, which may not be a storage row yet. For a caller that sends
|
|
20187
|
+
* many entries in several requests (`naro env import`) and has to refuse
|
|
20188
|
+
* before the first one is saved. A control plane whose project row has no
|
|
20189
|
+
* `database_provider` reads as having no managed database, so `DB` is only
|
|
20190
|
+
* left out there.
|
|
20191
|
+
*/
|
|
20192
|
+
async takenBindingNames(projectId) {
|
|
20193
|
+
const base2 = `/api/projects/${encodeURIComponent(projectId)}`;
|
|
20194
|
+
const [storage, project] = await Promise.all([
|
|
20195
|
+
this.client.request(
|
|
20196
|
+
`${base2}/storage`,
|
|
20197
|
+
{ context: "List storage" }
|
|
20198
|
+
),
|
|
20199
|
+
this.client.request(
|
|
20200
|
+
base2,
|
|
20201
|
+
{ context: "Read project" }
|
|
20202
|
+
)
|
|
20203
|
+
]);
|
|
20204
|
+
const names = /* @__PURE__ */ new Set([
|
|
20205
|
+
STATIC_ASSETS_BINDING,
|
|
20206
|
+
...storage.storage.map((row) => row.bindingName)
|
|
20207
|
+
]);
|
|
20208
|
+
if (project.project.database_provider === "d1")
|
|
20209
|
+
names.add(MANAGED_DATABASE_BINDING);
|
|
20210
|
+
return [...names];
|
|
20211
|
+
}
|
|
19880
20212
|
async removeById(projectId, envVarId) {
|
|
19881
20213
|
await this.client.request(
|
|
19882
20214
|
`/api/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(envVarId)}`,
|
|
@@ -19887,13 +20219,28 @@ var EnvApi = class {
|
|
|
19887
20219
|
* Removes a key's rows: the one `target` names, or, with no target, the
|
|
19888
20220
|
* `production`, `preview` and `all` rows. An environment's value goes only
|
|
19889
20221
|
* when its slug is the target, so removing a key everywhere else does not
|
|
19890
|
-
* take a QA or staging value with it.
|
|
20222
|
+
* take a QA or staging value with it. A key only environments hold fails
|
|
20223
|
+
* ENV_NOT_FOUND naming them (`details.environments`), not as if there were
|
|
20224
|
+
* none.
|
|
19891
20225
|
*/
|
|
19892
20226
|
async remove(projectId, key, target2) {
|
|
19893
20227
|
const rows = await this.list(projectId);
|
|
19894
20228
|
const matches = rows.filter(
|
|
19895
20229
|
(row) => row.key === key && (target2 === void 0 ? isReservedEnvTarget(row.target) : row.target === target2)
|
|
19896
20230
|
);
|
|
20231
|
+
if (matches.length === 0 && target2 === void 0) {
|
|
20232
|
+
const environments = [
|
|
20233
|
+
...new Set(
|
|
20234
|
+
rows.filter((row) => row.key === key).map((row) => row.target).filter((slug) => !isReservedEnvTarget(slug))
|
|
20235
|
+
)
|
|
20236
|
+
].toSorted();
|
|
20237
|
+
if (environments.length > 0)
|
|
20238
|
+
throw new NaroError(
|
|
20239
|
+
"ENV_NOT_FOUND",
|
|
20240
|
+
`No ${key} variable found outside named environments. Pass the environment to remove it: ${environments.join(", ")}.`,
|
|
20241
|
+
{ details: { key, environments } }
|
|
20242
|
+
);
|
|
20243
|
+
}
|
|
19897
20244
|
if (matches.length === 0) {
|
|
19898
20245
|
throw new NaroError(
|
|
19899
20246
|
"ENV_NOT_FOUND",
|
|
@@ -20509,6 +20856,7 @@ function storageInfo(body) {
|
|
|
20509
20856
|
return {
|
|
20510
20857
|
enabled: body.enabled,
|
|
20511
20858
|
bucket: body.bucket ?? null,
|
|
20859
|
+
disabledAt: body.disabledAt ?? null,
|
|
20512
20860
|
limits: {
|
|
20513
20861
|
bytes: body.limits.bytes,
|
|
20514
20862
|
objectBytes: body.limits.objectBytes,
|
|
@@ -20520,6 +20868,18 @@ function storageInfo(body) {
|
|
|
20520
20868
|
buckets: (body.buckets ?? []).map(storageBucket)
|
|
20521
20869
|
};
|
|
20522
20870
|
}
|
|
20871
|
+
function storageRunFailure(error51) {
|
|
20872
|
+
const details = isNaroError(error51) ? error51.details : void 0;
|
|
20873
|
+
if (isNaroError(error51) && error51.code === "BACKEND_PROVISION_FAILED" && isRecord(details?.storage) && isRecord(details.backend))
|
|
20874
|
+
return new NaroError(error51.code, error51.message, {
|
|
20875
|
+
...error51.status !== void 0 ? { status: error51.status } : {},
|
|
20876
|
+
details: {
|
|
20877
|
+
...details,
|
|
20878
|
+
backend: backendInfo(details.backend)
|
|
20879
|
+
}
|
|
20880
|
+
});
|
|
20881
|
+
return error51;
|
|
20882
|
+
}
|
|
20523
20883
|
function bucketFields(fields, into = {}) {
|
|
20524
20884
|
for (const key of ["public", "fileSizeLimit", "allowedMimeTypes"])
|
|
20525
20885
|
if (fields[key] !== void 0) into[key] = fields[key];
|
|
@@ -20948,16 +21308,45 @@ var StorageApi = class {
|
|
|
20948
21308
|
}
|
|
20949
21309
|
);
|
|
20950
21310
|
} catch (error51) {
|
|
20951
|
-
|
|
20952
|
-
|
|
20953
|
-
|
|
20954
|
-
|
|
20955
|
-
|
|
20956
|
-
|
|
20957
|
-
|
|
20958
|
-
|
|
20959
|
-
|
|
20960
|
-
|
|
21311
|
+
throw storageRunFailure(error51);
|
|
21312
|
+
}
|
|
21313
|
+
return storageInfo(body.storage);
|
|
21314
|
+
}
|
|
21315
|
+
/**
|
|
21316
|
+
* Turns Storage off and keeps its files (Storage 3-1k): the Naro worker is
|
|
21317
|
+
* uploaded again without the bucket, so the app's Storage calls and file
|
|
21318
|
+
* URLs answer `404 STORAGE_NOT_ENABLED` until `enable` binds the same
|
|
21319
|
+
* bucket again. The bucket, its files and their storage cost stay; while
|
|
21320
|
+
* it is off, bucket writes and the object routes answer `409
|
|
21321
|
+
* STORAGE_OFF`, and reads go on. Safe to repeat: Storage never on, or
|
|
21322
|
+
* already off, comes back as it is, and a turn-off that stopped is
|
|
21323
|
+
* finished. Two minutes, like `enable`.
|
|
21324
|
+
*
|
|
21325
|
+
* Refusals, in the order they are checked: `BACKEND_DISABLED`,
|
|
21326
|
+
* `FORBIDDEN` (`storage.manage`), `DATABASE_NOT_ENABLED`,
|
|
21327
|
+
* `BACKEND_NOT_READY` (no backend), `BACKEND_OFF`, `BACKEND_BUSY` (another
|
|
21328
|
+
* change holds the backend — or turning Storage on again took this
|
|
21329
|
+
* turn-off over, and Storage is on), `STORAGE_DISABLE_FAILED` (502: the
|
|
21330
|
+
* turn-off could not be recorded; nothing changed), and
|
|
21331
|
+
* `BACKEND_PROVISION_FAILED` (502) when it is
|
|
21332
|
+
* recorded but the worker's upload — or a step after it — stopped:
|
|
21333
|
+
* `details` holds `{ storage: { applied: false, enabled: false }, backend
|
|
21334
|
+
* }`, and `disable` again finishes. Never `STORAGE_DISABLED`: the control
|
|
21335
|
+
* plane's Storage switch does not hold a removal back.
|
|
21336
|
+
*/
|
|
21337
|
+
async disable(projectId) {
|
|
21338
|
+
let body;
|
|
21339
|
+
try {
|
|
21340
|
+
body = await this.client.request(
|
|
21341
|
+
this.path(projectId),
|
|
21342
|
+
{
|
|
21343
|
+
method: "DELETE",
|
|
21344
|
+
context: "Turn off Storage",
|
|
21345
|
+
timeoutMs: PROVISION_TIMEOUT_MS
|
|
21346
|
+
}
|
|
21347
|
+
);
|
|
21348
|
+
} catch (error51) {
|
|
21349
|
+
throw storageRunFailure(error51);
|
|
20961
21350
|
}
|
|
20962
21351
|
return storageInfo(body.storage);
|
|
20963
21352
|
}
|
|
@@ -21167,40 +21556,92 @@ function summarizeStatement(statement2) {
|
|
|
21167
21556
|
|
|
21168
21557
|
// ../../packages/sdk/src/dotenv.ts
|
|
21169
21558
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
21170
|
-
import { join as
|
|
21559
|
+
import { join as join20 } from "path";
|
|
21560
|
+
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/dgm;
|
|
21561
|
+
var QUOTES = /* @__PURE__ */ new Set(["'", '"', "`"]);
|
|
21562
|
+
function parseDotenvDetailed(text2) {
|
|
21563
|
+
const vars = {};
|
|
21564
|
+
const unclosedQuotes = [];
|
|
21565
|
+
const lines = text2.replace(/\r\n?/g, "\n");
|
|
21566
|
+
let line = 1;
|
|
21567
|
+
let counted = 0;
|
|
21568
|
+
for (const match of lines.matchAll(LINE)) {
|
|
21569
|
+
const key = match[1];
|
|
21570
|
+
const raw = match[2] ?? "";
|
|
21571
|
+
let value = raw.trim();
|
|
21572
|
+
const maybeQuote = value[0];
|
|
21573
|
+
if (maybeQuote !== void 0 && QUOTES.has(maybeQuote) && !value.includes(maybeQuote, 1)) {
|
|
21574
|
+
const at = (match.indices?.[2]?.[0] ?? match.index) + (raw.length - raw.trimStart().length);
|
|
21575
|
+
for (; counted < at; counted += 1) if (lines[counted] === "\n") line += 1;
|
|
21576
|
+
unclosedQuotes.push(line);
|
|
21577
|
+
}
|
|
21578
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
|
21579
|
+
if (maybeQuote === '"')
|
|
21580
|
+
value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
|
|
21581
|
+
vars[key] = value;
|
|
21582
|
+
}
|
|
21583
|
+
return { vars, unclosedQuotes };
|
|
21584
|
+
}
|
|
21171
21585
|
function parseDotenv(text2) {
|
|
21172
|
-
|
|
21173
|
-
for (const line of text2.split(/\r?\n/)) {
|
|
21174
|
-
const trimmed = line.trim().replace(/^export\s+(?=[^=\s])/, "");
|
|
21175
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
21176
|
-
const eq = trimmed.indexOf("=");
|
|
21177
|
-
if (eq <= 0) continue;
|
|
21178
|
-
const key = trimmed.slice(0, eq).trim();
|
|
21179
|
-
let value = trimmed.slice(eq + 1).trim();
|
|
21180
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
|
|
21181
|
-
value = value.slice(1, -1);
|
|
21182
|
-
out[key] = value;
|
|
21183
|
-
}
|
|
21184
|
-
return out;
|
|
21586
|
+
return parseDotenvDetailed(text2).vars;
|
|
21185
21587
|
}
|
|
21186
21588
|
function readProjectEnv(dir) {
|
|
21187
21589
|
for (const name of [".env.local", ".env.production", ".env"]) {
|
|
21188
|
-
const path =
|
|
21590
|
+
const path = join20(dir, name);
|
|
21189
21591
|
if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
|
|
21190
21592
|
}
|
|
21191
21593
|
return {};
|
|
21192
21594
|
}
|
|
21595
|
+
var PLAIN = /^[^\s"'#$`\\]*$/;
|
|
21596
|
+
function bareReadsBack(value) {
|
|
21597
|
+
return !/[#\r\n\u2028\u2029]/.test(value) && value.trim() === value && !QUOTES.has(value[0] ?? "");
|
|
21598
|
+
}
|
|
21599
|
+
function dotenvValue(value) {
|
|
21600
|
+
if (PLAIN.test(value)) return value;
|
|
21601
|
+
const endsInBackslash = value.endsWith("\\");
|
|
21602
|
+
if (!value.includes("\r") && !endsInBackslash) {
|
|
21603
|
+
if (!value.includes("'")) return `'${value}'`;
|
|
21604
|
+
if (!value.includes("`")) return `\`${value}\``;
|
|
21605
|
+
}
|
|
21606
|
+
if (!value.includes('"') && !/\\[nr]/.test(value) && !endsInBackslash)
|
|
21607
|
+
return `"${value.replaceAll("\r", "\\r")}"`;
|
|
21608
|
+
if (bareReadsBack(value)) return value;
|
|
21609
|
+
return void 0;
|
|
21610
|
+
}
|
|
21611
|
+
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.";
|
|
21193
21612
|
function serializeDotenv(vars) {
|
|
21194
|
-
const
|
|
21195
|
-
|
|
21196
|
-
|
|
21197
|
-
|
|
21613
|
+
const lines = [];
|
|
21614
|
+
const allQuotes = [];
|
|
21615
|
+
const other = [];
|
|
21616
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
21617
|
+
const written = dotenvValue(value);
|
|
21618
|
+
if (written !== void 0) lines.push(`${key}=${written}`);
|
|
21619
|
+
else if (value.includes("'") && value.includes('"') && value.includes("`"))
|
|
21620
|
+
allQuotes.push(key);
|
|
21621
|
+
else other.push(key);
|
|
21622
|
+
}
|
|
21623
|
+
const problems2 = [];
|
|
21624
|
+
if (allQuotes.length > 0)
|
|
21625
|
+
problems2.push(
|
|
21626
|
+
`Cannot write ${allQuotes.join(", ")} to a dotenv file: their values hold ', " and \` together.`
|
|
21627
|
+
);
|
|
21628
|
+
if (other.length > 0)
|
|
21629
|
+
problems2.push(
|
|
21630
|
+
`Cannot write ${other.join(", ")} to a dotenv file: dotenv would not read their values back unchanged.`
|
|
21631
|
+
);
|
|
21632
|
+
if (problems2.length > 0)
|
|
21633
|
+
throw new NaroError(
|
|
21634
|
+
"VALIDATION_FAILED",
|
|
21635
|
+
`${problems2.join(" ")} ${UNWRITABLE_HINT}`,
|
|
21636
|
+
{ details: { keys: [...allQuotes, ...other] } }
|
|
21637
|
+
);
|
|
21638
|
+
return `${lines.join("\n")}
|
|
21198
21639
|
`;
|
|
21199
21640
|
}
|
|
21200
21641
|
|
|
21201
21642
|
// ../../packages/sdk/src/env-scan.ts
|
|
21202
21643
|
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
|
|
21203
|
-
import { join as
|
|
21644
|
+
import { join as join21 } from "path";
|
|
21204
21645
|
var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
|
|
21205
21646
|
".ts",
|
|
21206
21647
|
".tsx",
|
|
@@ -21255,7 +21696,7 @@ function collect(dir, out) {
|
|
|
21255
21696
|
for (const entry of entries) {
|
|
21256
21697
|
if (out.length >= MAX_FILES) return;
|
|
21257
21698
|
if (entry.isSymbolicLink()) continue;
|
|
21258
|
-
const path =
|
|
21699
|
+
const path = join21(dir, entry.name);
|
|
21259
21700
|
if (entry.isDirectory()) {
|
|
21260
21701
|
if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
|
|
21261
21702
|
continue;
|
|
@@ -21287,7 +21728,7 @@ function scanEnvReferences(dir) {
|
|
|
21287
21728
|
}
|
|
21288
21729
|
}
|
|
21289
21730
|
for (const name of EXAMPLE_FILES) {
|
|
21290
|
-
const path =
|
|
21731
|
+
const path = join21(dir, name);
|
|
21291
21732
|
if (!existsSync2(path)) continue;
|
|
21292
21733
|
let parsed;
|
|
21293
21734
|
try {
|
|
@@ -21305,11 +21746,11 @@ function scanEnvReferences(dir) {
|
|
|
21305
21746
|
|
|
21306
21747
|
// ../../packages/sdk/src/framework.ts
|
|
21307
21748
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
21308
|
-
import { join as
|
|
21749
|
+
import { join as join22 } from "path";
|
|
21309
21750
|
function readPackageJson2(dir) {
|
|
21310
21751
|
try {
|
|
21311
21752
|
return JSON.parse(
|
|
21312
|
-
readFileSync4(
|
|
21753
|
+
readFileSync4(join22(dir, "package.json"), "utf8")
|
|
21313
21754
|
);
|
|
21314
21755
|
} catch {
|
|
21315
21756
|
return null;
|
|
@@ -21320,7 +21761,7 @@ function hasDependency(pkg, name) {
|
|
|
21320
21761
|
}
|
|
21321
21762
|
function hasConfig(dir, base2, evidence) {
|
|
21322
21763
|
for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
|
|
21323
|
-
if (existsSync3(
|
|
21764
|
+
if (existsSync3(join22(dir, `${base2}.${ext}`))) {
|
|
21324
21765
|
evidence.push(`${base2}.${ext}`);
|
|
21325
21766
|
return true;
|
|
21326
21767
|
}
|
|
@@ -21428,8 +21869,8 @@ function detectFramework2(dir) {
|
|
|
21428
21869
|
if (!byDependency && !byConfig) continue;
|
|
21429
21870
|
if (byDependency) evidence.push(`package.json: ${detector.dependency}`);
|
|
21430
21871
|
if (detector.framework === "nextjs") {
|
|
21431
|
-
if (existsSync3(
|
|
21432
|
-
if (existsSync3(
|
|
21872
|
+
if (existsSync3(join22(dir, "app"))) evidence.push("app/");
|
|
21873
|
+
if (existsSync3(join22(dir, "pages"))) evidence.push("pages/");
|
|
21433
21874
|
}
|
|
21434
21875
|
return {
|
|
21435
21876
|
framework: detector.framework,
|
|
@@ -21440,7 +21881,7 @@ function detectFramework2(dir) {
|
|
|
21440
21881
|
deployable: true
|
|
21441
21882
|
};
|
|
21442
21883
|
}
|
|
21443
|
-
if (!pkg && existsSync3(
|
|
21884
|
+
if (!pkg && existsSync3(join22(dir, "index.html"))) {
|
|
21444
21885
|
evidence.push("index.html (no package.json)");
|
|
21445
21886
|
return {
|
|
21446
21887
|
framework: "static",
|
|
@@ -21461,8 +21902,26 @@ function detectFramework2(dir) {
|
|
|
21461
21902
|
};
|
|
21462
21903
|
}
|
|
21463
21904
|
|
|
21905
|
+
// ../../packages/data-core/src/unicode-controls.ts
|
|
21906
|
+
function hasControlCharacter(text2) {
|
|
21907
|
+
for (let i = 0; i < text2.length; i += 1) {
|
|
21908
|
+
const code = text2.charCodeAt(i);
|
|
21909
|
+
if (code <= 31 || code >= 127 && code <= 159) return true;
|
|
21910
|
+
}
|
|
21911
|
+
return false;
|
|
21912
|
+
}
|
|
21913
|
+
function hasBidiControl(text2) {
|
|
21914
|
+
for (let i = 0; i < text2.length; i += 1) {
|
|
21915
|
+
const code = text2.charCodeAt(i);
|
|
21916
|
+
if (code === 1564 || code === 8206 || code === 8207 || code >= 8234 && code <= 8238 || code >= 8294 && code <= 8297)
|
|
21917
|
+
return true;
|
|
21918
|
+
}
|
|
21919
|
+
return false;
|
|
21920
|
+
}
|
|
21921
|
+
|
|
21464
21922
|
// ../../packages/data-core/src/storage-path.ts
|
|
21465
21923
|
var MAX_SINGLE_UPLOAD_BYTES = 52428800;
|
|
21924
|
+
var MAX_OBJECT_NAME_BYTES = 900;
|
|
21466
21925
|
var STORAGE_RESERVED_WORDS = [
|
|
21467
21926
|
"authenticated",
|
|
21468
21927
|
"copy",
|
|
@@ -21478,6 +21937,32 @@ var RESERVED = new Set(STORAGE_RESERVED_WORDS);
|
|
|
21478
21937
|
var ENCODER = new TextEncoder();
|
|
21479
21938
|
var LONE_SURROGATE2 = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
|
|
21480
21939
|
var LONE_SURROGATES = new RegExp(LONE_SURROGATE2.source, "g");
|
|
21940
|
+
function hasLoneSurrogate(text2) {
|
|
21941
|
+
return LONE_SURROGATE2.test(text2);
|
|
21942
|
+
}
|
|
21943
|
+
function refuse(reason) {
|
|
21944
|
+
return { ok: false, reason };
|
|
21945
|
+
}
|
|
21946
|
+
function checkWellFormed(name) {
|
|
21947
|
+
if (name === "") return refuse("is empty");
|
|
21948
|
+
if (name.length > MAX_OBJECT_NAME_BYTES || ENCODER.encode(name).length > MAX_OBJECT_NAME_BYTES)
|
|
21949
|
+
return refuse(`is longer than ${String(MAX_OBJECT_NAME_BYTES)} bytes`);
|
|
21950
|
+
if (name.startsWith("/") || name.endsWith("/"))
|
|
21951
|
+
return refuse("starts or ends with /");
|
|
21952
|
+
if (name.includes("//")) return refuse("has an empty segment (//)");
|
|
21953
|
+
for (const segment of name.split("/"))
|
|
21954
|
+
if (segment === "." || segment === "..")
|
|
21955
|
+
return refuse("has a . or .. segment");
|
|
21956
|
+
if (hasControlCharacter(name)) return refuse("has a control character");
|
|
21957
|
+
if (name.includes("\\")) return refuse("has a backslash");
|
|
21958
|
+
if (hasBidiControl(name))
|
|
21959
|
+
return refuse("has a bidirectional control character");
|
|
21960
|
+
return { ok: true, name };
|
|
21961
|
+
}
|
|
21962
|
+
function checkObjectName(name) {
|
|
21963
|
+
if (hasLoneSurrogate(name)) return refuse("is not valid UTF-8");
|
|
21964
|
+
return checkWellFormed(name);
|
|
21965
|
+
}
|
|
21481
21966
|
function objectParent(name) {
|
|
21482
21967
|
const slash = name.lastIndexOf("/");
|
|
21483
21968
|
return slash < 0 ? "" : name.slice(0, slash);
|
|
@@ -21507,6 +21992,7 @@ var STORAGE_OBJECTS = "storage.objects";
|
|
|
21507
21992
|
var STORAGE_OBJECTS_TABLE = "_naro_storage_objects";
|
|
21508
21993
|
var STORAGE_OBJECTS_QUALIFIER = "objects";
|
|
21509
21994
|
var STORAGE_UNAVAILABLE_MESSAGE = "storage.objects is not available on this backend yet. Run `naro backend enable` to update it.";
|
|
21995
|
+
var STORAGE_UNAVAILABLE_REASON = `${STORAGE_OBJECTS} is not available on this backend yet`;
|
|
21510
21996
|
var STORAGE_BUCKETS_TABLE = "_naro_storage_buckets";
|
|
21511
21997
|
var STORAGE_POLICY_COLUMNS = [
|
|
21512
21998
|
"id",
|
|
@@ -21674,7 +22160,8 @@ var CATALOG = {
|
|
|
21674
22160
|
policy,
|
|
21675
22161
|
recursion,
|
|
21676
22162
|
values,
|
|
21677
|
-
bytes
|
|
22163
|
+
bytes,
|
|
22164
|
+
depth
|
|
21678
22165
|
}) => {
|
|
21679
22166
|
if (recursion)
|
|
21680
22167
|
return `Infinite recursion detected in policy for relation ${table}.`;
|
|
@@ -21682,6 +22169,8 @@ var CATALOG = {
|
|
|
21682
22169
|
return `The policies of ${table} bind more than ${values === true ? "50" : String(values)} values in one statement.`;
|
|
21683
22170
|
if (bytes)
|
|
21684
22171
|
return `The policies of ${table} write more than ${bytes === true ? "50000" : String(bytes)} bytes of SQL into one statement.`;
|
|
22172
|
+
if (depth)
|
|
22173
|
+
return `The policies of ${table} nest an expression more than 100 deep into one statement.`;
|
|
21685
22174
|
if (policy !== void 0)
|
|
21686
22175
|
return `Policy ${policy} on ${table} cannot be applied.`;
|
|
21687
22176
|
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.`;
|
|
@@ -21739,7 +22228,7 @@ var CATALOG = {
|
|
|
21739
22228
|
},
|
|
21740
22229
|
OBJECT_TOO_LARGE: {
|
|
21741
22230
|
status: 413,
|
|
21742
|
-
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.`
|
|
22231
|
+
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.`
|
|
21743
22232
|
},
|
|
21744
22233
|
MIME_TYPE_NOT_ALLOWED: {
|
|
21745
22234
|
status: 415,
|
|
@@ -21751,7 +22240,7 @@ var CATALOG = {
|
|
|
21751
22240
|
},
|
|
21752
22241
|
OBJECT_EXISTS: {
|
|
21753
22242
|
status: 409,
|
|
21754
|
-
message: ({ bucket, path }) => `Object ${storageName(path)} already exists in bucket ${storageName(bucket)}. Upload with upsert to replace it
|
|
22243
|
+
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."}`
|
|
21755
22244
|
},
|
|
21756
22245
|
OBJECT_BUSY: {
|
|
21757
22246
|
status: 409,
|
|
@@ -22429,6 +22918,15 @@ function resolvePolicyTable(snapshot, name) {
|
|
|
22429
22918
|
if (folded === REALTIME_MESSAGES) return REALTIME_MESSAGES_TABLE;
|
|
22430
22919
|
return resolveTable(snapshot, name);
|
|
22431
22920
|
}
|
|
22921
|
+
function resolveSubqueryTable(snapshot, name) {
|
|
22922
|
+
if (asciiLower(name) === STORAGE_OBJECTS)
|
|
22923
|
+
return snapshot.storageSubqueries === false ? void 0 : snapshot.storage;
|
|
22924
|
+
return resolveTable(snapshot, name);
|
|
22925
|
+
}
|
|
22926
|
+
function subqueryName(subquery) {
|
|
22927
|
+
if (subquery.alias !== null) return subquery.alias;
|
|
22928
|
+
return asciiLower(subquery.table) === STORAGE_OBJECTS ? STORAGE_OBJECTS_QUALIFIER : subquery.table;
|
|
22929
|
+
}
|
|
22432
22930
|
async function readTableNames(db) {
|
|
22433
22931
|
return tableNamesFromRows(rowsOf(await db.prepare(TABLE_NAMES_SQL).all()));
|
|
22434
22932
|
}
|
|
@@ -22470,6 +22968,14 @@ function utf8ByteLength(text2) {
|
|
|
22470
22968
|
function quoteIdentifier(name) {
|
|
22471
22969
|
return `"${name.replaceAll('"', '""')}"`;
|
|
22472
22970
|
}
|
|
22971
|
+
var SIDE_BY_SIDE = 8;
|
|
22972
|
+
function joinConditions(terms, op) {
|
|
22973
|
+
if (terms.length <= SIDE_BY_SIDE) return `(${terms.join(` ${op} `)})`;
|
|
22974
|
+
const groups = [];
|
|
22975
|
+
for (let at = 0; at < terms.length; at += SIDE_BY_SIDE)
|
|
22976
|
+
groups.push(joinConditions(terms.slice(at, at + SIDE_BY_SIDE), op));
|
|
22977
|
+
return joinConditions(groups, op);
|
|
22978
|
+
}
|
|
22473
22979
|
var Failure = class extends Error {
|
|
22474
22980
|
constructor(error51, reason) {
|
|
22475
22981
|
super(error51.code);
|
|
@@ -22653,6 +23159,12 @@ var VALUE_KINDS = /* @__PURE__ */ new Set([
|
|
|
22653
23159
|
function isValueExpr(expr) {
|
|
22654
23160
|
return VALUE_KINDS.has(expr.kind);
|
|
22655
23161
|
}
|
|
23162
|
+
function comparedValue(value) {
|
|
23163
|
+
let current = value;
|
|
23164
|
+
while (current.kind === "func" && current.name === "coalesce" && current.args.length === 1 && current.args[0] !== void 0)
|
|
23165
|
+
current = current.args[0];
|
|
23166
|
+
return current;
|
|
23167
|
+
}
|
|
22656
23168
|
function isConditionExpr(expr) {
|
|
22657
23169
|
if (!isValueExpr(expr)) return true;
|
|
22658
23170
|
if (expr.kind === "column") return true;
|
|
@@ -22704,7 +23216,6 @@ var SPECIAL_WORDS = /* @__PURE__ */ new Set([
|
|
|
22704
23216
|
"auth"
|
|
22705
23217
|
]);
|
|
22706
23218
|
var TEXT_TYPES = /* @__PURE__ */ new Set(["text", "varchar", "uuid"]);
|
|
22707
|
-
var STORAGE_SUBQUERY_PROBLEM = "A policy cannot read storage.objects in a subquery yet";
|
|
22708
23219
|
var FOLDERNAME_INDEX_PROBLEM = `storage.foldername(name) is available only as (storage.foldername(name))[n] with n from 1 to ${String(MAX_FOLDERNAME_INDEX)}`;
|
|
22709
23220
|
function storageHelperColumnProblem(fn) {
|
|
22710
23221
|
return `storage.${fn}() takes the name column of storage.objects`;
|
|
@@ -23155,8 +23666,12 @@ function atRealtimeMessages(stream) {
|
|
|
23155
23666
|
}
|
|
23156
23667
|
function readTableName(stream) {
|
|
23157
23668
|
const first = stream.peek();
|
|
23158
|
-
if (atStorageObjects(stream))
|
|
23159
|
-
|
|
23669
|
+
if (atStorageObjects(stream)) {
|
|
23670
|
+
stream.next();
|
|
23671
|
+
stream.next();
|
|
23672
|
+
stream.next();
|
|
23673
|
+
return STORAGE_OBJECTS;
|
|
23674
|
+
}
|
|
23160
23675
|
if (atRealtimeMessages(stream))
|
|
23161
23676
|
throw new PolicySyntaxError(REALTIME_SUBQUERY_PROBLEM, first.position);
|
|
23162
23677
|
let token = first;
|
|
@@ -23183,12 +23698,6 @@ function readTableName(stream) {
|
|
|
23183
23698
|
return name;
|
|
23184
23699
|
}
|
|
23185
23700
|
function readPolicyTarget(stream) {
|
|
23186
|
-
if (atStorageObjects(stream)) {
|
|
23187
|
-
stream.next();
|
|
23188
|
-
stream.next();
|
|
23189
|
-
stream.next();
|
|
23190
|
-
return STORAGE_OBJECTS;
|
|
23191
|
-
}
|
|
23192
23701
|
if (atRealtimeMessages(stream)) {
|
|
23193
23702
|
stream.next();
|
|
23194
23703
|
stream.next();
|
|
@@ -23608,9 +24117,10 @@ function printValue(expr) {
|
|
|
23608
24117
|
}
|
|
23609
24118
|
function printSubquery(subquery) {
|
|
23610
24119
|
const select = subquery.select === "*" ? "*" : printValue(subquery.select);
|
|
24120
|
+
const table = asciiLower(subquery.table) === STORAGE_OBJECTS ? STORAGE_OBJECTS : quoteIdent(subquery.table);
|
|
23611
24121
|
const alias = subquery.alias === null ? "" : ` ${quoteIdent(subquery.alias)}`;
|
|
23612
24122
|
const where = subquery.where === null ? "" : ` WHERE ${printExpr(subquery.where)}`;
|
|
23613
|
-
return `SELECT ${select} FROM ${
|
|
24123
|
+
return `SELECT ${select} FROM ${table}${alias}${where}`;
|
|
23614
24124
|
}
|
|
23615
24125
|
function printElement(element) {
|
|
23616
24126
|
if (typeof element === "object") return printValue(element);
|
|
@@ -23963,6 +24473,9 @@ var FILTER_OPERATORS = [
|
|
|
23963
24473
|
"containedBy",
|
|
23964
24474
|
"overlaps"
|
|
23965
24475
|
];
|
|
24476
|
+
function leastFilters(firstColumn) {
|
|
24477
|
+
return firstColumn === void 0 ? [] : [{ operator: "is", column: firstColumn, value: null }];
|
|
24478
|
+
}
|
|
23966
24479
|
function isAuthUid(value) {
|
|
23967
24480
|
return isPlainObject2(value) && Object.keys(value).length === 1 && Object.hasOwn(value, "auth") && value.auth === "uid";
|
|
23968
24481
|
}
|
|
@@ -24576,12 +25089,21 @@ function storedSource(table, names, alias) {
|
|
|
24576
25089
|
function tableLabel(table) {
|
|
24577
25090
|
return table.name === STORAGE_OBJECTS_TABLE ? STORAGE_OBJECTS : table.name;
|
|
24578
25091
|
}
|
|
25092
|
+
function storageReadySql(alias) {
|
|
25093
|
+
return `(${quoteIdentifier(alias)}.${quoteIdentifier("status")} = 'ready')`;
|
|
25094
|
+
}
|
|
25095
|
+
function ownNames(table) {
|
|
25096
|
+
return table.name === STORAGE_OBJECTS_TABLE ? [STORAGE_OBJECTS_QUALIFIER] : [asciiLower(table.name)];
|
|
25097
|
+
}
|
|
24579
25098
|
function missingColumnReason(name, tables) {
|
|
24580
25099
|
return `column ${name} does not exist on ${tables.join(" or ")}`;
|
|
24581
25100
|
}
|
|
24582
25101
|
function missingRelationReason(table) {
|
|
24583
25102
|
return `relation ${table} does not exist (a system table cannot be read either)`;
|
|
24584
25103
|
}
|
|
25104
|
+
function unresolvedSubqueryReason(table) {
|
|
25105
|
+
return asciiLower(table) === STORAGE_OBJECTS ? STORAGE_UNAVAILABLE_REASON : missingRelationReason(table);
|
|
25106
|
+
}
|
|
24585
25107
|
function resolveColumn(env, ref) {
|
|
24586
25108
|
const name = asciiLower(ref.name);
|
|
24587
25109
|
if (ref.qualifier !== null) {
|
|
@@ -24691,10 +25213,10 @@ function storageHelperSql(fn, index, operand) {
|
|
|
24691
25213
|
}
|
|
24692
25214
|
function storageCallSql(env, expr) {
|
|
24693
25215
|
const problem = storageHelperColumnProblem(expr.fn);
|
|
24694
|
-
if (
|
|
25216
|
+
if (!env.scopes.some((scope) => scope.table.name === STORAGE_OBJECTS_TABLE))
|
|
24695
25217
|
return blamed(env, problem);
|
|
24696
25218
|
const { source, column: column2 } = resolveColumn(env, expr.column);
|
|
24697
|
-
if (source
|
|
25219
|
+
if (source.table.name !== STORAGE_OBJECTS_TABLE || column2.name !== "name")
|
|
24698
25220
|
return blamed(env, problem);
|
|
24699
25221
|
const index = expr.fn === "foldername" ? expr.index : null;
|
|
24700
25222
|
if (expr.fn === "foldername" ? index === null || !Number.isInteger(index) || index < 1 || index > MAX_FOLDERNAME_INDEX : expr.index !== null)
|
|
@@ -24708,7 +25230,8 @@ function storageCallSql(env, expr) {
|
|
|
24708
25230
|
})
|
|
24709
25231
|
);
|
|
24710
25232
|
}
|
|
24711
|
-
function operandSide(env,
|
|
25233
|
+
function operandSide(env, value) {
|
|
25234
|
+
const expr = comparedValue(value);
|
|
24712
25235
|
if (expr.kind === "cast") return operandSide(env, expr.operand);
|
|
24713
25236
|
if (expr.kind !== "column" && expr.kind !== "realtime")
|
|
24714
25237
|
return {
|
|
@@ -24769,7 +25292,10 @@ function conditionSql(env, expr) {
|
|
|
24769
25292
|
switch (expr.kind) {
|
|
24770
25293
|
case "and":
|
|
24771
25294
|
case "or":
|
|
24772
|
-
return
|
|
25295
|
+
return joinConditions(
|
|
25296
|
+
expr.args.map((arg) => conditionSql(env, arg)),
|
|
25297
|
+
expr.kind === "and" ? "AND" : "OR"
|
|
25298
|
+
);
|
|
24773
25299
|
case "not":
|
|
24774
25300
|
return `(NOT ${conditionSql(env, expr.arg)})`;
|
|
24775
25301
|
default:
|
|
@@ -24852,8 +25378,8 @@ function conditionSql(env, expr) {
|
|
|
24852
25378
|
}
|
|
24853
25379
|
}
|
|
24854
25380
|
function subquerySql(env, subquery, select) {
|
|
24855
|
-
const table =
|
|
24856
|
-
if (!table) return blamed(env,
|
|
25381
|
+
const table = resolveSubqueryTable(env.snapshot, subquery.table);
|
|
25382
|
+
if (!table) return blamed(env, unresolvedSubqueryReason(subquery.table));
|
|
24857
25383
|
const { state } = env;
|
|
24858
25384
|
if (++state.subqueries > MAX_STATEMENT_SUBQUERIES)
|
|
24859
25385
|
return blamed(env, STATEMENT_SUBQUERIES_REASON);
|
|
@@ -24867,7 +25393,7 @@ function subquerySql(env, subquery, select) {
|
|
|
24867
25393
|
const alias = `_naro_s${String(++state.aliases)}`;
|
|
24868
25394
|
const source = storedSource(
|
|
24869
25395
|
table,
|
|
24870
|
-
[asciiLower(subquery
|
|
25396
|
+
[asciiLower(subqueryName(subquery))],
|
|
24871
25397
|
alias
|
|
24872
25398
|
);
|
|
24873
25399
|
const inner = { ...env, scopes: [...env.scopes, source] };
|
|
@@ -24879,11 +25405,12 @@ function subquerySql(env, subquery, select) {
|
|
|
24879
25405
|
selected = valueSql(inner, subquery.select);
|
|
24880
25406
|
if (env.check) item = operandSide(inner, subquery.select);
|
|
24881
25407
|
}
|
|
24882
|
-
const own2 = storedSource(table,
|
|
25408
|
+
const own2 = storedSource(table, ownNames(table), alias);
|
|
24883
25409
|
const rls = inlinedPolicies(env, table, own2);
|
|
25410
|
+
const ready = table.name === STORAGE_OBJECTS_TABLE ? `${storageReadySql(alias)} AND ` : "";
|
|
24884
25411
|
const where = subquery.where === null ? "" : ` AND ${conditionSql(inner, subquery.where)}`;
|
|
24885
25412
|
return {
|
|
24886
|
-
sql: `SELECT ${selected} FROM ${quoteIdentifier(table.name)} AS ${quoteIdentifier(alias)} WHERE ${rls}${where}`,
|
|
25413
|
+
sql: `SELECT ${selected} FROM ${quoteIdentifier(table.name)} AS ${quoteIdentifier(alias)} WHERE ${ready}${rls}${where}`,
|
|
24887
25414
|
item
|
|
24888
25415
|
};
|
|
24889
25416
|
} finally {
|
|
@@ -24936,7 +25463,10 @@ function groupTerms(env, sources, action, permissive, restrictive, pick2) {
|
|
|
24936
25463
|
terms.push(term);
|
|
24937
25464
|
}
|
|
24938
25465
|
const group = terms.length === 0 ? { sql: "0", params: [] } : terms.length === 1 ? terms[0] : {
|
|
24939
|
-
sql:
|
|
25466
|
+
sql: joinConditions(
|
|
25467
|
+
terms.map((term) => term.sql),
|
|
25468
|
+
"OR"
|
|
25469
|
+
),
|
|
24940
25470
|
params: terms.flatMap((term) => term.params)
|
|
24941
25471
|
};
|
|
24942
25472
|
const restricted = restrictive.flatMap((policy) => {
|
|
@@ -24946,9 +25476,10 @@ function groupTerms(env, sources, action, permissive, restrictive, pick2) {
|
|
|
24946
25476
|
return { permissive: group, restrictive: restricted };
|
|
24947
25477
|
}
|
|
24948
25478
|
function inlinedPolicies(env, table, source) {
|
|
25479
|
+
const label2 = tableLabel(table);
|
|
24949
25480
|
const found = applicablePolicies(
|
|
24950
25481
|
env.req.policies,
|
|
24951
|
-
|
|
25482
|
+
label2,
|
|
24952
25483
|
"select",
|
|
24953
25484
|
env.req.role
|
|
24954
25485
|
);
|
|
@@ -24960,7 +25491,7 @@ function inlinedPolicies(env, table, source) {
|
|
|
24960
25491
|
if (found.permissive.length === 0) return "0";
|
|
24961
25492
|
return withTableActive(
|
|
24962
25493
|
env.state,
|
|
24963
|
-
|
|
25494
|
+
label2,
|
|
24964
25495
|
[...found.permissive, ...found.restrictive],
|
|
24965
25496
|
() => {
|
|
24966
25497
|
const inner = { ...env, check: null, params: [] };
|
|
@@ -24979,7 +25510,10 @@ function inlinedPolicies(env, table, source) {
|
|
|
24979
25510
|
for (const part of parts) env.params.push(...part.params);
|
|
24980
25511
|
const [only] = parts;
|
|
24981
25512
|
if (parts.length === 1 && only) return only.sql;
|
|
24982
|
-
return
|
|
25513
|
+
return joinConditions(
|
|
25514
|
+
parts.map((part) => part.sql),
|
|
25515
|
+
"AND"
|
|
25516
|
+
);
|
|
24983
25517
|
}
|
|
24984
25518
|
);
|
|
24985
25519
|
}
|
|
@@ -25074,11 +25608,8 @@ function scopedValues(operand, scopes) {
|
|
|
25074
25608
|
return operandValues(operand).map((value) => ({ value, scopes }));
|
|
25075
25609
|
}
|
|
25076
25610
|
function subqueryScopes(snapshot, subquery, scopes) {
|
|
25077
|
-
const inner =
|
|
25078
|
-
return inner ? [
|
|
25079
|
-
...scopes,
|
|
25080
|
-
{ names: [asciiLower(subquery.alias ?? inner.name)], table: inner }
|
|
25081
|
-
] : void 0;
|
|
25611
|
+
const inner = resolveSubqueryTable(snapshot, subquery.table);
|
|
25612
|
+
return inner ? [...scopes, { names: [asciiLower(subqueryName(subquery))], table: inner }] : void 0;
|
|
25082
25613
|
}
|
|
25083
25614
|
function columnsReadOn(snapshot, table, expr, written) {
|
|
25084
25615
|
const found = [];
|
|
@@ -25413,7 +25944,7 @@ function nodeSql(table, node, params, path) {
|
|
|
25413
25944
|
const terms = children.map(
|
|
25414
25945
|
(child, index) => nodeSql(table, child, params, `${path}.${key}[${index}]`)
|
|
25415
25946
|
);
|
|
25416
|
-
return
|
|
25947
|
+
return joinConditions(terms, key === "or" ? "OR" : "AND");
|
|
25417
25948
|
}
|
|
25418
25949
|
function whereClause(table, filters, policy, params) {
|
|
25419
25950
|
const parts = [];
|
|
@@ -25424,7 +25955,7 @@ function whereClause(table, filters, policy, params) {
|
|
|
25424
25955
|
const client = filters.map(
|
|
25425
25956
|
(node, index) => nodeSql(table, node, params, `filters[${index}]`)
|
|
25426
25957
|
);
|
|
25427
|
-
if (client.length > 0) parts.push(
|
|
25958
|
+
if (client.length > 0) parts.push(joinConditions(client, "AND"));
|
|
25428
25959
|
return parts.length === 0 ? "" : ` WHERE ${parts.join(" AND ")}`;
|
|
25429
25960
|
}
|
|
25430
25961
|
function orderClause(table, order) {
|
|
@@ -25626,7 +26157,7 @@ var Rls = class {
|
|
|
25626
26157
|
const found = this.found(command);
|
|
25627
26158
|
return [...found.permissive, ...found.restrictive];
|
|
25628
26159
|
});
|
|
25629
|
-
const key = asciiLower(
|
|
26160
|
+
const key = asciiLower(this.policyTable);
|
|
25630
26161
|
if (hasSubqueries(all) && !this.state.active.includes(key))
|
|
25631
26162
|
this.state.active.push(key);
|
|
25632
26163
|
for (const command of commands) {
|
|
@@ -26432,41 +26963,819 @@ function compile(query, snapshot, decision, context, options = {}) {
|
|
|
26432
26963
|
}
|
|
26433
26964
|
}
|
|
26434
26965
|
|
|
26435
|
-
// ../../packages/data-core/src/
|
|
26436
|
-
var
|
|
26437
|
-
|
|
26438
|
-
|
|
26439
|
-
|
|
26440
|
-
|
|
26441
|
-
const column2 = row.column_name;
|
|
26442
|
-
if (typeof table !== "string" || typeof column2 !== "string") continue;
|
|
26443
|
-
if (Number(row.partial) !== 0) continue;
|
|
26444
|
-
const key = asciiLower(table);
|
|
26445
|
-
const columns = tables.get(key) ?? /* @__PURE__ */ new Map();
|
|
26446
|
-
tables.set(key, columns);
|
|
26447
|
-
const collations = columns.get(asciiLower(column2)) ?? [];
|
|
26448
|
-
collations.push(
|
|
26449
|
-
typeof row.collation === "string" ? row.collation.toUpperCase() : "BINARY"
|
|
26450
|
-
);
|
|
26451
|
-
columns.set(asciiLower(column2), collations);
|
|
26452
|
-
}
|
|
26453
|
-
return tables;
|
|
26966
|
+
// ../../packages/data-core/src/sql-depth.ts
|
|
26967
|
+
var D1_EXPRESSION_DEPTH = 100;
|
|
26968
|
+
var Unreadable = class extends Error {
|
|
26969
|
+
};
|
|
26970
|
+
function isWordStart(code) {
|
|
26971
|
+
return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code >= 128;
|
|
26454
26972
|
}
|
|
26455
|
-
function
|
|
26456
|
-
|
|
26457
|
-
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
26458
|
-
const scope = scopes[index];
|
|
26459
|
-
if (qualifier !== null && !scope.names.includes(asciiLower(qualifier)))
|
|
26460
|
-
continue;
|
|
26461
|
-
const found = scope.table.byName.get(column2);
|
|
26462
|
-
if (found) return { column: found, scope: index };
|
|
26463
|
-
if (qualifier !== null) return void 0;
|
|
26464
|
-
}
|
|
26465
|
-
return void 0;
|
|
26973
|
+
function isDigit2(code) {
|
|
26974
|
+
return code >= 48 && code <= 57;
|
|
26466
26975
|
}
|
|
26467
|
-
function
|
|
26468
|
-
|
|
26469
|
-
|
|
26976
|
+
function isWordPart(code) {
|
|
26977
|
+
return isWordStart(code) || isDigit2(code) || code === 36;
|
|
26978
|
+
}
|
|
26979
|
+
function isSpace3(code) {
|
|
26980
|
+
return code === 32 || code >= 9 && code <= 13;
|
|
26981
|
+
}
|
|
26982
|
+
var OPERATORS_2 = /* @__PURE__ */ new Set([
|
|
26983
|
+
"->",
|
|
26984
|
+
"||",
|
|
26985
|
+
"<=",
|
|
26986
|
+
">=",
|
|
26987
|
+
"<>",
|
|
26988
|
+
"!=",
|
|
26989
|
+
"==",
|
|
26990
|
+
"<<",
|
|
26991
|
+
">>"
|
|
26992
|
+
]);
|
|
26993
|
+
var OPERATORS_1 = "=<>+-*/%&|~(),.;";
|
|
26994
|
+
var NUMBER2 = /^(?:0[xX][0-9A-Fa-f_]+|[0-9_]*\.?[0-9_]*(?:[eE][+-]?[0-9_]+)?)/;
|
|
26995
|
+
function tokenize3(sql) {
|
|
26996
|
+
const tokens = [];
|
|
26997
|
+
const length = sql.length;
|
|
26998
|
+
let at = 0;
|
|
26999
|
+
const closing = (close) => {
|
|
27000
|
+
let from = at + 1;
|
|
27001
|
+
for (; ; ) {
|
|
27002
|
+
const found = sql.indexOf(close, from);
|
|
27003
|
+
if (found < 0) throw new Unreadable("an unclosed quote");
|
|
27004
|
+
if (close === "]" || sql[found + 1] !== close) return found;
|
|
27005
|
+
from = found + 2;
|
|
27006
|
+
}
|
|
27007
|
+
};
|
|
27008
|
+
while (at < length) {
|
|
27009
|
+
const code = sql.charCodeAt(at);
|
|
27010
|
+
const next = sql.charCodeAt(at + 1);
|
|
27011
|
+
if (isSpace3(code)) {
|
|
27012
|
+
at++;
|
|
27013
|
+
} else if (code === 45 && next === 45) {
|
|
27014
|
+
const end = sql.indexOf("\n", at);
|
|
27015
|
+
at = end < 0 ? length : end + 1;
|
|
27016
|
+
} else if (code === 47 && next === 42) {
|
|
27017
|
+
const end = sql.indexOf("*/", at + 2);
|
|
27018
|
+
at = end < 0 ? length : end + 2;
|
|
27019
|
+
} else if (code === 39) {
|
|
27020
|
+
at = closing("'") + 1;
|
|
27021
|
+
tokens.push({ kind: "literal" });
|
|
27022
|
+
} else if (code === 34 || code === 96 || code === 91) {
|
|
27023
|
+
at = closing(code === 91 ? "]" : String.fromCharCode(code)) + 1;
|
|
27024
|
+
tokens.push({ kind: "name" });
|
|
27025
|
+
} else if ((code === 88 || code === 120) && next === 39) {
|
|
27026
|
+
at++;
|
|
27027
|
+
at = closing("'") + 1;
|
|
27028
|
+
tokens.push({ kind: "literal" });
|
|
27029
|
+
} else if (isDigit2(code) || code === 46 && isDigit2(next)) {
|
|
27030
|
+
const text2 = NUMBER2.exec(sql.slice(at, at + 64))?.[0] || sql[at] || "";
|
|
27031
|
+
tokens.push(
|
|
27032
|
+
/^[0-9]+$/.test(text2) ? { kind: "literal", integer: Number(text2) } : { kind: "literal" }
|
|
27033
|
+
);
|
|
27034
|
+
at += text2.length;
|
|
27035
|
+
} else if (code === 63 || code === 58 || code === 64 || code === 36) {
|
|
27036
|
+
at++;
|
|
27037
|
+
while (at < length && isWordPart(sql.charCodeAt(at))) at++;
|
|
27038
|
+
tokens.push({ kind: "param" });
|
|
27039
|
+
} else if (isWordStart(code)) {
|
|
27040
|
+
const start = at;
|
|
27041
|
+
while (at < length && isWordPart(sql.charCodeAt(at))) at++;
|
|
27042
|
+
const text2 = sql.slice(start, at);
|
|
27043
|
+
tokens.push({ kind: "word", text: text2, upper: text2.toUpperCase() });
|
|
27044
|
+
} else if (sql.startsWith("->>", at)) {
|
|
27045
|
+
tokens.push({ kind: "op", text: "->>" });
|
|
27046
|
+
at += 3;
|
|
27047
|
+
} else if (OPERATORS_2.has(sql.slice(at, at + 2))) {
|
|
27048
|
+
tokens.push({ kind: "op", text: sql.slice(at, at + 2) });
|
|
27049
|
+
at += 2;
|
|
27050
|
+
} else if (OPERATORS_1.includes(sql[at] ?? "")) {
|
|
27051
|
+
tokens.push({ kind: "op", text: sql[at] ?? "" });
|
|
27052
|
+
at++;
|
|
27053
|
+
} else throw new Unreadable(`an unexpected ${sql[at] ?? ""}`);
|
|
27054
|
+
}
|
|
27055
|
+
tokens.push({ kind: "end" });
|
|
27056
|
+
return tokens;
|
|
27057
|
+
}
|
|
27058
|
+
var CLAUSE_WORDS2 = /* @__PURE__ */ new Set([
|
|
27059
|
+
"AS",
|
|
27060
|
+
"CROSS",
|
|
27061
|
+
"DEFAULT",
|
|
27062
|
+
"DO",
|
|
27063
|
+
"EXCEPT",
|
|
27064
|
+
"FROM",
|
|
27065
|
+
"FULL",
|
|
27066
|
+
"GROUP",
|
|
27067
|
+
"HAVING",
|
|
27068
|
+
"INDEXED",
|
|
27069
|
+
"INNER",
|
|
27070
|
+
"INTERSECT",
|
|
27071
|
+
"JOIN",
|
|
27072
|
+
"LEFT",
|
|
27073
|
+
"LIMIT",
|
|
27074
|
+
"NATURAL",
|
|
27075
|
+
"NOT",
|
|
27076
|
+
"OFFSET",
|
|
27077
|
+
"ON",
|
|
27078
|
+
"ORDER",
|
|
27079
|
+
"OUTER",
|
|
27080
|
+
"RETURNING",
|
|
27081
|
+
"RIGHT",
|
|
27082
|
+
"SET",
|
|
27083
|
+
"UNION",
|
|
27084
|
+
"USING",
|
|
27085
|
+
"VALUES",
|
|
27086
|
+
"WHERE",
|
|
27087
|
+
"WINDOW"
|
|
27088
|
+
]);
|
|
27089
|
+
var NOT_CONSTANT = /* @__PURE__ */ new Set([
|
|
27090
|
+
"avg",
|
|
27091
|
+
"changes",
|
|
27092
|
+
"count",
|
|
27093
|
+
"group_concat",
|
|
27094
|
+
"json_group_array",
|
|
27095
|
+
"json_group_object",
|
|
27096
|
+
"jsonb_group_array",
|
|
27097
|
+
"jsonb_group_object",
|
|
27098
|
+
"last_insert_rowid",
|
|
27099
|
+
"random",
|
|
27100
|
+
"randomblob",
|
|
27101
|
+
"sqlite_offset",
|
|
27102
|
+
"string_agg",
|
|
27103
|
+
"sum",
|
|
27104
|
+
"total",
|
|
27105
|
+
"total_changes"
|
|
27106
|
+
]);
|
|
27107
|
+
var OR = 1;
|
|
27108
|
+
var AND = 2;
|
|
27109
|
+
var NOT = 3;
|
|
27110
|
+
var COMPARE = 4;
|
|
27111
|
+
var ORDER = 5;
|
|
27112
|
+
var ESCAPE = 6;
|
|
27113
|
+
var BITS = 7;
|
|
27114
|
+
var SUM = 8;
|
|
27115
|
+
var PRODUCT = 9;
|
|
27116
|
+
var CONCAT = 10;
|
|
27117
|
+
var COLLATE = 11;
|
|
27118
|
+
var UNARY = 12;
|
|
27119
|
+
var BINARY = {
|
|
27120
|
+
"=": COMPARE,
|
|
27121
|
+
"==": COMPARE,
|
|
27122
|
+
"!=": COMPARE,
|
|
27123
|
+
"<>": COMPARE,
|
|
27124
|
+
"<": ORDER,
|
|
27125
|
+
"<=": ORDER,
|
|
27126
|
+
">": ORDER,
|
|
27127
|
+
">=": ORDER,
|
|
27128
|
+
"&": BITS,
|
|
27129
|
+
"|": BITS,
|
|
27130
|
+
"<<": BITS,
|
|
27131
|
+
">>": BITS,
|
|
27132
|
+
"+": SUM,
|
|
27133
|
+
"-": SUM,
|
|
27134
|
+
"*": PRODUCT,
|
|
27135
|
+
"/": PRODUCT,
|
|
27136
|
+
"%": PRODUCT,
|
|
27137
|
+
"||": CONCAT,
|
|
27138
|
+
"->": CONCAT,
|
|
27139
|
+
"->>": CONCAT
|
|
27140
|
+
};
|
|
27141
|
+
var LIKE_WORDS = /* @__PURE__ */ new Set(["LIKE", "GLOB", "REGEXP", "MATCH"]);
|
|
27142
|
+
var USABLE_OPERATORS = /* @__PURE__ */ new Set(["=", "==", "<", "<=", ">", ">="]);
|
|
27143
|
+
var Reader = class {
|
|
27144
|
+
constructor(tokens, planner) {
|
|
27145
|
+
this.tokens = tokens;
|
|
27146
|
+
this.planner = planner;
|
|
27147
|
+
}
|
|
27148
|
+
at = 0;
|
|
27149
|
+
/** The deepest node built, kept or not: each is checked as it is built. */
|
|
27150
|
+
deepest = 0;
|
|
27151
|
+
peek(offset = 0) {
|
|
27152
|
+
return this.tokens[this.at + offset] ?? { kind: "end" };
|
|
27153
|
+
}
|
|
27154
|
+
isWord(word, offset = 0) {
|
|
27155
|
+
const token = this.peek(offset);
|
|
27156
|
+
return token.kind === "word" && token.upper === word;
|
|
27157
|
+
}
|
|
27158
|
+
isOp(op, offset = 0) {
|
|
27159
|
+
const token = this.peek(offset);
|
|
27160
|
+
return token.kind === "op" && token.text === op;
|
|
27161
|
+
}
|
|
27162
|
+
isName(offset = 0) {
|
|
27163
|
+
const kind = this.peek(offset).kind;
|
|
27164
|
+
return kind === "word" || kind === "name";
|
|
27165
|
+
}
|
|
27166
|
+
takeWord(word) {
|
|
27167
|
+
if (!this.isWord(word)) return false;
|
|
27168
|
+
this.at++;
|
|
27169
|
+
return true;
|
|
27170
|
+
}
|
|
27171
|
+
takeOp(op) {
|
|
27172
|
+
if (!this.isOp(op)) return false;
|
|
27173
|
+
this.at++;
|
|
27174
|
+
return true;
|
|
27175
|
+
}
|
|
27176
|
+
expectWord(word) {
|
|
27177
|
+
if (!this.takeWord(word)) throw new Unreadable(`no ${word}`);
|
|
27178
|
+
}
|
|
27179
|
+
expectOp(op) {
|
|
27180
|
+
if (!this.takeOp(op)) throw new Unreadable(`no ${op}`);
|
|
27181
|
+
}
|
|
27182
|
+
name() {
|
|
27183
|
+
if (!this.isName()) throw new Unreadable("no name");
|
|
27184
|
+
this.at++;
|
|
27185
|
+
}
|
|
27186
|
+
/** `name` or `schema.name`. */
|
|
27187
|
+
qualifiedName() {
|
|
27188
|
+
this.name();
|
|
27189
|
+
if (this.isOp(".") && this.isName(1)) this.at += 2;
|
|
27190
|
+
}
|
|
27191
|
+
atEnd() {
|
|
27192
|
+
this.takeOp(";");
|
|
27193
|
+
return this.peek().kind === "end";
|
|
27194
|
+
}
|
|
27195
|
+
// --- nodes, as expr.c builds them ---
|
|
27196
|
+
made(node) {
|
|
27197
|
+
this.deepest = Math.max(this.deepest, node.height);
|
|
27198
|
+
return node;
|
|
27199
|
+
}
|
|
27200
|
+
leaf(extra = {}) {
|
|
27201
|
+
return this.made({ height: 1, constant: true, subqueries: [], ...extra });
|
|
27202
|
+
}
|
|
27203
|
+
/** sqlite3PExpr, exprSetHeight: one deeper than the deepest part. */
|
|
27204
|
+
over(parts, extra = {}) {
|
|
27205
|
+
return this.made(this.unchecked(parts, extra));
|
|
27206
|
+
}
|
|
27207
|
+
/**
|
|
27208
|
+
* sqlite3ExprAttachSubtrees alone, as for a CAST: its height is set and
|
|
27209
|
+
* not checked, so it counts only through what holds it.
|
|
27210
|
+
*/
|
|
27211
|
+
unchecked(parts, extra = {}) {
|
|
27212
|
+
let height = 0;
|
|
27213
|
+
let constant = true;
|
|
27214
|
+
let hasFunc = false;
|
|
27215
|
+
let hasSubquery = false;
|
|
27216
|
+
let subqueries = [];
|
|
27217
|
+
for (const part of parts) {
|
|
27218
|
+
if (part.height > height) height = part.height;
|
|
27219
|
+
if (!part.constant) constant = false;
|
|
27220
|
+
if (part.hasFunc) hasFunc = true;
|
|
27221
|
+
if (part.hasSubquery) hasSubquery = true;
|
|
27222
|
+
if (part.subqueries.length > 0)
|
|
27223
|
+
subqueries = subqueries.length === 0 ? part.subqueries : [...subqueries, ...part.subqueries];
|
|
27224
|
+
}
|
|
27225
|
+
const node = { height: height + 1, constant, subqueries, ...extra };
|
|
27226
|
+
if (hasFunc) node.hasFunc ??= true;
|
|
27227
|
+
if (hasSubquery) node.hasSubquery ??= true;
|
|
27228
|
+
return node;
|
|
27229
|
+
}
|
|
27230
|
+
/** A node holding a SELECT: EXISTS, `(SELECT …)`, `x IN (SELECT …)`. */
|
|
27231
|
+
holding(left, select) {
|
|
27232
|
+
const node = {
|
|
27233
|
+
height: Math.max(select.height, left?.height ?? 0) + 1,
|
|
27234
|
+
constant: false,
|
|
27235
|
+
subqueries: [...left?.subqueries ?? [], select],
|
|
27236
|
+
hasSubquery: true
|
|
27237
|
+
};
|
|
27238
|
+
if (left?.hasFunc) node.hasFunc = true;
|
|
27239
|
+
if (left?.column) node.usable = true;
|
|
27240
|
+
return this.made(node);
|
|
27241
|
+
}
|
|
27242
|
+
// --- expressions, by parse.y's precedence ---
|
|
27243
|
+
expr(min = OR) {
|
|
27244
|
+
let left = this.prefix();
|
|
27245
|
+
for (; ; ) {
|
|
27246
|
+
const token = this.peek();
|
|
27247
|
+
if (token.kind === "op") {
|
|
27248
|
+
const level = BINARY[token.text];
|
|
27249
|
+
if (level === void 0 || level < min) return left;
|
|
27250
|
+
this.at++;
|
|
27251
|
+
const right = this.expr(level + 1);
|
|
27252
|
+
if (token.text === "->" || token.text === "->>") {
|
|
27253
|
+
left = this.over([left, right], { hasFunc: true });
|
|
27254
|
+
continue;
|
|
27255
|
+
}
|
|
27256
|
+
const compared = USABLE_OPERATORS.has(token.text);
|
|
27257
|
+
left = this.over(
|
|
27258
|
+
[left, right],
|
|
27259
|
+
compared && (left.column || right.column) ? { usable: true } : {}
|
|
27260
|
+
);
|
|
27261
|
+
continue;
|
|
27262
|
+
}
|
|
27263
|
+
if (token.kind !== "word") return left;
|
|
27264
|
+
const word = token.upper;
|
|
27265
|
+
if (word === "OR" || word === "AND") {
|
|
27266
|
+
if ((word === "OR" ? OR : AND) < min) return left;
|
|
27267
|
+
this.at++;
|
|
27268
|
+
const right = this.expr((word === "OR" ? OR : AND) + 1);
|
|
27269
|
+
if (word === "OR")
|
|
27270
|
+
left = this.over([left, right], { or: [left, right] });
|
|
27271
|
+
else if ((left.isFalse || right.isFalse) && !left.hasFunc && !right.hasFunc)
|
|
27272
|
+
left = this.leaf({ isFalse: true, literal: true });
|
|
27273
|
+
else left = this.over([left, right], { and: [left, right] });
|
|
27274
|
+
continue;
|
|
27275
|
+
}
|
|
27276
|
+
if (word === "COLLATE") {
|
|
27277
|
+
if (COLLATE < min) return left;
|
|
27278
|
+
this.at++;
|
|
27279
|
+
this.name();
|
|
27280
|
+
left = this.made({
|
|
27281
|
+
height: 1,
|
|
27282
|
+
constant: left.constant,
|
|
27283
|
+
subqueries: left.subqueries,
|
|
27284
|
+
...left.column ? { column: true } : {}
|
|
27285
|
+
});
|
|
27286
|
+
continue;
|
|
27287
|
+
}
|
|
27288
|
+
if (COMPARE < min) return left;
|
|
27289
|
+
const negated = word === "NOT" ? 1 : 0;
|
|
27290
|
+
const after = this.peek(negated);
|
|
27291
|
+
const verb = after.kind === "word" ? after.upper : "";
|
|
27292
|
+
if (word === "IS") {
|
|
27293
|
+
this.at++;
|
|
27294
|
+
let not = this.takeWord("NOT");
|
|
27295
|
+
if (this.takeWord("DISTINCT")) {
|
|
27296
|
+
this.expectWord("FROM");
|
|
27297
|
+
not = !not;
|
|
27298
|
+
}
|
|
27299
|
+
const right = this.expr(COMPARE + 1);
|
|
27300
|
+
left = right.isNull ? this.nullTest(left, !not) : this.over(
|
|
27301
|
+
[left, right],
|
|
27302
|
+
!not && (left.column || right.column) ? { usable: true } : {}
|
|
27303
|
+
);
|
|
27304
|
+
} else if (word === "ISNULL" || word === "NOTNULL") {
|
|
27305
|
+
this.at++;
|
|
27306
|
+
left = this.nullTest(left, word === "ISNULL");
|
|
27307
|
+
} else if (negated && verb === "NULL") {
|
|
27308
|
+
this.at += 2;
|
|
27309
|
+
left = this.nullTest(left, false);
|
|
27310
|
+
} else if (LIKE_WORDS.has(verb)) {
|
|
27311
|
+
this.at += negated + 1;
|
|
27312
|
+
const args = [this.expr(COMPARE + 1), left];
|
|
27313
|
+
if (this.takeWord("ESCAPE")) args.push(this.expr(ESCAPE + 1));
|
|
27314
|
+
left = this.over(args, { hasFunc: true });
|
|
27315
|
+
if (negated) left = this.over([left]);
|
|
27316
|
+
} else if (verb === "BETWEEN") {
|
|
27317
|
+
this.at += negated + 1;
|
|
27318
|
+
const low = this.expr(NOT);
|
|
27319
|
+
this.expectWord("AND");
|
|
27320
|
+
const high = this.expr(COMPARE + 1);
|
|
27321
|
+
left = this.over([left, low, high]);
|
|
27322
|
+
if (negated) left = this.over([left]);
|
|
27323
|
+
} else if (verb === "IN") {
|
|
27324
|
+
this.at += negated + 1;
|
|
27325
|
+
left = this.inList(left, negated === 1);
|
|
27326
|
+
} else return left;
|
|
27327
|
+
}
|
|
27328
|
+
}
|
|
27329
|
+
/**
|
|
27330
|
+
* IS NULL (`isNull`) or IS NOT NULL: on a literal, the answer itself; on
|
|
27331
|
+
* a column, a term the planner can use when it is IS NULL.
|
|
27332
|
+
*/
|
|
27333
|
+
nullTest(left, isNull) {
|
|
27334
|
+
if (left.literal)
|
|
27335
|
+
return this.leaf(
|
|
27336
|
+
isNull ? { isFalse: true, literal: true } : { literal: true }
|
|
27337
|
+
);
|
|
27338
|
+
return this.over([left], isNull && left.column ? { usable: true } : {});
|
|
27339
|
+
}
|
|
27340
|
+
inList(left, negated) {
|
|
27341
|
+
this.expectOp("(");
|
|
27342
|
+
if (this.takeOp(")"))
|
|
27343
|
+
return left.hasFunc ? this.over([left]) : this.leaf(negated ? {} : { isFalse: true });
|
|
27344
|
+
let node;
|
|
27345
|
+
if (this.startsSelect()) {
|
|
27346
|
+
node = this.holding(left, this.select());
|
|
27347
|
+
} else {
|
|
27348
|
+
const items = this.list();
|
|
27349
|
+
const [only] = items;
|
|
27350
|
+
const usable = left.column ? { usable: true } : {};
|
|
27351
|
+
if (items.length === 1 && only?.select)
|
|
27352
|
+
node = this.holding(left, only.select);
|
|
27353
|
+
else if (items.length === 1 && only?.constant)
|
|
27354
|
+
node = this.over([left, this.over([only])], usable);
|
|
27355
|
+
else node = this.over([left, ...items], usable);
|
|
27356
|
+
}
|
|
27357
|
+
this.expectOp(")");
|
|
27358
|
+
return negated ? this.over([node]) : node;
|
|
27359
|
+
}
|
|
27360
|
+
list() {
|
|
27361
|
+
const items = [this.expr()];
|
|
27362
|
+
while (this.takeOp(",")) items.push(this.expr());
|
|
27363
|
+
return items;
|
|
27364
|
+
}
|
|
27365
|
+
prefix() {
|
|
27366
|
+
const token = this.peek();
|
|
27367
|
+
this.at++;
|
|
27368
|
+
switch (token.kind) {
|
|
27369
|
+
case "literal":
|
|
27370
|
+
return this.leaf(
|
|
27371
|
+
token.integer === 0 ? { isFalse: true, literal: true } : { literal: true }
|
|
27372
|
+
);
|
|
27373
|
+
case "param":
|
|
27374
|
+
return this.leaf();
|
|
27375
|
+
case "name":
|
|
27376
|
+
return this.column();
|
|
27377
|
+
case "end":
|
|
27378
|
+
throw new Unreadable("no expression");
|
|
27379
|
+
case "op":
|
|
27380
|
+
if (token.text === "(") {
|
|
27381
|
+
if (this.startsSelect()) {
|
|
27382
|
+
const select = this.select();
|
|
27383
|
+
this.expectOp(")");
|
|
27384
|
+
return { ...this.holding(null, select), select };
|
|
27385
|
+
}
|
|
27386
|
+
const items = this.list();
|
|
27387
|
+
this.expectOp(")");
|
|
27388
|
+
if (items.length === 1 && items[0]) return items[0];
|
|
27389
|
+
throw new Unreadable("a row value");
|
|
27390
|
+
}
|
|
27391
|
+
if (token.text === "-" || token.text === "+") {
|
|
27392
|
+
const operand = this.expr(UNARY);
|
|
27393
|
+
return this.over([operand], operand.literal ? { literal: true } : {});
|
|
27394
|
+
}
|
|
27395
|
+
if (token.text === "~") return this.over([this.expr(UNARY)]);
|
|
27396
|
+
throw new Unreadable(`an unexpected ${token.text}`);
|
|
27397
|
+
case "word":
|
|
27398
|
+
break;
|
|
27399
|
+
}
|
|
27400
|
+
switch (token.upper) {
|
|
27401
|
+
case "NULL":
|
|
27402
|
+
return this.leaf({ isNull: true });
|
|
27403
|
+
case "TRUE":
|
|
27404
|
+
case "FALSE":
|
|
27405
|
+
return this.leaf();
|
|
27406
|
+
case "NOT":
|
|
27407
|
+
return this.over([this.expr(NOT)]);
|
|
27408
|
+
case "EXISTS": {
|
|
27409
|
+
this.expectOp("(");
|
|
27410
|
+
const select = this.select();
|
|
27411
|
+
this.expectOp(")");
|
|
27412
|
+
return this.holding(null, select);
|
|
27413
|
+
}
|
|
27414
|
+
case "CAST": {
|
|
27415
|
+
this.expectOp("(");
|
|
27416
|
+
const value = this.expr();
|
|
27417
|
+
this.expectWord("AS");
|
|
27418
|
+
this.typeName();
|
|
27419
|
+
this.expectOp(")");
|
|
27420
|
+
return this.unchecked([value]);
|
|
27421
|
+
}
|
|
27422
|
+
case "CASE": {
|
|
27423
|
+
const parts = [];
|
|
27424
|
+
if (!this.isWord("WHEN")) parts.push(this.expr());
|
|
27425
|
+
while (this.takeWord("WHEN")) {
|
|
27426
|
+
parts.push(this.expr());
|
|
27427
|
+
this.expectWord("THEN");
|
|
27428
|
+
parts.push(this.expr());
|
|
27429
|
+
}
|
|
27430
|
+
if (this.takeWord("ELSE")) parts.push(this.expr());
|
|
27431
|
+
this.expectWord("END");
|
|
27432
|
+
return this.over(parts);
|
|
27433
|
+
}
|
|
27434
|
+
default:
|
|
27435
|
+
break;
|
|
27436
|
+
}
|
|
27437
|
+
if (!this.takeOp("(")) return this.column();
|
|
27438
|
+
let args = [];
|
|
27439
|
+
if (!this.takeOp("*") && !this.isOp(")")) {
|
|
27440
|
+
if (!this.takeWord("DISTINCT")) this.takeWord("ALL");
|
|
27441
|
+
args = this.list();
|
|
27442
|
+
}
|
|
27443
|
+
this.expectOp(")");
|
|
27444
|
+
if (this.isWord("FILTER") || this.isWord("OVER"))
|
|
27445
|
+
throw new Unreadable("a window function");
|
|
27446
|
+
return this.over(args, {
|
|
27447
|
+
hasFunc: true,
|
|
27448
|
+
constant: !NOT_CONSTANT.has(token.text.toLowerCase()) && args.every((arg) => arg.constant)
|
|
27449
|
+
});
|
|
27450
|
+
}
|
|
27451
|
+
/**
|
|
27452
|
+
* `a`, `a.b` or `a.b.c`, its first name just read: TK_ID, and a TK_DOT a
|
|
27453
|
+
* dot — a.b.c is DOT(a, DOT(b, c)).
|
|
27454
|
+
*/
|
|
27455
|
+
column() {
|
|
27456
|
+
let node = this.leaf({ constant: false, column: true });
|
|
27457
|
+
while (this.isOp(".") && this.isName(1)) {
|
|
27458
|
+
this.at += 2;
|
|
27459
|
+
node = this.over([this.leaf({ constant: false }), node], {
|
|
27460
|
+
column: true
|
|
27461
|
+
});
|
|
27462
|
+
}
|
|
27463
|
+
return node;
|
|
27464
|
+
}
|
|
27465
|
+
typeName() {
|
|
27466
|
+
do
|
|
27467
|
+
this.name();
|
|
27468
|
+
while (this.isName());
|
|
27469
|
+
if (this.takeOp("(")) {
|
|
27470
|
+
while (!this.takeOp(")")) {
|
|
27471
|
+
if (this.peek().kind === "end") throw new Unreadable("no )");
|
|
27472
|
+
this.at++;
|
|
27473
|
+
}
|
|
27474
|
+
}
|
|
27475
|
+
}
|
|
27476
|
+
/**
|
|
27477
|
+
* A WHERE, and the chain an OR in it may bring when each of its branches
|
|
27478
|
+
* could be read through an index — a column compared, or an AND holding
|
|
27479
|
+
* one (exprAnalyzeOrTerm) — whether or not the table has that index: the
|
|
27480
|
+
* other terms the planner can use, without a subquery, ANDed one by one
|
|
27481
|
+
* from the first, under one more AND (wherecode.c, WHERE_MULTI_OR).
|
|
27482
|
+
*/
|
|
27483
|
+
where() {
|
|
27484
|
+
const where = this.expr();
|
|
27485
|
+
if (!this.planner) return where;
|
|
27486
|
+
const terms = split(where, "and");
|
|
27487
|
+
for (const or of terms) {
|
|
27488
|
+
if (!or.or || !split(or, "or").every(
|
|
27489
|
+
(branch) => branch.usable || split(branch, "and").some((term) => term.usable)
|
|
27490
|
+
))
|
|
27491
|
+
continue;
|
|
27492
|
+
let chain = 0;
|
|
27493
|
+
for (const term of terms)
|
|
27494
|
+
if (term !== or && (term.usable || term.or) && !term.hasSubquery)
|
|
27495
|
+
chain = chain === 0 ? term.height : Math.max(chain, term.height) + 1;
|
|
27496
|
+
if (chain > 0) this.deepest = Math.max(this.deepest, chain + 1);
|
|
27497
|
+
}
|
|
27498
|
+
return where;
|
|
27499
|
+
}
|
|
27500
|
+
// --- statements ---
|
|
27501
|
+
startsSelect() {
|
|
27502
|
+
return this.isWord("SELECT") || this.isWord("VALUES") || this.isWord("WITH");
|
|
27503
|
+
}
|
|
27504
|
+
/** A SELECT, compound or not, with its ORDER BY and LIMIT. */
|
|
27505
|
+
select() {
|
|
27506
|
+
if (this.isWord("WITH")) throw new Unreadable("WITH");
|
|
27507
|
+
const whole = { height: 0, resolved: [], from: [] };
|
|
27508
|
+
const counted = (node) => {
|
|
27509
|
+
whole.height = Math.max(whole.height, node.height);
|
|
27510
|
+
whole.resolved.push(node);
|
|
27511
|
+
};
|
|
27512
|
+
do {
|
|
27513
|
+
if (this.takeWord("VALUES")) {
|
|
27514
|
+
do {
|
|
27515
|
+
this.expectOp("(");
|
|
27516
|
+
for (const item of this.list()) counted(item);
|
|
27517
|
+
this.expectOp(")");
|
|
27518
|
+
} while (this.takeOp(","));
|
|
27519
|
+
continue;
|
|
27520
|
+
}
|
|
27521
|
+
this.expectWord("SELECT");
|
|
27522
|
+
if (!this.takeWord("DISTINCT")) this.takeWord("ALL");
|
|
27523
|
+
const stars = [];
|
|
27524
|
+
do {
|
|
27525
|
+
if (this.takeOp("*")) stars.push(this.leaf());
|
|
27526
|
+
else if (this.isName() && this.isOp(".", 1) && this.isOp("*", 2)) {
|
|
27527
|
+
this.at += 3;
|
|
27528
|
+
stars.push(this.over([this.leaf(), this.leaf()]));
|
|
27529
|
+
} else {
|
|
27530
|
+
counted(this.expr());
|
|
27531
|
+
this.alias();
|
|
27532
|
+
}
|
|
27533
|
+
} while (this.takeOp(","));
|
|
27534
|
+
const sources = this.takeWord("FROM") ? this.from(whole) : 0;
|
|
27535
|
+
for (const star of stars) {
|
|
27536
|
+
whole.height = Math.max(whole.height, star.height);
|
|
27537
|
+
whole.resolved.push(
|
|
27538
|
+
sources > 1 ? this.made({ height: 3, constant: false, subqueries: [] }) : star
|
|
27539
|
+
);
|
|
27540
|
+
}
|
|
27541
|
+
if (this.takeWord("WHERE")) counted(this.where());
|
|
27542
|
+
if (this.takeWord("GROUP")) {
|
|
27543
|
+
this.expectWord("BY");
|
|
27544
|
+
for (const item of this.list()) counted(item);
|
|
27545
|
+
if (this.takeWord("HAVING")) counted(this.expr());
|
|
27546
|
+
}
|
|
27547
|
+
} while (this.compound());
|
|
27548
|
+
if (this.takeWord("ORDER")) {
|
|
27549
|
+
this.expectWord("BY");
|
|
27550
|
+
do {
|
|
27551
|
+
counted(this.expr());
|
|
27552
|
+
if (!this.takeWord("ASC")) this.takeWord("DESC");
|
|
27553
|
+
if (this.takeWord("NULLS") && !this.takeWord("FIRST"))
|
|
27554
|
+
this.expectWord("LAST");
|
|
27555
|
+
} while (this.takeOp(","));
|
|
27556
|
+
}
|
|
27557
|
+
if (this.takeWord("LIMIT")) {
|
|
27558
|
+
const parts = [this.expr()];
|
|
27559
|
+
if (this.takeWord("OFFSET") || this.takeOp(",")) parts.push(this.expr());
|
|
27560
|
+
counted(this.over(parts));
|
|
27561
|
+
}
|
|
27562
|
+
return whole;
|
|
27563
|
+
}
|
|
27564
|
+
compound() {
|
|
27565
|
+
if (this.takeWord("UNION")) {
|
|
27566
|
+
this.takeWord("ALL");
|
|
27567
|
+
return true;
|
|
27568
|
+
}
|
|
27569
|
+
return this.takeWord("INTERSECT") || this.takeWord("EXCEPT");
|
|
27570
|
+
}
|
|
27571
|
+
alias() {
|
|
27572
|
+
if (this.takeWord("AS")) {
|
|
27573
|
+
this.name();
|
|
27574
|
+
return;
|
|
27575
|
+
}
|
|
27576
|
+
const token = this.peek();
|
|
27577
|
+
if (token.kind === "name" || token.kind === "word" && !CLAUSE_WORDS2.has(token.upper))
|
|
27578
|
+
this.at++;
|
|
27579
|
+
}
|
|
27580
|
+
/** The FROM items, into `into`; how many there are. */
|
|
27581
|
+
from(into) {
|
|
27582
|
+
let sources = 0;
|
|
27583
|
+
do {
|
|
27584
|
+
sources++;
|
|
27585
|
+
if (this.takeOp("(")) {
|
|
27586
|
+
if (!this.startsSelect()) throw new Unreadable("a parenthesized join");
|
|
27587
|
+
into.from.push(this.select());
|
|
27588
|
+
this.expectOp(")");
|
|
27589
|
+
} else {
|
|
27590
|
+
this.qualifiedName();
|
|
27591
|
+
if (this.takeOp("(")) {
|
|
27592
|
+
if (!this.isOp(")"))
|
|
27593
|
+
for (const arg of this.list()) {
|
|
27594
|
+
into.resolved.push(arg);
|
|
27595
|
+
this.deepest = Math.max(this.deepest, arg.height + 2);
|
|
27596
|
+
}
|
|
27597
|
+
this.expectOp(")");
|
|
27598
|
+
}
|
|
27599
|
+
}
|
|
27600
|
+
this.alias();
|
|
27601
|
+
if (this.takeWord("INDEXED")) {
|
|
27602
|
+
this.expectWord("BY");
|
|
27603
|
+
this.name();
|
|
27604
|
+
} else if (this.isWord("NOT") && this.isWord("INDEXED", 1)) this.at += 2;
|
|
27605
|
+
if (this.isWord("ON") || this.isWord("USING"))
|
|
27606
|
+
throw new Unreadable("a join constraint");
|
|
27607
|
+
} while (this.join());
|
|
27608
|
+
return sources;
|
|
27609
|
+
}
|
|
27610
|
+
join() {
|
|
27611
|
+
if (this.takeOp(",")) return true;
|
|
27612
|
+
const start = this.at;
|
|
27613
|
+
this.takeWord("NATURAL");
|
|
27614
|
+
if (this.takeWord("LEFT") || this.takeWord("RIGHT") || this.takeWord("FULL"))
|
|
27615
|
+
this.takeWord("OUTER");
|
|
27616
|
+
else if (!this.takeWord("INNER")) this.takeWord("CROSS");
|
|
27617
|
+
if (this.takeWord("JOIN")) return true;
|
|
27618
|
+
this.at = start;
|
|
27619
|
+
return false;
|
|
27620
|
+
}
|
|
27621
|
+
names() {
|
|
27622
|
+
this.expectOp("(");
|
|
27623
|
+
do
|
|
27624
|
+
this.name();
|
|
27625
|
+
while (this.takeOp(","));
|
|
27626
|
+
this.expectOp(")");
|
|
27627
|
+
}
|
|
27628
|
+
returning(into) {
|
|
27629
|
+
if (!this.takeWord("RETURNING")) return;
|
|
27630
|
+
do {
|
|
27631
|
+
if (this.takeOp("*")) into.push(this.leaf());
|
|
27632
|
+
else {
|
|
27633
|
+
into.push(this.expr());
|
|
27634
|
+
this.alias();
|
|
27635
|
+
}
|
|
27636
|
+
} while (this.takeOp(","));
|
|
27637
|
+
}
|
|
27638
|
+
assignments(into) {
|
|
27639
|
+
do {
|
|
27640
|
+
if (this.isOp("(")) this.names();
|
|
27641
|
+
else this.name();
|
|
27642
|
+
this.expectOp("=");
|
|
27643
|
+
into.push(this.expr());
|
|
27644
|
+
} while (this.takeOp(","));
|
|
27645
|
+
}
|
|
27646
|
+
/** The statement: each of its expressions resolved from 0. */
|
|
27647
|
+
statement() {
|
|
27648
|
+
const top = { height: 0, resolved: [], from: [] };
|
|
27649
|
+
if (this.startsSelect()) {
|
|
27650
|
+
top.from.push(this.select());
|
|
27651
|
+
} else if (this.takeWord("INSERT") || this.takeWord("REPLACE")) {
|
|
27652
|
+
if (this.takeWord("OR")) this.name();
|
|
27653
|
+
this.expectWord("INTO");
|
|
27654
|
+
this.qualifiedName();
|
|
27655
|
+
if (this.takeWord("AS")) this.name();
|
|
27656
|
+
if (this.isOp("(")) this.names();
|
|
27657
|
+
if (this.takeWord("DEFAULT")) this.expectWord("VALUES");
|
|
27658
|
+
else top.from.push(this.select());
|
|
27659
|
+
while (this.takeWord("ON")) {
|
|
27660
|
+
this.expectWord("CONFLICT");
|
|
27661
|
+
if (this.takeOp("(")) {
|
|
27662
|
+
top.resolved.push(...this.list());
|
|
27663
|
+
this.expectOp(")");
|
|
27664
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.expr());
|
|
27665
|
+
}
|
|
27666
|
+
this.expectWord("DO");
|
|
27667
|
+
if (this.takeWord("NOTHING")) continue;
|
|
27668
|
+
this.expectWord("UPDATE");
|
|
27669
|
+
this.expectWord("SET");
|
|
27670
|
+
this.assignments(top.resolved);
|
|
27671
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.expr());
|
|
27672
|
+
}
|
|
27673
|
+
this.returning(top.resolved);
|
|
27674
|
+
} else if (this.takeWord("UPDATE")) {
|
|
27675
|
+
if (this.takeWord("OR")) this.name();
|
|
27676
|
+
this.qualifiedName();
|
|
27677
|
+
this.alias();
|
|
27678
|
+
if (this.takeWord("INDEXED")) {
|
|
27679
|
+
this.expectWord("BY");
|
|
27680
|
+
this.name();
|
|
27681
|
+
}
|
|
27682
|
+
this.expectWord("SET");
|
|
27683
|
+
this.assignments(top.resolved);
|
|
27684
|
+
if (this.isWord("FROM")) throw new Unreadable("UPDATE \u2026 FROM");
|
|
27685
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.where());
|
|
27686
|
+
this.returning(top.resolved);
|
|
27687
|
+
} else if (this.takeWord("DELETE")) {
|
|
27688
|
+
this.expectWord("FROM");
|
|
27689
|
+
this.qualifiedName();
|
|
27690
|
+
this.alias();
|
|
27691
|
+
if (this.takeWord("INDEXED")) {
|
|
27692
|
+
this.expectWord("BY");
|
|
27693
|
+
this.name();
|
|
27694
|
+
}
|
|
27695
|
+
if (this.takeWord("WHERE")) top.resolved.push(this.where());
|
|
27696
|
+
this.returning(top.resolved);
|
|
27697
|
+
} else throw new Unreadable("not a statement naro compiles");
|
|
27698
|
+
return top;
|
|
27699
|
+
}
|
|
27700
|
+
};
|
|
27701
|
+
function split(node, at) {
|
|
27702
|
+
const found = [];
|
|
27703
|
+
const pending = [node];
|
|
27704
|
+
for (let next = pending.pop(); next; next = pending.pop()) {
|
|
27705
|
+
const sides = next[at];
|
|
27706
|
+
if (sides) pending.push(sides[1], sides[0]);
|
|
27707
|
+
else found.push(next);
|
|
27708
|
+
}
|
|
27709
|
+
return found;
|
|
27710
|
+
}
|
|
27711
|
+
function nodeNeed(node, base2) {
|
|
27712
|
+
const at = base2 + node.height;
|
|
27713
|
+
let need = at;
|
|
27714
|
+
for (const select of node.subqueries)
|
|
27715
|
+
need = Math.max(need, selectNeed(select, at));
|
|
27716
|
+
return need;
|
|
27717
|
+
}
|
|
27718
|
+
function selectNeed(select, base2) {
|
|
27719
|
+
let need = base2;
|
|
27720
|
+
for (const node of select.resolved)
|
|
27721
|
+
need = Math.max(need, nodeNeed(node, base2));
|
|
27722
|
+
for (const inner of select.from)
|
|
27723
|
+
need = Math.max(need, selectNeed(inner, base2));
|
|
27724
|
+
return need;
|
|
27725
|
+
}
|
|
27726
|
+
function expressionDepth(sql, { planner = true } = {}) {
|
|
27727
|
+
try {
|
|
27728
|
+
const reader = new Reader(tokenize3(sql), planner);
|
|
27729
|
+
const top = reader.statement();
|
|
27730
|
+
if (!reader.atEnd()) return void 0;
|
|
27731
|
+
return Math.max(reader.deepest, selectNeed(top, 0));
|
|
27732
|
+
} catch (error51) {
|
|
27733
|
+
if (error51 instanceof Unreadable) return void 0;
|
|
27734
|
+
throw error51;
|
|
27735
|
+
}
|
|
27736
|
+
}
|
|
27737
|
+
|
|
27738
|
+
// ../../packages/data-core/src/policy-index.ts
|
|
27739
|
+
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`;
|
|
27740
|
+
function indexesFromRows(rows) {
|
|
27741
|
+
const tables = /* @__PURE__ */ new Map();
|
|
27742
|
+
for (const row of rows) {
|
|
27743
|
+
const table = row.table_name;
|
|
27744
|
+
const column2 = row.column_name;
|
|
27745
|
+
if (typeof table !== "string" || typeof column2 !== "string") continue;
|
|
27746
|
+
if (Number(row.partial) !== 0) continue;
|
|
27747
|
+
const key = asciiLower(table);
|
|
27748
|
+
const columns = tables.get(key) ?? /* @__PURE__ */ new Map();
|
|
27749
|
+
tables.set(key, columns);
|
|
27750
|
+
const collations = columns.get(asciiLower(column2)) ?? [];
|
|
27751
|
+
collations.push(
|
|
27752
|
+
typeof row.collation === "string" ? row.collation.toUpperCase() : "BINARY"
|
|
27753
|
+
);
|
|
27754
|
+
columns.set(asciiLower(column2), collations);
|
|
27755
|
+
}
|
|
27756
|
+
return tables;
|
|
27757
|
+
}
|
|
27758
|
+
var STORAGE_OBJECTS_INDEXES = /* @__PURE__ */ new Map(
|
|
27759
|
+
[
|
|
27760
|
+
["id", ["BINARY"]],
|
|
27761
|
+
["bucket_id", ["BINARY"]]
|
|
27762
|
+
]
|
|
27763
|
+
);
|
|
27764
|
+
function resolve8(scopes, qualifier, name) {
|
|
27765
|
+
const column2 = asciiLower(name);
|
|
27766
|
+
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
27767
|
+
const scope = scopes[index];
|
|
27768
|
+
if (qualifier !== null && !scope.names.includes(asciiLower(qualifier)))
|
|
27769
|
+
continue;
|
|
27770
|
+
const found = scope.table.byName.get(column2);
|
|
27771
|
+
if (found) return { column: found, scope: index };
|
|
27772
|
+
if (qualifier !== null) return void 0;
|
|
27773
|
+
}
|
|
27774
|
+
return void 0;
|
|
27775
|
+
}
|
|
27776
|
+
function realtimeTopic2(scopes) {
|
|
27777
|
+
const [root] = scopes;
|
|
27778
|
+
if (root?.table.name !== REALTIME_MESSAGES) return void 0;
|
|
26470
27779
|
const column2 = root.table.byName.get("topic");
|
|
26471
27780
|
return column2 ? { column: column2, scope: 0 } : void 0;
|
|
26472
27781
|
}
|
|
@@ -26527,12 +27836,9 @@ function readsOutside(scopes, expr, depth, snapshot) {
|
|
|
26527
27836
|
}
|
|
26528
27837
|
}
|
|
26529
27838
|
function subqueryScopes2(scopes, subquery, snapshot) {
|
|
26530
|
-
const table =
|
|
27839
|
+
const table = resolveSubqueryTable(snapshot, subquery.table);
|
|
26531
27840
|
if (!table) return void 0;
|
|
26532
|
-
return [
|
|
26533
|
-
...scopes,
|
|
26534
|
-
{ table, names: [asciiLower(subquery.alias ?? table.name)] }
|
|
26535
|
-
];
|
|
27841
|
+
return [...scopes, { table, names: [asciiLower(subqueryName(subquery))] }];
|
|
26536
27842
|
}
|
|
26537
27843
|
function conjuncts(expr) {
|
|
26538
27844
|
if (expr === null) return [];
|
|
@@ -26546,24 +27852,27 @@ function columnAffinityOf(column2) {
|
|
|
26546
27852
|
if (affinity2 === "BLOB") return "blob";
|
|
26547
27853
|
return "numeric";
|
|
26548
27854
|
}
|
|
26549
|
-
function operandColumn(scopes,
|
|
27855
|
+
function operandColumn(scopes, operand) {
|
|
27856
|
+
const value = comparedValue(operand);
|
|
26550
27857
|
if (value.kind === "realtime") return realtimeTopic2(scopes);
|
|
26551
27858
|
if (value.kind !== "column") return void 0;
|
|
26552
27859
|
return resolve8(scopes, value.qualifier, value.name);
|
|
26553
27860
|
}
|
|
26554
27861
|
function valueAffinity(scopes, value) {
|
|
26555
|
-
if (value.kind === "cast") return "text";
|
|
27862
|
+
if (comparedValue(value).kind === "cast") return "text";
|
|
26556
27863
|
const found = operandColumn(scopes, value);
|
|
26557
27864
|
return found ? columnAffinityOf(found.column) : "none";
|
|
26558
27865
|
}
|
|
26559
27866
|
function valueCollation(scopes, value) {
|
|
26560
|
-
|
|
27867
|
+
const compared = comparedValue(value);
|
|
27868
|
+
if (compared.kind === "cast") return valueCollation(scopes, compared.operand);
|
|
26561
27869
|
const found = operandColumn(scopes, value);
|
|
26562
27870
|
return found ? found.column.collation : void 0;
|
|
26563
27871
|
}
|
|
26564
27872
|
function waysIn(scopes, where) {
|
|
26565
27873
|
const inner = scopes.length - 1;
|
|
26566
|
-
const own2 = (
|
|
27874
|
+
const own2 = (operand) => {
|
|
27875
|
+
const value = comparedValue(operand);
|
|
26567
27876
|
if (value.kind !== "column") return void 0;
|
|
26568
27877
|
const found = resolve8(scopes, value.qualifier, value.name);
|
|
26569
27878
|
return found?.scope === inner ? found.column : void 0;
|
|
@@ -26611,7 +27920,7 @@ function isRowid(table, column2) {
|
|
|
26611
27920
|
function blocked(table, indexes, way) {
|
|
26612
27921
|
if (isRowid(table, way.column))
|
|
26613
27922
|
return way.affinity === "text" ? "affinity" : void 0;
|
|
26614
|
-
const collations = indexes.get(asciiLower(table.name))?.get(asciiLower(way.column.name));
|
|
27923
|
+
const collations = (table.name === STORAGE_OBJECTS_TABLE ? STORAGE_OBJECTS_INDEXES : indexes.get(asciiLower(table.name)))?.get(asciiLower(way.column.name));
|
|
26615
27924
|
if (!collations || collations.length === 0) return "none";
|
|
26616
27925
|
const wanted = way.collation === void 0 ? way.column.collation : way.collation;
|
|
26617
27926
|
if (wanted !== null && !collations.includes(wanted)) return "collation";
|
|
@@ -26622,9 +27931,17 @@ function blocked(table, indexes, way) {
|
|
|
26622
27931
|
}
|
|
26623
27932
|
function problemMessage(policy, outer, table, ways, why) {
|
|
26624
27933
|
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`;
|
|
26625
|
-
const start = `Policy ${policy.name} on ${tableLabel(outer)}: its subquery on ${table
|
|
27934
|
+
const start = `Policy ${policy.name} on ${tableLabel(outer)}: its subquery on ${tableLabel(table)} ${runs}`;
|
|
26626
27935
|
const override = "or send it with allow_unindexed=1 (naro policy create --allow-unindexed) to accept a scan of every row";
|
|
26627
27936
|
const [way] = ways;
|
|
27937
|
+
if (table.name === STORAGE_OBJECTS_TABLE) {
|
|
27938
|
+
const column3 = way ? `${STORAGE_OBJECTS}.${way.column.name}` : "";
|
|
27939
|
+
if (way && why === "collation")
|
|
27940
|
+
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}.`;
|
|
27941
|
+
if (way && why === "affinity")
|
|
27942
|
+
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}.`;
|
|
27943
|
+
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}.`;
|
|
27944
|
+
}
|
|
26628
27945
|
if (!way)
|
|
26629
27946
|
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}.`;
|
|
26630
27947
|
const column2 = way.column.name;
|
|
@@ -26664,12 +27981,12 @@ function unindexedSubqueries(snapshot, indexes, policies) {
|
|
|
26664
27981
|
const ways = waysIn(inner, expr.subquery.where);
|
|
26665
27982
|
const reasons = ways.map((way) => blocked(table, indexes, way));
|
|
26666
27983
|
if (!reasons.includes(void 0)) {
|
|
26667
|
-
const order = ["none", "collation", "affinity"];
|
|
27984
|
+
const order = table.name === STORAGE_OBJECTS_TABLE ? ["collation", "affinity", "none"] : ["none", "collation", "affinity"];
|
|
26668
27985
|
const best = order.map((reason) => reasons.indexOf(reason)).find((index) => index >= 0);
|
|
26669
27986
|
const way = best === void 0 ? void 0 : ways[best];
|
|
26670
27987
|
found.push({
|
|
26671
27988
|
policy,
|
|
26672
|
-
table: table
|
|
27989
|
+
table: tableLabel(table),
|
|
26673
27990
|
message: problemMessage(
|
|
26674
27991
|
policy,
|
|
26675
27992
|
outer,
|
|
@@ -26863,6 +28180,14 @@ function privateChannelAccess(input) {
|
|
|
26863
28180
|
return { ok: true, statement: { sql, params } };
|
|
26864
28181
|
}
|
|
26865
28182
|
|
|
28183
|
+
// ../../packages/data-core/src/storage-transfer.ts
|
|
28184
|
+
function moveToPending(to) {
|
|
28185
|
+
return JSON.stringify({ moveTo: to });
|
|
28186
|
+
}
|
|
28187
|
+
function moveFromPending(from) {
|
|
28188
|
+
return JSON.stringify({ moveFrom: from });
|
|
28189
|
+
}
|
|
28190
|
+
|
|
26866
28191
|
// ../../packages/data-core/src/storage-sql.ts
|
|
26867
28192
|
var STORAGE_NONCE_MAX_DIGITS = 64;
|
|
26868
28193
|
function nonceOf(nonce) {
|
|
@@ -26992,6 +28317,73 @@ function claimOverwrite(i) {
|
|
|
26992
28317
|
[[i.lockId, i.expiresAt, i.pending, i.bucket, i.name], room.values]
|
|
26993
28318
|
);
|
|
26994
28319
|
}
|
|
28320
|
+
var MOVE_KEPT_COLUMNS = [
|
|
28321
|
+
"id",
|
|
28322
|
+
"bucket_id",
|
|
28323
|
+
"owner",
|
|
28324
|
+
"owner_id",
|
|
28325
|
+
"version",
|
|
28326
|
+
"created_at",
|
|
28327
|
+
"updated_at",
|
|
28328
|
+
"metadata"
|
|
28329
|
+
];
|
|
28330
|
+
function moveCheckRow(i) {
|
|
28331
|
+
return {
|
|
28332
|
+
row: { name: i.to },
|
|
28333
|
+
stored: { alias: STORAGE_OBJECT_ALIAS, columns: MOVE_KEPT_COLUMNS }
|
|
28334
|
+
};
|
|
28335
|
+
}
|
|
28336
|
+
function claimMove(i) {
|
|
28337
|
+
const nonce = nonceOf(i.nonce);
|
|
28338
|
+
const slotNonce = nonceOf(i.slotNonce);
|
|
28339
|
+
const source = compose(
|
|
28340
|
+
[
|
|
28341
|
+
`UPDATE ${STORAGE_OBJECTS_TABLE} AS o SET lock_id = ?, lock_expires_at = ?, pending = ? WHERE o.id = ? AND o.name = ? AND CASE WHEN NOT (`,
|
|
28342
|
+
i.selectUsing,
|
|
28343
|
+
`) 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 (`,
|
|
28344
|
+
i.updateUsing,
|
|
28345
|
+
`) THEN ${marker("denied", nonce)} WHEN (`,
|
|
28346
|
+
i.check,
|
|
28347
|
+
// The check is 1 or fails with its marker; anything else is no
|
|
28348
|
+
// check that held — refused as the update policy refuses.
|
|
28349
|
+
`) <> 1 THEN ${marker("denied", nonce)} WHEN ${PROJECT_BYTES} + o.size > ? THEN ${marker("quota", nonce)} ELSE 1 END RETURNING id, size`
|
|
28350
|
+
],
|
|
28351
|
+
[
|
|
28352
|
+
[i.lockId, i.expiresAt, moveToPending(i.to), i.id, i.from],
|
|
28353
|
+
[i.version],
|
|
28354
|
+
[i.limitBytes]
|
|
28355
|
+
]
|
|
28356
|
+
);
|
|
28357
|
+
const slot = compose(
|
|
28358
|
+
[
|
|
28359
|
+
`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`
|
|
28360
|
+
],
|
|
28361
|
+
[
|
|
28362
|
+
[
|
|
28363
|
+
i.slotId,
|
|
28364
|
+
i.to,
|
|
28365
|
+
objectParent(i.to),
|
|
28366
|
+
moveFromPending(i.from),
|
|
28367
|
+
i.id,
|
|
28368
|
+
i.lockId,
|
|
28369
|
+
i.to,
|
|
28370
|
+
i.to
|
|
28371
|
+
]
|
|
28372
|
+
]
|
|
28373
|
+
);
|
|
28374
|
+
return [source, slot];
|
|
28375
|
+
}
|
|
28376
|
+
function copySourceGuard(i) {
|
|
28377
|
+
const nonce = nonceOf(i.nonce);
|
|
28378
|
+
return compose(
|
|
28379
|
+
[
|
|
28380
|
+
`SELECT CASE WHEN EXISTS (SELECT 1 FROM ${STORAGE_OBJECTS_TABLE} AS o WHERE o.id = ? AND o.name = ? AND (`,
|
|
28381
|
+
i.using,
|
|
28382
|
+
`)) THEN 1 ELSE ${marker("source", nonce)} END AS ok`
|
|
28383
|
+
],
|
|
28384
|
+
[[i.id, i.name]]
|
|
28385
|
+
);
|
|
28386
|
+
}
|
|
26995
28387
|
function claimDelete(i) {
|
|
26996
28388
|
return compose(
|
|
26997
28389
|
[
|
|
@@ -27023,6 +28415,16 @@ function readObjects(i) {
|
|
|
27023
28415
|
[[i.bucket, JSON.stringify([...new Set(i.names)])]]
|
|
27024
28416
|
);
|
|
27025
28417
|
}
|
|
28418
|
+
function readTransferSource(i) {
|
|
28419
|
+
return compose(
|
|
28420
|
+
[
|
|
28421
|
+
`SELECT o.id, o.version, o.size, o.metadata FROM ${STORAGE_OBJECTS_TABLE} AS o WHERE o.bucket_id = ? AND o.name = ? AND (`,
|
|
28422
|
+
i.using,
|
|
28423
|
+
")"
|
|
28424
|
+
],
|
|
28425
|
+
[[i.bucket, i.name]]
|
|
28426
|
+
);
|
|
28427
|
+
}
|
|
27026
28428
|
var SORT_COLUMNS = {
|
|
27027
28429
|
name: "name",
|
|
27028
28430
|
created_at: "created_at",
|
|
@@ -27114,7 +28516,7 @@ function storageUsing(input) {
|
|
|
27114
28516
|
assertFragmentAlias(alias, "storageUsing");
|
|
27115
28517
|
const on = target(snapshot);
|
|
27116
28518
|
if ("ok" in on) return on;
|
|
27117
|
-
const status =
|
|
28519
|
+
const status = storageReadySql(alias);
|
|
27118
28520
|
if (context.role === "service")
|
|
27119
28521
|
return { ok: true, fragment: { sql: status, params: [] } };
|
|
27120
28522
|
try {
|
|
@@ -27204,12 +28606,12 @@ var CONTEXTS = [
|
|
|
27204
28606
|
userId: "naro-policy-check",
|
|
27205
28607
|
email: "check@naro.invalid",
|
|
27206
28608
|
emailVerified: true
|
|
27207
|
-
}
|
|
28609
|
+
},
|
|
28610
|
+
{ role: "authenticated", userId: "naro-policy-check" }
|
|
27208
28611
|
];
|
|
27209
28612
|
function probes(table, context, owners) {
|
|
27210
28613
|
const writable = table.columns.filter((column2) => !column2.generated);
|
|
27211
|
-
const
|
|
27212
|
-
const filters = first ? [{ operator: "is", column: first.name, value: null }] : [];
|
|
28614
|
+
const filters = leastFilters(table.columns[0]?.name);
|
|
27213
28615
|
const row = Object.fromEntries(
|
|
27214
28616
|
writable.map((column2) => [
|
|
27215
28617
|
column2.name,
|
|
@@ -27217,7 +28619,7 @@ function probes(table, context, owners) {
|
|
|
27217
28619
|
])
|
|
27218
28620
|
);
|
|
27219
28621
|
const queries = [
|
|
27220
|
-
{ version: 1, action: "select", table: table.name, columns: "*" },
|
|
28622
|
+
{ version: 1, action: "select", table: table.name, columns: "*", filters },
|
|
27221
28623
|
{ version: 1, action: "insert", table: table.name, rows: [row] },
|
|
27222
28624
|
{
|
|
27223
28625
|
version: 1,
|
|
@@ -27240,16 +28642,35 @@ function probes(table, context, owners) {
|
|
|
27240
28642
|
}
|
|
27241
28643
|
var STORAGE_PROBE_ALIAS = "o";
|
|
27242
28644
|
var STORAGE_PROBE_NONCE = "f".repeat(STORAGE_NONCE_MAX_DIGITS);
|
|
27243
|
-
var
|
|
27244
|
-
|
|
27245
|
-
|
|
27246
|
-
|
|
27247
|
-
|
|
28645
|
+
var UNVERIFIED = {
|
|
28646
|
+
role: "authenticated",
|
|
28647
|
+
userId: "naro-policy-check",
|
|
28648
|
+
emailVerified: false
|
|
28649
|
+
};
|
|
28650
|
+
function depthContexts(contexts, claims) {
|
|
28651
|
+
if (claims) return [...contexts, UNVERIFIED];
|
|
28652
|
+
return contexts.filter(
|
|
28653
|
+
(context) => context.role !== "authenticated" || context.emailVerified === true
|
|
28654
|
+
);
|
|
28655
|
+
}
|
|
28656
|
+
function readsClaims(policies) {
|
|
28657
|
+
return policies.some(
|
|
28658
|
+
(policy) => [policy.using, policy.check].some(
|
|
28659
|
+
(text2) => text2 !== null && /auth\s*\.\s*jwt\s*\(/i.test(text2)
|
|
28660
|
+
)
|
|
28661
|
+
);
|
|
28662
|
+
}
|
|
28663
|
+
function callerName(context) {
|
|
28664
|
+
if (context.role !== "authenticated") return context.role;
|
|
28665
|
+
if (context.emailVerified === true) return "authenticated";
|
|
28666
|
+
return context.emailVerified === false ? "authenticated with no verified email" : "authenticated with no email claims";
|
|
28667
|
+
}
|
|
28668
|
+
function storageRefusals(snapshot, table, policies, measuring, measured) {
|
|
27248
28669
|
const refusals = [];
|
|
27249
28670
|
const row = Object.fromEntries(
|
|
27250
28671
|
table.columns.map((column2) => [column2.name, ""])
|
|
27251
28672
|
);
|
|
27252
|
-
for (const context of
|
|
28673
|
+
for (const context of /* @__PURE__ */ new Set([...CONTEXTS, ...measuring])) {
|
|
27253
28674
|
const using = (commands) => {
|
|
27254
28675
|
const result = storageUsing({
|
|
27255
28676
|
snapshot,
|
|
@@ -27285,25 +28706,33 @@ function storageRefusals(snapshot, table, policies) {
|
|
|
27285
28706
|
overwriteCheckRow({ metadata: "", now: "", version: "" }),
|
|
27286
28707
|
true
|
|
27287
28708
|
);
|
|
27288
|
-
|
|
28709
|
+
const moveCheck = check2("update", moveCheckRow({ to: "" }), true);
|
|
28710
|
+
for (const { label: label2, statement: statement2 } of storageStatements(
|
|
27289
28711
|
select,
|
|
27290
28712
|
update,
|
|
27291
28713
|
remove,
|
|
27292
28714
|
[insertCheck, upsertCheck],
|
|
27293
|
-
updateCheck
|
|
28715
|
+
updateCheck,
|
|
28716
|
+
moveCheck
|
|
27294
28717
|
)) {
|
|
27295
28718
|
const problem = storageStatementProblem(statement2, "select");
|
|
27296
28719
|
if (problem) refusals.push(problem);
|
|
28720
|
+
else if (measuring.includes(context))
|
|
28721
|
+
measured(label2, statement2.sql, callerName(context));
|
|
27297
28722
|
}
|
|
27298
28723
|
}
|
|
27299
28724
|
return refusals;
|
|
27300
28725
|
}
|
|
27301
|
-
function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
28726
|
+
function storageStatements(select, update, remove, insertChecks, updateCheck, moveCheck) {
|
|
27302
28727
|
const statements = [];
|
|
28728
|
+
const add = (label2, ...made) => {
|
|
28729
|
+
for (const statement2 of made) statements.push({ label: label2, statement: statement2 });
|
|
28730
|
+
};
|
|
27303
28731
|
const value = "";
|
|
27304
|
-
for (const insertCheck of insertChecks)
|
|
28732
|
+
for (const [index, insertCheck] of insertChecks.entries())
|
|
27305
28733
|
if (insertCheck)
|
|
27306
|
-
|
|
28734
|
+
add(
|
|
28735
|
+
index === 0 ? "an upload's claim" : "an upsert's claim",
|
|
27307
28736
|
claimUpload({
|
|
27308
28737
|
id: value,
|
|
27309
28738
|
bucket: value,
|
|
@@ -27319,7 +28748,8 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27319
28748
|
})
|
|
27320
28749
|
);
|
|
27321
28750
|
if (select && update && updateCheck)
|
|
27322
|
-
|
|
28751
|
+
add(
|
|
28752
|
+
"an overwrite's claim",
|
|
27323
28753
|
claimOverwrite({
|
|
27324
28754
|
bucket: value,
|
|
27325
28755
|
name: value,
|
|
@@ -27334,8 +28764,28 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27334
28764
|
nonce: STORAGE_PROBE_NONCE
|
|
27335
28765
|
})
|
|
27336
28766
|
);
|
|
28767
|
+
if (select && update && moveCheck)
|
|
28768
|
+
add(
|
|
28769
|
+
"a move's claim",
|
|
28770
|
+
...claimMove({
|
|
28771
|
+
id: value,
|
|
28772
|
+
version: value,
|
|
28773
|
+
from: value,
|
|
28774
|
+
to: value,
|
|
28775
|
+
slotId: value,
|
|
28776
|
+
lockId: value,
|
|
28777
|
+
expiresAt: value,
|
|
28778
|
+
limitBytes: 0,
|
|
28779
|
+
selectUsing: select,
|
|
28780
|
+
updateUsing: update,
|
|
28781
|
+
check: moveCheck,
|
|
28782
|
+
nonce: STORAGE_PROBE_NONCE,
|
|
28783
|
+
slotNonce: STORAGE_PROBE_NONCE
|
|
28784
|
+
})
|
|
28785
|
+
);
|
|
27337
28786
|
if (remove)
|
|
27338
|
-
|
|
28787
|
+
add(
|
|
28788
|
+
"a delete's claim",
|
|
27339
28789
|
claimDelete({
|
|
27340
28790
|
bucket: value,
|
|
27341
28791
|
names: [],
|
|
@@ -27344,13 +28794,27 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27344
28794
|
expiresAt: value
|
|
27345
28795
|
})
|
|
27346
28796
|
);
|
|
27347
|
-
if (select)
|
|
27348
|
-
|
|
27349
|
-
|
|
27350
|
-
|
|
27351
|
-
|
|
27352
|
-
|
|
27353
|
-
|
|
28797
|
+
if (select) {
|
|
28798
|
+
add("a read", readObject2({ bucket: value, name: value, using: select }));
|
|
28799
|
+
add(
|
|
28800
|
+
"a read of several objects",
|
|
28801
|
+
readObjects({ bucket: value, names: [], using: select })
|
|
28802
|
+
);
|
|
28803
|
+
add(
|
|
28804
|
+
"a move's or a copy's read",
|
|
28805
|
+
readTransferSource({ bucket: value, name: value, using: select })
|
|
28806
|
+
);
|
|
28807
|
+
add(
|
|
28808
|
+
"a copy's guard",
|
|
28809
|
+
copySourceGuard({
|
|
28810
|
+
id: value,
|
|
28811
|
+
name: value,
|
|
28812
|
+
using: select,
|
|
28813
|
+
nonce: STORAGE_PROBE_NONCE
|
|
28814
|
+
})
|
|
28815
|
+
);
|
|
28816
|
+
add(
|
|
28817
|
+
"a listing",
|
|
27354
28818
|
listObjects({
|
|
27355
28819
|
bucket: value,
|
|
27356
28820
|
prefix: "p",
|
|
@@ -27361,6 +28825,7 @@ function storageStatements(select, update, remove, insertChecks, updateCheck) {
|
|
|
27361
28825
|
using: select
|
|
27362
28826
|
})
|
|
27363
28827
|
);
|
|
28828
|
+
}
|
|
27364
28829
|
return statements;
|
|
27365
28830
|
}
|
|
27366
28831
|
var REALTIME_PROBE_CHANNEL = "naro-policy-check";
|
|
@@ -27373,13 +28838,13 @@ function joinReason(reason) {
|
|
|
27373
28838
|
return JOIN_STATEMENT;
|
|
27374
28839
|
return reason;
|
|
27375
28840
|
}
|
|
27376
|
-
function realtimeRefusals(snapshot, policies) {
|
|
28841
|
+
function realtimeRefusals(snapshot, policies, measuring, measured) {
|
|
27377
28842
|
const refusals = [];
|
|
27378
28843
|
const applying = (command) => policies.filter(
|
|
27379
28844
|
(policy) => asciiLower(policy.table) !== REALTIME_MESSAGES || policy.command === command || policy.command === "all"
|
|
27380
28845
|
);
|
|
27381
28846
|
for (const set2 of [policies, applying("select"), applying("insert")])
|
|
27382
|
-
for (const context of
|
|
28847
|
+
for (const context of /* @__PURE__ */ new Set([...CONTEXTS, ...measuring])) {
|
|
27383
28848
|
const plan = privateChannelAccess({
|
|
27384
28849
|
snapshot,
|
|
27385
28850
|
context,
|
|
@@ -27387,7 +28852,11 @@ function realtimeRefusals(snapshot, policies) {
|
|
|
27387
28852
|
channel: REALTIME_PROBE_CHANNEL,
|
|
27388
28853
|
nonce: REALTIME_PROBE_NONCE
|
|
27389
28854
|
});
|
|
27390
|
-
if (plan.ok)
|
|
28855
|
+
if (plan.ok) {
|
|
28856
|
+
if ("statement" in plan && measuring.includes(context))
|
|
28857
|
+
measured("a join", plan.statement.sql, callerName(context));
|
|
28858
|
+
continue;
|
|
28859
|
+
}
|
|
27391
28860
|
const reason = joinReason(plan.reason);
|
|
27392
28861
|
refusals.push(
|
|
27393
28862
|
reason === void 0 ? { error: plan.error } : { error: plan.error, reason }
|
|
@@ -27429,9 +28898,48 @@ function namedInRefusal(message, policies) {
|
|
|
27429
28898
|
}
|
|
27430
28899
|
return found;
|
|
27431
28900
|
}
|
|
27432
|
-
function
|
|
28901
|
+
function probeLabel(query) {
|
|
28902
|
+
switch (query.action) {
|
|
28903
|
+
case "select":
|
|
28904
|
+
return "a select";
|
|
28905
|
+
case "insert":
|
|
28906
|
+
return query.returning === void 0 ? "an insert" : "an insert with returning";
|
|
28907
|
+
case "update":
|
|
28908
|
+
return "an update";
|
|
28909
|
+
case "delete":
|
|
28910
|
+
return "a delete";
|
|
28911
|
+
}
|
|
28912
|
+
}
|
|
28913
|
+
function inlinedTables(sql, probed) {
|
|
28914
|
+
const names = /* @__PURE__ */ new Set();
|
|
28915
|
+
for (const [, written] of sql.matchAll(
|
|
28916
|
+
/FROM ("(?:[^"]|"")*"|[A-Za-z_][A-Za-z0-9_$]*) AS "?_naro_s\d+"?/g
|
|
28917
|
+
)) {
|
|
28918
|
+
if (written === void 0) continue;
|
|
28919
|
+
const name = written.startsWith('"') ? written.slice(1, -1).replaceAll('""', '"') : written;
|
|
28920
|
+
const label2 = name === STORAGE_OBJECTS_TABLE ? STORAGE_OBJECTS : name;
|
|
28921
|
+
if (label2 !== probed) names.add(label2);
|
|
28922
|
+
}
|
|
28923
|
+
return [...names];
|
|
28924
|
+
}
|
|
28925
|
+
function listed(names) {
|
|
28926
|
+
if (names.length < 2) return names.join("");
|
|
28927
|
+
return `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
28928
|
+
}
|
|
28929
|
+
function tooDeepMessage({
|
|
28930
|
+
table,
|
|
28931
|
+
label: label2,
|
|
28932
|
+
caller,
|
|
28933
|
+
depth,
|
|
28934
|
+
inlined
|
|
28935
|
+
}) {
|
|
28936
|
+
const others = inlined.length === 0 ? "" : `, with those of ${listed(inlined)} that they read,`;
|
|
28937
|
+
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.`;
|
|
28938
|
+
}
|
|
28939
|
+
function problems(snapshot, policies, options, claims) {
|
|
27433
28940
|
const found = /* @__PURE__ */ new Map();
|
|
27434
28941
|
const loaded = loadNamed(policies);
|
|
28942
|
+
const measuring = depthContexts(CONTEXTS, claims ?? readsClaims(loaded));
|
|
27435
28943
|
for (const policy of loaded)
|
|
27436
28944
|
if (policy.invalid !== null)
|
|
27437
28945
|
found.set(`read:${policy.table}:${policy.name}`, {
|
|
@@ -27493,24 +29001,30 @@ function problems(snapshot, policies, options) {
|
|
|
27493
29001
|
}
|
|
27494
29002
|
}
|
|
27495
29003
|
const probe = loadNamed(
|
|
27496
|
-
[...tables.values()].
|
|
27497
|
-
|
|
27498
|
-
|
|
27499
|
-
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
27505
|
-
|
|
29004
|
+
[...tables.values()].flatMap((table) => {
|
|
29005
|
+
const roles = POLICY_ROLES.filter(
|
|
29006
|
+
(role) => applicablePolicies(loaded, table.name, "select", role).permissive.length === 0
|
|
29007
|
+
);
|
|
29008
|
+
return roles.length === 0 ? [] : [
|
|
29009
|
+
{
|
|
29010
|
+
table: table.name,
|
|
29011
|
+
name: "naro_policy_check",
|
|
29012
|
+
permissive: true,
|
|
29013
|
+
command: "select",
|
|
29014
|
+
roles,
|
|
29015
|
+
using: "true",
|
|
29016
|
+
check: null,
|
|
29017
|
+
owner: null
|
|
29018
|
+
}
|
|
29019
|
+
];
|
|
29020
|
+
})
|
|
27506
29021
|
);
|
|
27507
|
-
const compiled = [...loaded, ...probe];
|
|
27508
29022
|
const refused = (probed, error51, reason) => {
|
|
27509
29023
|
if (error51.code !== "POLICY_INVALID") return;
|
|
27510
|
-
const message = reason === void 0 ? error51.message : `${error51.message.replace(/\.$/, "")}: ${reason}.`;
|
|
29024
|
+
const message = reason === STORAGE_UNAVAILABLE_REASON ? STORAGE_UNAVAILABLE_MESSAGE : reason === void 0 ? error51.message : `${error51.message.replace(/\.$/, "")}: ${reason}.`;
|
|
27511
29025
|
const named = namedInRefusal(error51.message, loaded);
|
|
27512
29026
|
const blamed2 = named === void 0 ? probed : tableLabelOf(snapshot, named.table);
|
|
27513
|
-
const key = `${blamed2}:${message}`;
|
|
29027
|
+
const key = reason === STORAGE_UNAVAILABLE_REASON ? `${blamed2}:${named?.name ?? ""}:${message}` : `${blamed2}:${message}`;
|
|
27514
29028
|
if (!found.has(key))
|
|
27515
29029
|
found.set(key, {
|
|
27516
29030
|
problem: {
|
|
@@ -27522,12 +29036,29 @@ function problems(snapshot, policies, options) {
|
|
|
27522
29036
|
policy: ""
|
|
27523
29037
|
});
|
|
27524
29038
|
};
|
|
29039
|
+
const deepest = /* @__PURE__ */ new Map();
|
|
29040
|
+
const measured = (table) => (label2, sql, caller) => {
|
|
29041
|
+
const depth = expressionDepth(sql);
|
|
29042
|
+
const key = `depth:${table}:${label2}:${caller}`;
|
|
29043
|
+
if (depth !== void 0 && depth > (deepest.get(key)?.depth ?? D1_EXPRESSION_DEPTH))
|
|
29044
|
+
deepest.set(key, {
|
|
29045
|
+
table,
|
|
29046
|
+
label: label2,
|
|
29047
|
+
caller,
|
|
29048
|
+
depth,
|
|
29049
|
+
inlined: inlinedTables(sql, table)
|
|
29050
|
+
});
|
|
29051
|
+
};
|
|
27525
29052
|
for (const table of tables.values()) {
|
|
27526
29053
|
const owners = new Set(
|
|
27527
29054
|
policiesOn(loaded, table.name).flatMap(
|
|
27528
29055
|
(policy) => policy.owner ? [asciiLower(policy.owner)] : []
|
|
27529
29056
|
)
|
|
27530
29057
|
);
|
|
29058
|
+
const compiled = [
|
|
29059
|
+
...loaded,
|
|
29060
|
+
...probe.filter((policy) => policy.table === table.name)
|
|
29061
|
+
];
|
|
27531
29062
|
for (const context of CONTEXTS)
|
|
27532
29063
|
for (const query of probes(table, context, owners)) {
|
|
27533
29064
|
const decision = decideQuery(context, query, compiled, table.name);
|
|
@@ -27538,13 +29069,27 @@ function problems(snapshot, policies, options) {
|
|
|
27538
29069
|
};
|
|
27539
29070
|
if (!result.ok) refused(table.name, result.error, result.reason);
|
|
27540
29071
|
}
|
|
29072
|
+
for (const context of measuring)
|
|
29073
|
+
for (const query of probes(table, context, owners)) {
|
|
29074
|
+
const real = decideQuery(context, query, loaded, table.name);
|
|
29075
|
+
const made = real.allow ? compile(query, snapshot, real, context) : void 0;
|
|
29076
|
+
if (made?.ok)
|
|
29077
|
+
for (const statement2 of made.statements)
|
|
29078
|
+
measured(table.name)(
|
|
29079
|
+
probeLabel(query),
|
|
29080
|
+
statement2.sql,
|
|
29081
|
+
callerName(context)
|
|
29082
|
+
);
|
|
29083
|
+
}
|
|
27541
29084
|
}
|
|
27542
29085
|
if (policiesOn(loaded, STORAGE_OBJECTS).length > 0) {
|
|
27543
29086
|
if (snapshot.storage)
|
|
27544
29087
|
for (const { error: error51, reason } of storageRefusals(
|
|
27545
29088
|
snapshot,
|
|
27546
29089
|
snapshot.storage,
|
|
27547
|
-
loaded
|
|
29090
|
+
loaded,
|
|
29091
|
+
measuring,
|
|
29092
|
+
measured(STORAGE_OBJECTS)
|
|
27548
29093
|
))
|
|
27549
29094
|
refused(STORAGE_OBJECTS, error51, reason);
|
|
27550
29095
|
else
|
|
@@ -27558,8 +29103,22 @@ function problems(snapshot, policies, options) {
|
|
|
27558
29103
|
});
|
|
27559
29104
|
}
|
|
27560
29105
|
if (policiesOn(loaded, REALTIME_MESSAGES).length > 0)
|
|
27561
|
-
for (const { error: error51, reason } of realtimeRefusals(
|
|
29106
|
+
for (const { error: error51, reason } of realtimeRefusals(
|
|
29107
|
+
snapshot,
|
|
29108
|
+
loaded,
|
|
29109
|
+
measuring,
|
|
29110
|
+
measured(REALTIME_MESSAGES)
|
|
29111
|
+
))
|
|
27562
29112
|
refused(REALTIME_MESSAGES, error51, reason);
|
|
29113
|
+
for (const [key, tooDeep] of deepest)
|
|
29114
|
+
found.set(key, {
|
|
29115
|
+
problem: {
|
|
29116
|
+
table: tooDeep.table,
|
|
29117
|
+
message: tooDeepMessage(tooDeep),
|
|
29118
|
+
kind: "compile"
|
|
29119
|
+
},
|
|
29120
|
+
policy: ""
|
|
29121
|
+
});
|
|
27563
29122
|
if (options.indexes && options.allowUnindexed !== true)
|
|
27564
29123
|
for (const problem of unindexedSubqueries(
|
|
27565
29124
|
snapshot,
|
|
@@ -27877,10 +29436,10 @@ function isBlankStart(char) {
|
|
|
27877
29436
|
function isBlank(char) {
|
|
27878
29437
|
return isBlankStart(char) || char === "\v";
|
|
27879
29438
|
}
|
|
27880
|
-
function
|
|
29439
|
+
function isWordStart2(char) {
|
|
27881
29440
|
return /[A-Za-z_]/.test(char) || char.charCodeAt(0) >= 128;
|
|
27882
29441
|
}
|
|
27883
|
-
function
|
|
29442
|
+
function isWordPart2(char) {
|
|
27884
29443
|
return /[A-Za-z0-9_$]/.test(char) || char.charCodeAt(0) >= 128;
|
|
27885
29444
|
}
|
|
27886
29445
|
function skipBlanks(text2, from) {
|
|
@@ -27934,9 +29493,9 @@ function headTokens(text2) {
|
|
|
27934
29493
|
index = close + 1;
|
|
27935
29494
|
} else if ((char === "x" || char === "X") && text2.charAt(index + 1) === "'") {
|
|
27936
29495
|
break;
|
|
27937
|
-
} else if (
|
|
29496
|
+
} else if (isWordStart2(char)) {
|
|
27938
29497
|
let end = index + 1;
|
|
27939
|
-
while (end < text2.length &&
|
|
29498
|
+
while (end < text2.length && isWordPart2(text2.charAt(end))) end++;
|
|
27940
29499
|
tokens.push({ kind: "word", text: text2.slice(index, end) });
|
|
27941
29500
|
index = end;
|
|
27942
29501
|
} else if (char === "." && !/[0-9]/.test(text2.charAt(index + 1))) {
|
|
@@ -27952,6 +29511,10 @@ var NO_NAME = {
|
|
|
27952
29511
|
kind: "unreadable",
|
|
27953
29512
|
reason: "the table name could not be read"
|
|
27954
29513
|
};
|
|
29514
|
+
var NO_COLUMN = {
|
|
29515
|
+
kind: "unreadable",
|
|
29516
|
+
reason: "the column name could not be read"
|
|
29517
|
+
};
|
|
27955
29518
|
var OTHER_SCHEMA = {
|
|
27956
29519
|
kind: "unreadable",
|
|
27957
29520
|
reason: "a table in another schema"
|
|
@@ -27977,8 +29540,9 @@ var Head = class {
|
|
|
27977
29540
|
return token.kind === "word" || token.kind === "name" ? token.text : void 0;
|
|
27978
29541
|
}
|
|
27979
29542
|
/**
|
|
27980
|
-
* SQLite's `fullname`. `main.` is the database a migration writes
|
|
27981
|
-
*
|
|
29543
|
+
* SQLite's `fullname`. `main.` is the database a migration writes: the
|
|
29544
|
+
* name goes without it, and `main` says it was written (known-issues
|
|
29545
|
+
* #226); `temp.` (in any case, quoted or not, as sqlite3FindDbName
|
|
27982
29546
|
* matches it) is a table that goes with the connection; `public.` is
|
|
27983
29547
|
* what a Postgres migration writes, said in its own words; any other
|
|
27984
29548
|
* schema is one D1 does not have.
|
|
@@ -27987,17 +29551,21 @@ var Head = class {
|
|
|
27987
29551
|
const first = this.nameAt(at);
|
|
27988
29552
|
if (first === void 0) return { ok: false, head: NO_NAME };
|
|
27989
29553
|
if (this.token(at + 1).kind !== "dot")
|
|
27990
|
-
return { ok: true, name: first, next: at + 1 };
|
|
29554
|
+
return { ok: true, name: first, next: at + 1, main: false };
|
|
27991
29555
|
const second = this.nameAt(at + 2);
|
|
27992
29556
|
if (second === void 0) return { ok: false, head: NO_NAME };
|
|
27993
29557
|
const schema = asciiLower(first);
|
|
27994
|
-
if (schema === "main")
|
|
29558
|
+
if (schema === "main")
|
|
29559
|
+
return { ok: true, name: second, next: at + 3, main: true };
|
|
27995
29560
|
let head = OTHER_SCHEMA;
|
|
27996
29561
|
if (schema === "temp") head = OTHER;
|
|
27997
29562
|
else if (schema === "public") head = PUBLIC_SCHEMA;
|
|
27998
29563
|
return { ok: false, head, next: at + 3 };
|
|
27999
29564
|
}
|
|
28000
29565
|
};
|
|
29566
|
+
function inMain(read) {
|
|
29567
|
+
return read.main ? { main: true } : {};
|
|
29568
|
+
}
|
|
28001
29569
|
function readCreate2(head) {
|
|
28002
29570
|
if (head.is(1, "policy")) return { kind: "create-policy" };
|
|
28003
29571
|
if (head.is(1, "temp") || head.is(1, "temporary")) return OTHER;
|
|
@@ -28021,9 +29589,24 @@ function readDrop2(head) {
|
|
|
28021
29589
|
at += 2;
|
|
28022
29590
|
}
|
|
28023
29591
|
const table = head.table(at);
|
|
28024
|
-
return table.ok ? { kind: "drop-table", table: table.name } : table.head;
|
|
29592
|
+
return table.ok ? { kind: "drop-table", table: table.name, ...inMain(table) } : table.head;
|
|
28025
29593
|
}
|
|
28026
29594
|
var RLS_ACTIONS = ["enable", "disable", "force", "no"];
|
|
29595
|
+
function readRenameColumn(head, table, at) {
|
|
29596
|
+
let from = at;
|
|
29597
|
+
if (head.is(from, "column") && head.nameAt(from + 1) !== void 0 && head.is(from + 2, "to"))
|
|
29598
|
+
from += 1;
|
|
29599
|
+
const column2 = head.nameAt(from);
|
|
29600
|
+
if (column2 === void 0 || !head.is(from + 1, "to")) return NO_COLUMN;
|
|
29601
|
+
const to = head.nameAt(from + 2);
|
|
29602
|
+
return to === void 0 ? NO_COLUMN : {
|
|
29603
|
+
kind: "rename-column",
|
|
29604
|
+
table: table.name,
|
|
29605
|
+
column: column2,
|
|
29606
|
+
to,
|
|
29607
|
+
...inMain(table)
|
|
29608
|
+
};
|
|
29609
|
+
}
|
|
28027
29610
|
function readAlter(head) {
|
|
28028
29611
|
if (head.is(1, "policy")) return { kind: "alter-policy" };
|
|
28029
29612
|
if (!head.is(1, "table")) return OTHER;
|
|
@@ -28033,9 +29616,18 @@ function readAlter(head) {
|
|
|
28033
29616
|
if (head.is(action, "rename") && head.is(action + 1, "to")) {
|
|
28034
29617
|
if (!sqlite.ok) return sqlite.head;
|
|
28035
29618
|
const to = head.table(action + 2);
|
|
28036
|
-
return to.ok ? {
|
|
29619
|
+
return to.ok ? {
|
|
29620
|
+
kind: "rename-table",
|
|
29621
|
+
table: sqlite.name,
|
|
29622
|
+
to: to.name,
|
|
29623
|
+
...inMain(sqlite)
|
|
29624
|
+
} : to.head;
|
|
29625
|
+
}
|
|
29626
|
+
if (head.is(action, "rename")) {
|
|
29627
|
+
if (!sqlite.ok) return sqlite.head;
|
|
29628
|
+
return readRenameColumn(head, sqlite, action + 1);
|
|
28037
29629
|
}
|
|
28038
|
-
if (head.is(action, "
|
|
29630
|
+
if (head.is(action, "add") || head.is(action, "drop"))
|
|
28039
29631
|
return sqlite.ok ? { kind: "alter-table", table: sqlite.name } : sqlite.head;
|
|
28040
29632
|
}
|
|
28041
29633
|
let at = 2;
|
|
@@ -28060,6 +29652,130 @@ function readSqlHead(text2) {
|
|
|
28060
29652
|
return OTHER;
|
|
28061
29653
|
}
|
|
28062
29654
|
|
|
29655
|
+
// ../../packages/data-core/src/system-sql.ts
|
|
29656
|
+
async function convertLegacyPolicies(db, found) {
|
|
29657
|
+
const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
|
|
29658
|
+
if (rows.length === 0) return [];
|
|
29659
|
+
const read = rows.map((row) => ({
|
|
29660
|
+
policy: policyFromRow(row),
|
|
29661
|
+
asRead: legacyRowAsRead(row)
|
|
29662
|
+
}));
|
|
29663
|
+
const inForce = new Set(
|
|
29664
|
+
legacyInForce(
|
|
29665
|
+
read.flatMap(({ policy }) => policy ? [policy] : []),
|
|
29666
|
+
await readTableNames(db)
|
|
29667
|
+
)
|
|
29668
|
+
);
|
|
29669
|
+
const statements = [];
|
|
29670
|
+
for (const { policy, asRead } of read) {
|
|
29671
|
+
if (!policy || !asRead) continue;
|
|
29672
|
+
if (inForce.has(policy)) {
|
|
29673
|
+
for (const named of namedFromLegacy(policy))
|
|
29674
|
+
statements.push({
|
|
29675
|
+
sql: POLICY_SQL.insertConverted,
|
|
29676
|
+
params: [...policyValues(named), ...asRead]
|
|
29677
|
+
});
|
|
29678
|
+
found.unreadable.push(...unreadableConversions(policy));
|
|
29679
|
+
}
|
|
29680
|
+
statements.push({ sql: POLICY_SQL.markConverted, params: asRead });
|
|
29681
|
+
}
|
|
29682
|
+
return statements;
|
|
29683
|
+
}
|
|
29684
|
+
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')))";
|
|
29685
|
+
var STORAGE_SUBQUERIES_VERSION = 7;
|
|
29686
|
+
var STORAGE_SUBQUERIES_NAME = "storage_subquery_policies";
|
|
29687
|
+
var STORAGE_SUBQUERIES_SQL = `SELECT version FROM _naro_migrations WHERE version = ${String(STORAGE_SUBQUERIES_VERSION)} AND name = '${STORAGE_SUBQUERIES_NAME}'`;
|
|
29688
|
+
var SYSTEM_MIGRATIONS = [
|
|
29689
|
+
{
|
|
29690
|
+
version: 1,
|
|
29691
|
+
name: "system_tables",
|
|
29692
|
+
statements: [
|
|
29693
|
+
MIGRATIONS_TABLE,
|
|
29694
|
+
"CREATE TABLE IF NOT EXISTS _naro_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')))",
|
|
29695
|
+
// Spec §11.2, as written there.
|
|
29696
|
+
"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')))"
|
|
29697
|
+
]
|
|
29698
|
+
},
|
|
29699
|
+
{
|
|
29700
|
+
// Spec P2-2 §3.1: an action whose rule column says 'condition' reads its
|
|
29701
|
+
// condition from these. A worker from before them reads 'condition' as
|
|
29702
|
+
// an unknown rule — none.
|
|
29703
|
+
version: 2,
|
|
29704
|
+
name: "policy_conditions",
|
|
29705
|
+
statements: [],
|
|
29706
|
+
addColumns: ["select", "insert", "update", "delete"].map((action) => ({
|
|
29707
|
+
table: "_naro_policies",
|
|
29708
|
+
column: `${action}_condition`,
|
|
29709
|
+
definition: "TEXT"
|
|
29710
|
+
}))
|
|
29711
|
+
},
|
|
29712
|
+
{
|
|
29713
|
+
// Spec P2-6 §11: the Naro worker's sign-in handoff. A provider sign-in's
|
|
29714
|
+
// flow (state → the app's redirect and the browser's PKCE challenge), the
|
|
29715
|
+
// one-time codes a redirect hands the app, the sends the per-address
|
|
29716
|
+
// email limit counts, and (adversarial review I3) the unconfirmed
|
|
29717
|
+
// accounts someone else also tried to sign up as. Times are epoch
|
|
29718
|
+
// milliseconds.
|
|
29719
|
+
version: 3,
|
|
29720
|
+
name: "auth_handoff",
|
|
29721
|
+
statements: [
|
|
29722
|
+
"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)",
|
|
29723
|
+
"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)",
|
|
29724
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_email_log (address_hash TEXT NOT NULL, sent_at INTEGER NOT NULL)",
|
|
29725
|
+
"CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
|
|
29726
|
+
"CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
|
|
29727
|
+
]
|
|
29728
|
+
},
|
|
29729
|
+
{
|
|
29730
|
+
// P2-7 §9: named policies, many per table. The old rows are converted
|
|
29731
|
+
// in the same batch and their rules set to a word old workers read as
|
|
29732
|
+
// `none`.
|
|
29733
|
+
version: 4,
|
|
29734
|
+
name: "named_policies",
|
|
29735
|
+
statements: [NAMED_POLICY_TABLE_SQL],
|
|
29736
|
+
compute: convertLegacyPolicies
|
|
29737
|
+
},
|
|
29738
|
+
{
|
|
29739
|
+
// Storage (first-release design §6): the buckets and the objects' index
|
|
29740
|
+
// with their counter triggers. Every backend gets them, Storage on or
|
|
29741
|
+
// not — what turns it on is the worker's R2 binding. A worker from
|
|
29742
|
+
// before them never reads them.
|
|
29743
|
+
version: 5,
|
|
29744
|
+
name: "storage",
|
|
29745
|
+
statements: [...STORAGE_TABLES_SQL]
|
|
29746
|
+
},
|
|
29747
|
+
{
|
|
29748
|
+
// Storage 3-1b (known-issues #107, first-release design §14): the mark
|
|
29749
|
+
// a bucket's removal sets before it empties the bucket. The claims and
|
|
29750
|
+
// the worker's bucket reads take a marked bucket as not there. A worker
|
|
29751
|
+
// from before it never reads the column.
|
|
29752
|
+
version: 6,
|
|
29753
|
+
name: "storage_bucket_deleting",
|
|
29754
|
+
statements: [],
|
|
29755
|
+
addColumns: [STORAGE_BUCKET_DELETING_COLUMN]
|
|
29756
|
+
},
|
|
29757
|
+
{
|
|
29758
|
+
// Storage 3-1j (plan correction (p)): no schema — the mark that a worker
|
|
29759
|
+
// which reads storage.objects in a policy's subquery has migrated this
|
|
29760
|
+
// database. Only the Naro worker's own `internal/migrate` applies system
|
|
29761
|
+
// migrations, so the row is there once such a worker has run, and stays:
|
|
29762
|
+
// every later worker reads them too — as long as the worker bundle does
|
|
29763
|
+
// not go back. Deploying web from before 3-1j makes the next upgrade
|
|
29764
|
+
// put an older worker back, and a run that lost its lease may upload
|
|
29765
|
+
// one late; such a policy then answers 500 POLICY_INVALID (closed, no
|
|
29766
|
+
// rows) until it is dropped or the newer worker is uploaded again
|
|
29767
|
+
// (3-1j 최종 리뷰). Migration 5 alone cannot tell —
|
|
29768
|
+
// backends migrated by 3-1a to 3-1i have it, and their worker refuses
|
|
29769
|
+
// such a policy ("A policy cannot read storage.objects in a subquery
|
|
29770
|
+
// yet") and answers every request of its table 500 POLICY_INVALID until
|
|
29771
|
+
// the next upgrade. The control plane takes one only where this row is
|
|
29772
|
+
// (`STORAGE_SUBQUERIES_SQL`, `buildPolicyBatch`).
|
|
29773
|
+
version: STORAGE_SUBQUERIES_VERSION,
|
|
29774
|
+
name: STORAGE_SUBQUERIES_NAME,
|
|
29775
|
+
statements: []
|
|
29776
|
+
}
|
|
29777
|
+
];
|
|
29778
|
+
|
|
28063
29779
|
// ../../packages/data-core/src/policy-migration.ts
|
|
28064
29780
|
var GRANT_REFUSAL = "naro has no GRANT or REVOKE: every table is behind row-level security, so write a policy instead (CREATE POLICY \u2026).";
|
|
28065
29781
|
function disableRefusal(table) {
|
|
@@ -28077,6 +29793,7 @@ function tablesChanged(head) {
|
|
|
28077
29793
|
case "create-table":
|
|
28078
29794
|
case "drop-table":
|
|
28079
29795
|
case "alter-table":
|
|
29796
|
+
case "rename-column":
|
|
28080
29797
|
return [asciiLower(head.table)];
|
|
28081
29798
|
case "rename-table":
|
|
28082
29799
|
return [asciiLower(head.table), asciiLower(head.to)];
|
|
@@ -28104,18 +29821,28 @@ function tablesInScope(policy) {
|
|
|
28104
29821
|
}
|
|
28105
29822
|
return tables;
|
|
28106
29823
|
}
|
|
29824
|
+
function readsStorageObjects(policy) {
|
|
29825
|
+
return [policy.using, policy.check].some((text2) => {
|
|
29826
|
+
if (text2 === null) return false;
|
|
29827
|
+
const parsed = parseExpression(text2);
|
|
29828
|
+
return parsed.ok && subqueriesOf(parsed.expr).some(
|
|
29829
|
+
(subquery) => isStorageObjects(subquery.table)
|
|
29830
|
+
);
|
|
29831
|
+
});
|
|
29832
|
+
}
|
|
28107
29833
|
function planPolicyMigration(statements) {
|
|
28108
29834
|
const count2 = statements.length;
|
|
28109
29835
|
const planned = [];
|
|
28110
29836
|
let hasPolicyStatements = false;
|
|
28111
29837
|
let touchesTables = false;
|
|
29838
|
+
let renamesColumns = false;
|
|
28112
29839
|
const heads = statements.map((text2) => readSqlHead(text2));
|
|
28113
29840
|
const changes = heads.map(tablesChanged);
|
|
28114
29841
|
const changedAfter = (index, tables) => changes.slice(index + 1).some(
|
|
28115
29842
|
(changed) => changed === "any" || changed.some((table) => tables.has(table))
|
|
28116
29843
|
);
|
|
28117
29844
|
for (const [index, text2] of statements.entries()) {
|
|
28118
|
-
const
|
|
29845
|
+
const refuse2 = (message) => ({
|
|
28119
29846
|
ok: false,
|
|
28120
29847
|
index,
|
|
28121
29848
|
message: `Statement ${String(index + 1)} of ${String(count2)}: ${message}`
|
|
@@ -28124,15 +29851,19 @@ function planPolicyMigration(statements) {
|
|
|
28124
29851
|
switch (head.kind) {
|
|
28125
29852
|
case "grant":
|
|
28126
29853
|
case "revoke":
|
|
28127
|
-
return
|
|
29854
|
+
return refuse2(GRANT_REFUSAL);
|
|
28128
29855
|
case "unreadable":
|
|
28129
|
-
return
|
|
29856
|
+
return refuse2(`${head.reason}.`);
|
|
28130
29857
|
case "create-table":
|
|
28131
29858
|
case "drop-table":
|
|
28132
29859
|
case "rename-table":
|
|
28133
29860
|
touchesTables = true;
|
|
28134
29861
|
planned.push({ kind: "verbatim", text: text2, head });
|
|
28135
29862
|
continue;
|
|
29863
|
+
case "rename-column":
|
|
29864
|
+
renamesColumns = true;
|
|
29865
|
+
planned.push({ kind: "verbatim", text: text2, head });
|
|
29866
|
+
continue;
|
|
28136
29867
|
case "alter-table":
|
|
28137
29868
|
case "other":
|
|
28138
29869
|
planned.push({ kind: "verbatim", text: text2, head });
|
|
@@ -28142,17 +29873,17 @@ function planPolicyMigration(statements) {
|
|
|
28142
29873
|
}
|
|
28143
29874
|
const parsed = parseMigrationPolicyStatement(text2);
|
|
28144
29875
|
if (!parsed.ok)
|
|
28145
|
-
return
|
|
29876
|
+
return refuse2(
|
|
28146
29877
|
`${parsed.reason} at character ${String(parsed.position)}.`
|
|
28147
29878
|
);
|
|
28148
29879
|
const statement2 = parsed.statement;
|
|
28149
29880
|
const table = statement2.kind === "create" ? statement2.policy.table : statement2.table;
|
|
28150
29881
|
if (!isStorageObjects(table) && isSystemTable(table))
|
|
28151
|
-
return
|
|
29882
|
+
return refuse2(`${relationMissing(table)}.`);
|
|
28152
29883
|
switch (statement2.kind) {
|
|
28153
29884
|
case "create":
|
|
28154
29885
|
if (readsBareSubqueryColumn(statement2.policy) && changedAfter(index, tablesInScope(statement2.policy)))
|
|
28155
|
-
return
|
|
29886
|
+
return refuse2(LATER_TABLE_CHANGE);
|
|
28156
29887
|
hasPolicyStatements = true;
|
|
28157
29888
|
planned.push({ kind: "create-policy", policy: statement2.policy });
|
|
28158
29889
|
break;
|
|
@@ -28180,14 +29911,19 @@ function planPolicyMigration(statements) {
|
|
|
28180
29911
|
break;
|
|
28181
29912
|
case "rls":
|
|
28182
29913
|
if (statement2.action === "disable")
|
|
28183
|
-
return
|
|
29914
|
+
return refuse2(disableRefusal(table));
|
|
28184
29915
|
planned.push({ kind: "noop" });
|
|
28185
29916
|
break;
|
|
28186
29917
|
}
|
|
28187
29918
|
}
|
|
28188
29919
|
return {
|
|
28189
29920
|
ok: true,
|
|
28190
|
-
plan: {
|
|
29921
|
+
plan: {
|
|
29922
|
+
statements: planned,
|
|
29923
|
+
hasPolicyStatements,
|
|
29924
|
+
touchesTables,
|
|
29925
|
+
renamesColumns
|
|
29926
|
+
}
|
|
28191
29927
|
};
|
|
28192
29928
|
}
|
|
28193
29929
|
function policyMigrationSql(policy) {
|
|
@@ -28202,6 +29938,7 @@ function unreadablePolicyComment(policy) {
|
|
|
28202
29938
|
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`;
|
|
28203
29939
|
var SYSTEM_PREDICATE = systemTablePredicate("name");
|
|
28204
29940
|
var STORAGE_EXISTS = `EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${sqlStringLiteral(STORAGE_OBJECTS_TABLE)})`;
|
|
29941
|
+
var STORAGE_SUBQUERIES_READY = `EXISTS (${STORAGE_SUBQUERIES_SQL})`;
|
|
28205
29942
|
|
|
28206
29943
|
// ../../packages/data-core/src/realtime-limits.ts
|
|
28207
29944
|
var HOBBY_REALTIME_LIMITS = Object.freeze({
|
|
@@ -28290,107 +30027,6 @@ var MULTIPART_COMPLETE_LOCK_MS = 15 * 60 * 1e3;
|
|
|
28290
30027
|
var UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
28291
30028
|
var UPLOAD_ID = new RegExp(`^(${UUID})\\.(${UUID})$`);
|
|
28292
30029
|
|
|
28293
|
-
// ../../packages/data-core/src/system-sql.ts
|
|
28294
|
-
async function convertLegacyPolicies(db, found) {
|
|
28295
|
-
const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
|
|
28296
|
-
if (rows.length === 0) return [];
|
|
28297
|
-
const read = rows.map((row) => ({
|
|
28298
|
-
policy: policyFromRow(row),
|
|
28299
|
-
asRead: legacyRowAsRead(row)
|
|
28300
|
-
}));
|
|
28301
|
-
const inForce = new Set(
|
|
28302
|
-
legacyInForce(
|
|
28303
|
-
read.flatMap(({ policy }) => policy ? [policy] : []),
|
|
28304
|
-
await readTableNames(db)
|
|
28305
|
-
)
|
|
28306
|
-
);
|
|
28307
|
-
const statements = [];
|
|
28308
|
-
for (const { policy, asRead } of read) {
|
|
28309
|
-
if (!policy || !asRead) continue;
|
|
28310
|
-
if (inForce.has(policy)) {
|
|
28311
|
-
for (const named of namedFromLegacy(policy))
|
|
28312
|
-
statements.push({
|
|
28313
|
-
sql: POLICY_SQL.insertConverted,
|
|
28314
|
-
params: [...policyValues(named), ...asRead]
|
|
28315
|
-
});
|
|
28316
|
-
found.unreadable.push(...unreadableConversions(policy));
|
|
28317
|
-
}
|
|
28318
|
-
statements.push({ sql: POLICY_SQL.markConverted, params: asRead });
|
|
28319
|
-
}
|
|
28320
|
-
return statements;
|
|
28321
|
-
}
|
|
28322
|
-
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')))";
|
|
28323
|
-
var SYSTEM_MIGRATIONS = [
|
|
28324
|
-
{
|
|
28325
|
-
version: 1,
|
|
28326
|
-
name: "system_tables",
|
|
28327
|
-
statements: [
|
|
28328
|
-
MIGRATIONS_TABLE,
|
|
28329
|
-
"CREATE TABLE IF NOT EXISTS _naro_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')))",
|
|
28330
|
-
// Spec §11.2, as written there.
|
|
28331
|
-
"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')))"
|
|
28332
|
-
]
|
|
28333
|
-
},
|
|
28334
|
-
{
|
|
28335
|
-
// Spec P2-2 §3.1: an action whose rule column says 'condition' reads its
|
|
28336
|
-
// condition from these. A worker from before them reads 'condition' as
|
|
28337
|
-
// an unknown rule — none.
|
|
28338
|
-
version: 2,
|
|
28339
|
-
name: "policy_conditions",
|
|
28340
|
-
statements: [],
|
|
28341
|
-
addColumns: ["select", "insert", "update", "delete"].map((action) => ({
|
|
28342
|
-
table: "_naro_policies",
|
|
28343
|
-
column: `${action}_condition`,
|
|
28344
|
-
definition: "TEXT"
|
|
28345
|
-
}))
|
|
28346
|
-
},
|
|
28347
|
-
{
|
|
28348
|
-
// Spec P2-6 §11: the Naro worker's sign-in handoff. A provider sign-in's
|
|
28349
|
-
// flow (state → the app's redirect and the browser's PKCE challenge), the
|
|
28350
|
-
// one-time codes a redirect hands the app, the sends the per-address
|
|
28351
|
-
// email limit counts, and (adversarial review I3) the unconfirmed
|
|
28352
|
-
// accounts someone else also tried to sign up as. Times are epoch
|
|
28353
|
-
// milliseconds.
|
|
28354
|
-
version: 3,
|
|
28355
|
-
name: "auth_handoff",
|
|
28356
|
-
statements: [
|
|
28357
|
-
"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)",
|
|
28358
|
-
"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)",
|
|
28359
|
-
"CREATE TABLE IF NOT EXISTS _naro_auth_email_log (address_hash TEXT NOT NULL, sent_at INTEGER NOT NULL)",
|
|
28360
|
-
"CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
|
|
28361
|
-
"CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
|
|
28362
|
-
]
|
|
28363
|
-
},
|
|
28364
|
-
{
|
|
28365
|
-
// P2-7 §9: named policies, many per table. The old rows are converted
|
|
28366
|
-
// in the same batch and their rules set to a word old workers read as
|
|
28367
|
-
// `none`.
|
|
28368
|
-
version: 4,
|
|
28369
|
-
name: "named_policies",
|
|
28370
|
-
statements: [NAMED_POLICY_TABLE_SQL],
|
|
28371
|
-
compute: convertLegacyPolicies
|
|
28372
|
-
},
|
|
28373
|
-
{
|
|
28374
|
-
// Storage (first-release design §6): the buckets and the objects' index
|
|
28375
|
-
// with their counter triggers. Every backend gets them, Storage on or
|
|
28376
|
-
// not — what turns it on is the worker's R2 binding. A worker from
|
|
28377
|
-
// before them never reads them.
|
|
28378
|
-
version: 5,
|
|
28379
|
-
name: "storage",
|
|
28380
|
-
statements: [...STORAGE_TABLES_SQL]
|
|
28381
|
-
},
|
|
28382
|
-
{
|
|
28383
|
-
// Storage 3-1b (known-issues #107, first-release design §14): the mark
|
|
28384
|
-
// a bucket's removal sets before it empties the bucket. The claims and
|
|
28385
|
-
// the worker's bucket reads take a marked bucket as not there. A worker
|
|
28386
|
-
// from before it never reads the column.
|
|
28387
|
-
version: 6,
|
|
28388
|
-
name: "storage_bucket_deleting",
|
|
28389
|
-
statements: [],
|
|
28390
|
-
addColumns: [STORAGE_BUCKET_DELETING_COLUMN]
|
|
28391
|
-
}
|
|
28392
|
-
];
|
|
28393
|
-
|
|
28394
30030
|
// ../../packages/data-core/src/write-quota.ts
|
|
28395
30031
|
var WRITE_QUOTA_KEY = "write_quota";
|
|
28396
30032
|
var WRITE_QUOTA_SQL = {
|
|
@@ -28636,12 +30272,12 @@ import {
|
|
|
28636
30272
|
rmSync as rmSync2,
|
|
28637
30273
|
writeFileSync as writeFileSync2
|
|
28638
30274
|
} from "fs";
|
|
28639
|
-
import { join as
|
|
30275
|
+
import { join as join23 } from "path";
|
|
28640
30276
|
function linkDir(dir) {
|
|
28641
|
-
return
|
|
30277
|
+
return join23(dir, ".naro");
|
|
28642
30278
|
}
|
|
28643
30279
|
function linkFile(dir) {
|
|
28644
|
-
return
|
|
30280
|
+
return join23(linkDir(dir), "project.json");
|
|
28645
30281
|
}
|
|
28646
30282
|
function readProjectLink(dir) {
|
|
28647
30283
|
try {
|
|
@@ -28663,7 +30299,7 @@ function writeProjectLink(dir, link) {
|
|
|
28663
30299
|
writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
|
|
28664
30300
|
`);
|
|
28665
30301
|
writeFileSync2(
|
|
28666
|
-
|
|
30302
|
+
join23(linkDir(dir), "README.txt"),
|
|
28667
30303
|
"This directory links the project to Naro and should not be committed.\n"
|
|
28668
30304
|
);
|
|
28669
30305
|
}
|
|
@@ -28673,9 +30309,9 @@ function removeProjectLink(dir) {
|
|
|
28673
30309
|
return true;
|
|
28674
30310
|
}
|
|
28675
30311
|
function ensureGitignoreHasNaro(dir) {
|
|
28676
|
-
const gitignorePath =
|
|
30312
|
+
const gitignorePath = join23(dir, ".gitignore");
|
|
28677
30313
|
const hasGitignore = existsSync4(gitignorePath);
|
|
28678
|
-
if (!hasGitignore && !existsSync4(
|
|
30314
|
+
if (!hasGitignore && !existsSync4(join23(dir, ".git"))) return false;
|
|
28679
30315
|
const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
|
|
28680
30316
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
|
|
28681
30317
|
if (covered) return false;
|
|
@@ -29128,15 +30764,15 @@ function policiesFromFiles(files) {
|
|
|
29128
30764
|
policies = policies.filter((policy) => !sameTable(policy.table, table));
|
|
29129
30765
|
};
|
|
29130
30766
|
for (const [file2, sql] of files.entries()) {
|
|
29131
|
-
const
|
|
29132
|
-
if (!
|
|
30767
|
+
const split2 = splitStatements(sql);
|
|
30768
|
+
if (!split2.ok)
|
|
29133
30769
|
return {
|
|
29134
30770
|
policies,
|
|
29135
30771
|
tables,
|
|
29136
|
-
refused: { file: file2, reason: `sql could not be parsed: ${
|
|
30772
|
+
refused: { file: file2, reason: `sql could not be parsed: ${split2.reason}.` }
|
|
29137
30773
|
};
|
|
29138
30774
|
const planned = planPolicyMigration(
|
|
29139
|
-
|
|
30775
|
+
split2.statements.map((statement2) => statement2.text)
|
|
29140
30776
|
);
|
|
29141
30777
|
if (!planned.ok)
|
|
29142
30778
|
return {
|
|
@@ -29204,6 +30840,31 @@ function policiesLostByReset(input) {
|
|
|
29204
30840
|
withTables: gone.filter((policy) => !remade.has(asciiLower(policy.table)))
|
|
29205
30841
|
};
|
|
29206
30842
|
}
|
|
30843
|
+
function storageRefusalOfFiles(files, backend) {
|
|
30844
|
+
for (const [file2, sql] of files.entries()) {
|
|
30845
|
+
const split2 = splitStatements(sql);
|
|
30846
|
+
if (!split2.ok) return void 0;
|
|
30847
|
+
const planned = planPolicyMigration(
|
|
30848
|
+
split2.statements.map((statement2) => statement2.text)
|
|
30849
|
+
);
|
|
30850
|
+
if (!planned.ok) return void 0;
|
|
30851
|
+
const count2 = planned.plan.statements.length;
|
|
30852
|
+
for (const [index, statement2] of planned.plan.statements.entries()) {
|
|
30853
|
+
let needs;
|
|
30854
|
+
if (statement2.kind === "create-policy")
|
|
30855
|
+
needs = readsStorageObjects(statement2.policy) ? "subqueries" : sameTable(statement2.policy.table, STORAGE_OBJECTS) ? "storage" : void 0;
|
|
30856
|
+
else if (statement2.kind === "drop-policy" && !statement2.ifExists || statement2.kind === "owner")
|
|
30857
|
+
needs = sameTable(statement2.table, STORAGE_OBJECTS) ? "storage" : void 0;
|
|
30858
|
+
const missing = needs === "subqueries" ? !backend.storage || !backend.storageSubqueries : needs === "storage" && !backend.storage;
|
|
30859
|
+
if (needs !== void 0 && missing)
|
|
30860
|
+
return { file: file2, statement: index + 1, count: count2, needs };
|
|
30861
|
+
}
|
|
30862
|
+
}
|
|
30863
|
+
return void 0;
|
|
30864
|
+
}
|
|
30865
|
+
function resetStorageOutdatedMessage(file2, at) {
|
|
30866
|
+
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.`;
|
|
30867
|
+
}
|
|
29207
30868
|
function resetPoliciesLostMessage(lost) {
|
|
29208
30869
|
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:`;
|
|
29209
30870
|
}
|
|
@@ -29282,7 +30943,7 @@ function usageStatus(report, currentMonth, requests) {
|
|
|
29282
30943
|
|
|
29283
30944
|
// src/env-consent.ts
|
|
29284
30945
|
import { existsSync as existsSync6 } from "fs";
|
|
29285
|
-
import { join as
|
|
30946
|
+
import { join as join24 } from "path";
|
|
29286
30947
|
var DOTENV_FILES = [".env.local", ".env.production", ".env"];
|
|
29287
30948
|
var PUBLIC_PREFIX = "NEXT_PUBLIC_";
|
|
29288
30949
|
function maskSecretValue(value) {
|
|
@@ -29294,7 +30955,7 @@ function maskEnvValue(key, value) {
|
|
|
29294
30955
|
return maskSecretValue(value);
|
|
29295
30956
|
}
|
|
29296
30957
|
function findDotenvFile(dir) {
|
|
29297
|
-
for (const name of DOTENV_FILES) if (existsSync6(
|
|
30958
|
+
for (const name of DOTENV_FILES) if (existsSync6(join24(dir, name))) return name;
|
|
29298
30959
|
return null;
|
|
29299
30960
|
}
|
|
29300
30961
|
function envAddGuidance(keys, environment) {
|
|
@@ -29525,9 +31186,9 @@ import {
|
|
|
29525
31186
|
cp as cp2,
|
|
29526
31187
|
lstat,
|
|
29527
31188
|
mkdir as mkdir3,
|
|
29528
|
-
readFile as
|
|
31189
|
+
readFile as readFile14,
|
|
29529
31190
|
readlink,
|
|
29530
|
-
realpath,
|
|
31191
|
+
realpath as realpath3,
|
|
29531
31192
|
rm as rm4,
|
|
29532
31193
|
stat as stat3,
|
|
29533
31194
|
symlink,
|
|
@@ -29538,10 +31199,10 @@ import {
|
|
|
29538
31199
|
basename as basename3,
|
|
29539
31200
|
dirname as dirname5,
|
|
29540
31201
|
isAbsolute as isAbsolute4,
|
|
29541
|
-
join as
|
|
31202
|
+
join as join25,
|
|
29542
31203
|
relative as relative6,
|
|
29543
31204
|
resolve as resolve10,
|
|
29544
|
-
sep as
|
|
31205
|
+
sep as sep5
|
|
29545
31206
|
} from "path";
|
|
29546
31207
|
var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
|
|
29547
31208
|
var ENV_FILE = /^\.env(\.(?!(?:example|sample|template)$)[^\\/]+)?$/i;
|
|
@@ -29579,7 +31240,7 @@ async function committedEnvFiles(sourceDir) {
|
|
|
29579
31240
|
}
|
|
29580
31241
|
async function staysInside(link, text2, root) {
|
|
29581
31242
|
try {
|
|
29582
|
-
return inside(await
|
|
31243
|
+
return inside(await realpath3(link), root);
|
|
29583
31244
|
} catch (error51) {
|
|
29584
31245
|
return error51.code === "ENOENT" && !text2.split(/[\\/]/).includes("..");
|
|
29585
31246
|
}
|
|
@@ -29602,12 +31263,12 @@ async function keptOnly(items, keep, remove) {
|
|
|
29602
31263
|
async function readLocal(path) {
|
|
29603
31264
|
const found = await stat3(path).catch(() => null);
|
|
29604
31265
|
if (!found?.isFile()) return null;
|
|
29605
|
-
return
|
|
31266
|
+
return readFile14(path).catch(() => null);
|
|
29606
31267
|
}
|
|
29607
31268
|
async function realAncestor(dir) {
|
|
29608
31269
|
for (let current = dir; ; current = dirname5(current)) {
|
|
29609
31270
|
try {
|
|
29610
|
-
return await
|
|
31271
|
+
return await realpath3(current);
|
|
29611
31272
|
} catch {
|
|
29612
31273
|
if (dirname5(current) === current)
|
|
29613
31274
|
throw new Error(`No ancestor of ${dir}`);
|
|
@@ -29618,7 +31279,7 @@ function sameText(a, b) {
|
|
|
29618
31279
|
return a.equals(b) || a.toString("utf8").replace(/\r\n/g, "\n") === b.toString("utf8").replace(/\r\n/g, "\n");
|
|
29619
31280
|
}
|
|
29620
31281
|
async function copyWorkingTree(source, checkout) {
|
|
29621
|
-
const sourceDir = await
|
|
31282
|
+
const sourceDir = await realpath3(source);
|
|
29622
31283
|
const inWorkingTree = [];
|
|
29623
31284
|
const absoluteLinks = [];
|
|
29624
31285
|
const linksOutside = [];
|
|
@@ -29637,12 +31298,12 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29637
31298
|
if (entry.isFIFO() || entry.isSocket() || entry.isCharacterDevice() || entry.isBlockDevice())
|
|
29638
31299
|
return false;
|
|
29639
31300
|
if (ENV_FILE.test(basename3(src)) && !entry.isDirectory()) {
|
|
29640
|
-
inWorkingTree.push(relative6(sourceDir, src).split(
|
|
31301
|
+
inWorkingTree.push(relative6(sourceDir, src).split(sep5).join("/"));
|
|
29641
31302
|
return false;
|
|
29642
31303
|
}
|
|
29643
31304
|
if (entry.isSymbolicLink()) {
|
|
29644
31305
|
const target2 = await readlink(src);
|
|
29645
|
-
const path = relative6(sourceDir, src).split(
|
|
31306
|
+
const path = relative6(sourceDir, src).split(sep5).join("/");
|
|
29646
31307
|
if (isAbsolute4(target2)) {
|
|
29647
31308
|
absoluteLinks.push(path);
|
|
29648
31309
|
return false;
|
|
@@ -29660,17 +31321,17 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29660
31321
|
return true;
|
|
29661
31322
|
}
|
|
29662
31323
|
});
|
|
29663
|
-
const root = await
|
|
31324
|
+
const root = await realpath3(checkout);
|
|
29664
31325
|
const strays = await keptOnly(
|
|
29665
31326
|
keptLinks,
|
|
29666
31327
|
async ({ path, text: text2 }) => {
|
|
29667
31328
|
try {
|
|
29668
|
-
return inside(await
|
|
31329
|
+
return inside(await realpath3(join25(checkout, path)), root);
|
|
29669
31330
|
} catch (error51) {
|
|
29670
31331
|
return error51.code === "ENOENT" && climbsOnlyFirst(text2);
|
|
29671
31332
|
}
|
|
29672
31333
|
},
|
|
29673
|
-
({ path }) => unlink(
|
|
31334
|
+
({ path }) => unlink(join25(checkout, path))
|
|
29674
31335
|
);
|
|
29675
31336
|
linksElsewhere.push(...strays.map(({ path }) => path));
|
|
29676
31337
|
const committed = await committedEnvFiles(sourceDir);
|
|
@@ -29684,7 +31345,7 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29684
31345
|
outsideProject.push(path);
|
|
29685
31346
|
continue;
|
|
29686
31347
|
}
|
|
29687
|
-
const target2 =
|
|
31348
|
+
const target2 = join25(checkout, path);
|
|
29688
31349
|
if (!inside(await realAncestor(dirname5(target2)), root)) {
|
|
29689
31350
|
outsideProject.push(path);
|
|
29690
31351
|
continue;
|
|
@@ -29709,16 +31370,16 @@ async function copyWorkingTree(source, checkout) {
|
|
|
29709
31370
|
continue;
|
|
29710
31371
|
}
|
|
29711
31372
|
await writeFile7(target2, content, { flag: "wx" });
|
|
29712
|
-
const local = await readLocal(
|
|
31373
|
+
const local = await readLocal(join25(sourceDir, path));
|
|
29713
31374
|
if (!local || !sameText(local, content)) committedVersion.push(path);
|
|
29714
31375
|
}
|
|
29715
31376
|
const envStrays = await keptOnly(
|
|
29716
31377
|
envLinks,
|
|
29717
31378
|
async (path) => {
|
|
29718
|
-
const real = await
|
|
31379
|
+
const real = await realpath3(join25(checkout, path)).catch(() => null);
|
|
29719
31380
|
return real !== null && inside(real, root);
|
|
29720
31381
|
},
|
|
29721
|
-
(path) => unlink(
|
|
31382
|
+
(path) => unlink(join25(checkout, path))
|
|
29722
31383
|
);
|
|
29723
31384
|
outsideProject.push(...envStrays);
|
|
29724
31385
|
const inCommit = new Set(
|
|
@@ -29835,7 +31496,7 @@ function slugify2(value) {
|
|
|
29835
31496
|
async function projectName(sourceDir) {
|
|
29836
31497
|
try {
|
|
29837
31498
|
const pkg = JSON.parse(
|
|
29838
|
-
await
|
|
31499
|
+
await readFile15(join26(sourceDir, "package.json"), "utf8")
|
|
29839
31500
|
);
|
|
29840
31501
|
if (pkg.name) return pkg.name;
|
|
29841
31502
|
} catch {
|
|
@@ -29845,7 +31506,7 @@ async function projectName(sourceDir) {
|
|
|
29845
31506
|
function resolveBin(name) {
|
|
29846
31507
|
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
29847
31508
|
for (; ; ) {
|
|
29848
|
-
const candidate =
|
|
31509
|
+
const candidate = join26(dir, "node_modules", ".bin", name);
|
|
29849
31510
|
if (existsSync7(candidate)) return candidate;
|
|
29850
31511
|
const parent = dirname6(dir);
|
|
29851
31512
|
if (parent === dir) return name;
|
|
@@ -29912,7 +31573,7 @@ function workingTreeNotices(copy) {
|
|
|
29912
31573
|
}
|
|
29913
31574
|
function localDependencies(sourceDir, say = () => void 0) {
|
|
29914
31575
|
return {
|
|
29915
|
-
createWorkspace: () => mkdtemp2(
|
|
31576
|
+
createWorkspace: () => mkdtemp2(join26(tmpdir2(), "naro-build-")),
|
|
29916
31577
|
removeWorkspace: (path) => rm5(path, { recursive: true, force: true }),
|
|
29917
31578
|
async runCommand(spec, observer) {
|
|
29918
31579
|
if (spec.command === "git" && spec.args[0] === "clone") {
|
|
@@ -29950,7 +31611,12 @@ function localDependencies(sourceDir, say = () => void 0) {
|
|
|
29950
31611
|
async function deployDirectory(sourceDir, options, log2) {
|
|
29951
31612
|
const cpUrl = controlPlaneUrl();
|
|
29952
31613
|
const production = options.production ?? false;
|
|
29953
|
-
|
|
31614
|
+
if (production && options.environment !== void 0)
|
|
31615
|
+
throw new NaroError(
|
|
31616
|
+
"VALIDATION_FAILED",
|
|
31617
|
+
"environment and production cannot be used together."
|
|
31618
|
+
);
|
|
31619
|
+
const environment = options.environment;
|
|
29954
31620
|
if (environment === "production" || environment === "preview")
|
|
29955
31621
|
throw new NaroError(
|
|
29956
31622
|
"VALIDATION_FAILED",
|
|
@@ -30147,6 +31813,7 @@ export {
|
|
|
30147
31813
|
splitStatements,
|
|
30148
31814
|
ANALYTICS_FILTER_KEYS,
|
|
30149
31815
|
NaroError,
|
|
31816
|
+
promoteTargetUnsupported,
|
|
30150
31817
|
isNaroError,
|
|
30151
31818
|
toNaroError,
|
|
30152
31819
|
saveSession,
|
|
@@ -30178,12 +31845,14 @@ export {
|
|
|
30178
31845
|
cellText,
|
|
30179
31846
|
renderRows,
|
|
30180
31847
|
summarizeStatement,
|
|
30181
|
-
|
|
31848
|
+
parseDotenvDetailed,
|
|
30182
31849
|
serializeDotenv,
|
|
30183
31850
|
MAX_SINGLE_UPLOAD_BYTES,
|
|
31851
|
+
checkObjectName,
|
|
30184
31852
|
parseCacheControl,
|
|
30185
31853
|
STORAGE_OBJECTS_TABLE,
|
|
30186
31854
|
systemTablePredicate,
|
|
31855
|
+
dataError,
|
|
30187
31856
|
SCHEMA_SQL,
|
|
30188
31857
|
tableNames,
|
|
30189
31858
|
snapshotFromRows,
|
|
@@ -30198,6 +31867,7 @@ export {
|
|
|
30198
31867
|
indexesFromRows,
|
|
30199
31868
|
parsePolicyStatement,
|
|
30200
31869
|
splitPolicyStatements,
|
|
31870
|
+
STORAGE_SUBQUERIES_SQL,
|
|
30201
31871
|
planPolicyMigration,
|
|
30202
31872
|
unreadablePolicyComment,
|
|
30203
31873
|
parseRedirectEntry,
|
|
@@ -30220,6 +31890,8 @@ export {
|
|
|
30220
31890
|
RESET_BACKEND_OUTDATED,
|
|
30221
31891
|
buildResetPlan,
|
|
30222
31892
|
policiesLostByReset,
|
|
31893
|
+
storageRefusalOfFiles,
|
|
31894
|
+
resetStorageOutdatedMessage,
|
|
30223
31895
|
resetPoliciesLostMessage,
|
|
30224
31896
|
resetPoliciesWithTablesMessage,
|
|
30225
31897
|
resetFileRefusedMessage,
|
|
@@ -30230,7 +31902,7 @@ export {
|
|
|
30230
31902
|
promptForMissingEnv,
|
|
30231
31903
|
pullEnv,
|
|
30232
31904
|
detectGitInfo,
|
|
30233
|
-
parseDotenv2,
|
|
31905
|
+
parseDotenv2 as parseDotenv,
|
|
30234
31906
|
readProjectEnv2 as readProjectEnv,
|
|
30235
31907
|
eventsObserver,
|
|
30236
31908
|
slugify2 as slugify,
|