naro-cli 0.9.1 → 0.11.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 +248 -52
- package/dist/artifact-types.d.ts +5 -0
- package/dist/{chunk-Z757IOF3.js → chunk-L7JOGWP3.js} +2455 -133
- package/dist/deploy.js +1 -1
- package/dist/git-info.d.ts +14 -1
- package/dist/index.js +2342 -1833
- package/package.json +4 -4
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { existsSync as existsSync7 } from "fs";
|
|
8
8
|
import { cp as cp2, mkdtemp as mkdtemp2, readFile as readFile13, rm as rm3 } from "fs/promises";
|
|
9
9
|
import { tmpdir as tmpdir2 } from "os";
|
|
10
|
-
import { basename as basename3, dirname as dirname4, join as
|
|
10
|
+
import { basename as basename3, dirname as dirname4, join as join24, resolve as resolve9 } from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
12
|
|
|
13
13
|
// ../../packages/build/src/artifact-upload.ts
|
|
@@ -226,7 +226,22 @@ function hashAsset(data, contentType2) {
|
|
|
226
226
|
// ../../packages/build/src/local-build-executor.ts
|
|
227
227
|
import { access as access8, mkdtemp, readFile as readFile12, rm as rm2 } from "fs/promises";
|
|
228
228
|
import { tmpdir } from "os";
|
|
229
|
-
import { dirname as dirname3, join as
|
|
229
|
+
import { dirname as dirname3, join as join17, resolve as resolve7 } from "path";
|
|
230
|
+
|
|
231
|
+
// ../../packages/core/src/route-patterns.ts
|
|
232
|
+
var ROUTE_PATTERN = /^\/[A-Za-z0-9\-._~%!$&()*+,;=:@/[\]]*$/;
|
|
233
|
+
var MAX_ROUTE_LENGTH = 256;
|
|
234
|
+
var MAX_ROUTES = 2e3;
|
|
235
|
+
var MAX_ROUTE_LIST_BYTES = 64 * 1024;
|
|
236
|
+
function isRoutePattern(value) {
|
|
237
|
+
return typeof value === "string" && value.length <= MAX_ROUTE_LENGTH && ROUTE_PATTERN.test(value);
|
|
238
|
+
}
|
|
239
|
+
function sanitizeRoutes(value) {
|
|
240
|
+
if (!Array.isArray(value)) return void 0;
|
|
241
|
+
const routes = [...new Set(value.filter(isRoutePattern))].sort();
|
|
242
|
+
if (routes.length > MAX_ROUTES) return void 0;
|
|
243
|
+
return JSON.stringify(routes).length > MAX_ROUTE_LIST_BYTES ? void 0 : routes;
|
|
244
|
+
}
|
|
230
245
|
|
|
231
246
|
// ../../packages/core/src/artifact-manifest.ts
|
|
232
247
|
var MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|
@@ -4950,8 +4965,8 @@ function az_default() {
|
|
|
4950
4965
|
}
|
|
4951
4966
|
|
|
4952
4967
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/be.js
|
|
4953
|
-
function getBelarusianPlural(
|
|
4954
|
-
const absCount = Math.abs(
|
|
4968
|
+
function getBelarusianPlural(count2, one, few, many) {
|
|
4969
|
+
const absCount = Math.abs(count2);
|
|
4955
4970
|
const lastDigit = absCount % 10;
|
|
4956
4971
|
const lastTwoDigits = absCount % 100;
|
|
4957
4972
|
if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
|
|
@@ -7029,8 +7044,8 @@ function hu_default() {
|
|
|
7029
7044
|
}
|
|
7030
7045
|
|
|
7031
7046
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/hy.js
|
|
7032
|
-
function getArmenianPlural(
|
|
7033
|
-
return Math.abs(
|
|
7047
|
+
function getArmenianPlural(count2, one, many) {
|
|
7048
|
+
return Math.abs(count2) === 1 ? one : many;
|
|
7034
7049
|
}
|
|
7035
7050
|
function withDefiniteArticle(word) {
|
|
7036
7051
|
if (!word)
|
|
@@ -9158,8 +9173,8 @@ function ro_default() {
|
|
|
9158
9173
|
}
|
|
9159
9174
|
|
|
9160
9175
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ru.js
|
|
9161
|
-
function getRussianPlural(
|
|
9162
|
-
const absCount = Math.abs(
|
|
9176
|
+
function getRussianPlural(count2, one, few, many) {
|
|
9177
|
+
const absCount = Math.abs(count2);
|
|
9163
9178
|
const lastDigit = absCount % 10;
|
|
9164
9179
|
const lastTwoDigits = absCount % 100;
|
|
9165
9180
|
if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
|
|
@@ -16015,6 +16030,93 @@ async function detectFramework(root, overrides = {}) {
|
|
|
16015
16030
|
};
|
|
16016
16031
|
}
|
|
16017
16032
|
|
|
16033
|
+
// ../../packages/build/src/next-routes.ts
|
|
16034
|
+
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
16035
|
+
import { join as join8 } from "path";
|
|
16036
|
+
var APP_PAGE_FILE = /^page\.(?:js|jsx|ts|tsx|md|mdx)$/;
|
|
16037
|
+
var PAGES_ROUTER_FILE = /^(.+)\.(?:js|jsx|ts|tsx)$/;
|
|
16038
|
+
var DECLARATION_FILE = /\.d\.ts$/;
|
|
16039
|
+
var PAGES_ROUTER_SPECIAL = /* @__PURE__ */ new Set(["_app", "_document", "_error", "api"]);
|
|
16040
|
+
var DYNAMIC_SEGMENT = /^\[.+\]$/;
|
|
16041
|
+
var ROUTE_GROUP = /^\(.+\)$/;
|
|
16042
|
+
var INTERCEPTING = /^\(\.{1,3}\)/;
|
|
16043
|
+
var LITERAL_IN_PATHNAME = /%(?:21|24|26|28|29|2A|2B|2C|3B|3D|3A|40)/g;
|
|
16044
|
+
function errorCode(error51) {
|
|
16045
|
+
return error51?.code;
|
|
16046
|
+
}
|
|
16047
|
+
async function firstDirectory(root, candidates) {
|
|
16048
|
+
for (const candidate of candidates) {
|
|
16049
|
+
const path = join8(root, candidate);
|
|
16050
|
+
try {
|
|
16051
|
+
if ((await stat2(path)).isDirectory()) return path;
|
|
16052
|
+
} catch (error51) {
|
|
16053
|
+
const code = errorCode(error51);
|
|
16054
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error51;
|
|
16055
|
+
}
|
|
16056
|
+
}
|
|
16057
|
+
return null;
|
|
16058
|
+
}
|
|
16059
|
+
function urlSegment(name, router) {
|
|
16060
|
+
if (DYNAMIC_SEGMENT.test(name)) return name;
|
|
16061
|
+
const decoded = router === "app" ? name.replace(/%5F/gi, "_") : name;
|
|
16062
|
+
return encodeURIComponent(decoded).replace(/'/g, "%27").replace(
|
|
16063
|
+
LITERAL_IN_PATHNAME,
|
|
16064
|
+
(escape) => String.fromCharCode(Number.parseInt(escape.slice(1), 16))
|
|
16065
|
+
);
|
|
16066
|
+
}
|
|
16067
|
+
function toRoute(segments) {
|
|
16068
|
+
return `/${segments.join("/")}`;
|
|
16069
|
+
}
|
|
16070
|
+
async function readAppRouter(dir, segments, routes) {
|
|
16071
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
16072
|
+
if (entries.some((entry) => entry.isFile() && APP_PAGE_FILE.test(entry.name)))
|
|
16073
|
+
routes.add(toRoute(segments));
|
|
16074
|
+
for (const entry of entries) {
|
|
16075
|
+
if (!entry.isDirectory()) continue;
|
|
16076
|
+
const name = entry.name;
|
|
16077
|
+
if (name.startsWith("_") || INTERCEPTING.test(name)) continue;
|
|
16078
|
+
const next = ROUTE_GROUP.test(name) || name.startsWith("@") ? segments : [...segments, urlSegment(name, "app")];
|
|
16079
|
+
await readAppRouter(join8(dir, name), next, routes);
|
|
16080
|
+
}
|
|
16081
|
+
}
|
|
16082
|
+
async function readPagesRouter(dir, segments, routes) {
|
|
16083
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
16084
|
+
for (const entry of entries) {
|
|
16085
|
+
if (entry.isDirectory()) {
|
|
16086
|
+
if (segments.length === 0 && entry.name === "api") continue;
|
|
16087
|
+
await readPagesRouter(
|
|
16088
|
+
join8(dir, entry.name),
|
|
16089
|
+
[...segments, urlSegment(entry.name, "pages")],
|
|
16090
|
+
routes
|
|
16091
|
+
);
|
|
16092
|
+
continue;
|
|
16093
|
+
}
|
|
16094
|
+
if (!entry.isFile() || DECLARATION_FILE.test(entry.name)) continue;
|
|
16095
|
+
const base2 = PAGES_ROUTER_FILE.exec(entry.name)?.[1];
|
|
16096
|
+
if (!base2) continue;
|
|
16097
|
+
if (segments.length === 0 && PAGES_ROUTER_SPECIAL.has(base2)) continue;
|
|
16098
|
+
routes.add(
|
|
16099
|
+
toRoute(
|
|
16100
|
+
base2 === "index" ? segments : [...segments, urlSegment(base2, "pages")]
|
|
16101
|
+
)
|
|
16102
|
+
);
|
|
16103
|
+
}
|
|
16104
|
+
}
|
|
16105
|
+
async function listNextRoutes(appRoot) {
|
|
16106
|
+
const app = await firstDirectory(appRoot, ["app", join8("src", "app")]);
|
|
16107
|
+
const pages = await firstDirectory(appRoot, ["pages", join8("src", "pages")]);
|
|
16108
|
+
if (!app && !pages) return null;
|
|
16109
|
+
const routes = /* @__PURE__ */ new Set();
|
|
16110
|
+
if (app) await readAppRouter(app, [], routes);
|
|
16111
|
+
if (pages) await readPagesRouter(pages, [], routes);
|
|
16112
|
+
const valid = sanitizeRoutes([...routes]);
|
|
16113
|
+
if (!valid)
|
|
16114
|
+
throw new Error(
|
|
16115
|
+
"the app has more than 2,000 routes or its route list is over 64 KiB"
|
|
16116
|
+
);
|
|
16117
|
+
return valid.length > 0 ? valid : null;
|
|
16118
|
+
}
|
|
16119
|
+
|
|
16018
16120
|
// ../../packages/build/src/process.ts
|
|
16019
16121
|
import { spawn } from "child_process";
|
|
16020
16122
|
import { createInterface } from "readline";
|
|
@@ -16246,8 +16348,8 @@ function resolveAppRoot(checkout, configuredRoot) {
|
|
|
16246
16348
|
}
|
|
16247
16349
|
|
|
16248
16350
|
// ../../packages/build/src/static-artifact.ts
|
|
16249
|
-
import { readdir as
|
|
16250
|
-
import { isAbsolute as isAbsolute3, join as
|
|
16351
|
+
import { readdir as readdir3, readFile as readFile7 } from "fs/promises";
|
|
16352
|
+
import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve4, sep as sep2 } from "path";
|
|
16251
16353
|
var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
|
|
16252
16354
|
var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
|
|
16253
16355
|
var ALLOWED_DOTFILE = ".well-known";
|
|
@@ -16270,12 +16372,12 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16270
16372
|
);
|
|
16271
16373
|
const assets = [];
|
|
16272
16374
|
async function visit(directory) {
|
|
16273
|
-
const entries = await
|
|
16375
|
+
const entries = await readdir3(directory, { withFileTypes: true });
|
|
16274
16376
|
for (const entry of entries.sort(
|
|
16275
16377
|
(left, right) => left.name.localeCompare(right.name)
|
|
16276
16378
|
)) {
|
|
16277
16379
|
if (excluded(entry.name)) continue;
|
|
16278
|
-
const path =
|
|
16380
|
+
const path = join9(directory, entry.name);
|
|
16279
16381
|
if (entry.isSymbolicLink())
|
|
16280
16382
|
throw new DeploymentError(
|
|
16281
16383
|
"ARTIFACT_INVALID",
|
|
@@ -16331,7 +16433,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
|
|
|
16331
16433
|
// ../../packages/build/src/strategy/fingerprint.ts
|
|
16332
16434
|
import { createHash as createHash3 } from "crypto";
|
|
16333
16435
|
import { readFile as readFile8 } from "fs/promises";
|
|
16334
|
-
import { join as
|
|
16436
|
+
import { join as join10, relative as relative4, sep as sep3 } from "path";
|
|
16335
16437
|
var APP_ROOT_FILES = [
|
|
16336
16438
|
"package.json",
|
|
16337
16439
|
"next.config.js",
|
|
@@ -16375,11 +16477,11 @@ async function hashFile(path) {
|
|
|
16375
16477
|
}
|
|
16376
16478
|
async function hashStrategyInputs(checkout, appRoot) {
|
|
16377
16479
|
const candidates = /* @__PURE__ */ new Set();
|
|
16378
|
-
for (const name of CHECKOUT_FILES) candidates.add(
|
|
16379
|
-
for (const name of APP_ROOT_FILES) candidates.add(
|
|
16480
|
+
for (const name of CHECKOUT_FILES) candidates.add(join10(checkout, name));
|
|
16481
|
+
for (const name of APP_ROOT_FILES) candidates.add(join10(appRoot, name));
|
|
16380
16482
|
for (const base2 of SOURCE_FILES) {
|
|
16381
16483
|
for (const ext of SOURCE_EXTENSIONS) {
|
|
16382
|
-
candidates.add(
|
|
16484
|
+
candidates.add(join10(appRoot, `${base2}.${ext}`));
|
|
16383
16485
|
}
|
|
16384
16486
|
}
|
|
16385
16487
|
const entries = [];
|
|
@@ -16408,7 +16510,7 @@ async function computeStrategyFingerprint(input) {
|
|
|
16408
16510
|
|
|
16409
16511
|
// ../../packages/build/src/strategy/project-facts.ts
|
|
16410
16512
|
import { access as access6, readFile as readFile9 } from "fs/promises";
|
|
16411
|
-
import { join as
|
|
16513
|
+
import { join as join11 } from "path";
|
|
16412
16514
|
var SOURCE_EXTENSIONS2 = ["ts", "tsx", "js", "jsx", "mjs", "mts"];
|
|
16413
16515
|
var SOURCE_DIRECTORIES = ["", "src"];
|
|
16414
16516
|
var VITE_CONFIGS = [
|
|
@@ -16429,7 +16531,7 @@ async function findSourceFile(appRoot, basename4) {
|
|
|
16429
16531
|
for (const dir of SOURCE_DIRECTORIES) {
|
|
16430
16532
|
for (const ext of SOURCE_EXTENSIONS2) {
|
|
16431
16533
|
const rel = dir ? `${dir}/${basename4}.${ext}` : `${basename4}.${ext}`;
|
|
16432
|
-
if (await exists4(
|
|
16534
|
+
if (await exists4(join11(appRoot, rel))) return rel;
|
|
16433
16535
|
}
|
|
16434
16536
|
}
|
|
16435
16537
|
return null;
|
|
@@ -16458,10 +16560,10 @@ async function collectProjectFacts(appRoot) {
|
|
|
16458
16560
|
const proxyFile = await findSourceFile(appRoot, "proxy");
|
|
16459
16561
|
const middlewareFile = await findSourceFile(appRoot, "middleware");
|
|
16460
16562
|
const routeSourceFile = middlewareFile ?? proxyFile;
|
|
16461
|
-
const routeSource = routeSourceFile ? await readIfExists(
|
|
16563
|
+
const routeSource = routeSourceFile ? await readIfExists(join11(appRoot, routeSourceFile)) : null;
|
|
16462
16564
|
let usesVinext = false;
|
|
16463
16565
|
let nextSpec = null;
|
|
16464
|
-
const packageJsonSource = await readIfExists(
|
|
16566
|
+
const packageJsonSource = await readIfExists(join11(appRoot, "package.json"));
|
|
16465
16567
|
if (packageJsonSource) {
|
|
16466
16568
|
try {
|
|
16467
16569
|
const packageJson = JSON.parse(packageJsonSource);
|
|
@@ -16472,7 +16574,7 @@ async function collectProjectFacts(appRoot) {
|
|
|
16472
16574
|
nextSpec = dependency("next");
|
|
16473
16575
|
if (dependency("vinext") !== null) {
|
|
16474
16576
|
for (const name of VITE_CONFIGS) {
|
|
16475
|
-
const config2 = await readIfExists(
|
|
16577
|
+
const config2 = await readIfExists(join11(appRoot, name));
|
|
16476
16578
|
if (config2 && /\bvinext\s*\(/.test(config2) && /@cloudflare\/vite-plugin/.test(config2)) {
|
|
16477
16579
|
usesVinext = true;
|
|
16478
16580
|
break;
|
|
@@ -16726,7 +16828,7 @@ function createSmokeTester(options = {}) {
|
|
|
16726
16828
|
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
16727
16829
|
async function runOnce(request, observer) {
|
|
16728
16830
|
const stage = request.stage ?? "BUILDING";
|
|
16729
|
-
const
|
|
16831
|
+
const errorCode2 = request.errorCode ?? "BUILD_FAILED";
|
|
16730
16832
|
const bootTimeoutMs = request.bootTimeoutMs ?? 9e4;
|
|
16731
16833
|
const probeTimeoutMs = request.probeTimeoutMs ?? 15e3;
|
|
16732
16834
|
const port = await allocatePort();
|
|
@@ -16734,7 +16836,7 @@ function createSmokeTester(options = {}) {
|
|
|
16734
16836
|
const boot = request.boot(port);
|
|
16735
16837
|
const recent = [];
|
|
16736
16838
|
const server = start(
|
|
16737
|
-
{ ...boot, stage, errorCode },
|
|
16839
|
+
{ ...boot, stage, errorCode: errorCode2 },
|
|
16738
16840
|
// The dev server is chatty; keep its stderr from reading as build
|
|
16739
16841
|
// errors in the deployment log.
|
|
16740
16842
|
{
|
|
@@ -16850,11 +16952,11 @@ function defaultSmokeProbes(matcherRoutes) {
|
|
|
16850
16952
|
|
|
16851
16953
|
// ../../packages/build/src/strategy/strategies/opennext-aws.ts
|
|
16852
16954
|
import { writeFile as writeFile6 } from "fs/promises";
|
|
16853
|
-
import { join as
|
|
16955
|
+
import { join as join13 } from "path";
|
|
16854
16956
|
|
|
16855
16957
|
// ../../packages/build/src/strategy/strategies/shared.ts
|
|
16856
16958
|
import { access as access7, readFile as readFile10, rm, writeFile as writeFile5 } from "fs/promises";
|
|
16857
|
-
import { dirname as dirname2, join as
|
|
16959
|
+
import { dirname as dirname2, join as join12, resolve as resolve5 } from "path";
|
|
16858
16960
|
function capturingObserver(observer) {
|
|
16859
16961
|
const lines = [];
|
|
16860
16962
|
return {
|
|
@@ -16907,7 +17009,7 @@ async function findProjectBin(appRoot, checkout, name) {
|
|
|
16907
17009
|
let dir = resolve5(appRoot);
|
|
16908
17010
|
const stop = resolve5(checkout);
|
|
16909
17011
|
for (; ; ) {
|
|
16910
|
-
const candidate =
|
|
17012
|
+
const candidate = join12(dir, "node_modules", ".bin", name);
|
|
16911
17013
|
if (await pathExists2(candidate)) return candidate;
|
|
16912
17014
|
if (dir === stop) return null;
|
|
16913
17015
|
const parent = dirname2(dir);
|
|
@@ -16921,7 +17023,7 @@ function dotenvQuote(value) {
|
|
|
16921
17023
|
return `"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`;
|
|
16922
17024
|
}
|
|
16923
17025
|
async function withDevVars(wranglerConfigPath, env, run) {
|
|
16924
|
-
const path =
|
|
17026
|
+
const path = join12(dirname2(wranglerConfigPath), ".dev.vars");
|
|
16925
17027
|
const keys = Object.keys(env).filter(
|
|
16926
17028
|
(key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
|
|
16927
17029
|
);
|
|
@@ -16938,7 +17040,7 @@ async function withDevVars(wranglerConfigPath, env, run) {
|
|
|
16938
17040
|
await rm(path, { force: true });
|
|
16939
17041
|
}
|
|
16940
17042
|
}
|
|
16941
|
-
async function bundleWorker(context, layout, observer,
|
|
17043
|
+
async function bundleWorker(context, layout, observer, errorCode2) {
|
|
16942
17044
|
return runStep(
|
|
16943
17045
|
context,
|
|
16944
17046
|
{
|
|
@@ -16954,7 +17056,7 @@ async function bundleWorker(context, layout, observer, errorCode) {
|
|
|
16954
17056
|
cwd: layout.appRoot,
|
|
16955
17057
|
env: {
|
|
16956
17058
|
CI: "1",
|
|
16957
|
-
WRANGLER_LOG_PATH:
|
|
17059
|
+
WRANGLER_LOG_PATH: join12(
|
|
16958
17060
|
layout.appRoot,
|
|
16959
17061
|
layout.outputRoot,
|
|
16960
17062
|
"wrangler.log"
|
|
@@ -16962,7 +17064,7 @@ async function bundleWorker(context, layout, observer, errorCode) {
|
|
|
16962
17064
|
},
|
|
16963
17065
|
timeoutMs: 5 * 6e4,
|
|
16964
17066
|
stage: "BUILDING",
|
|
16965
|
-
errorCode
|
|
17067
|
+
errorCode: errorCode2
|
|
16966
17068
|
},
|
|
16967
17069
|
observer
|
|
16968
17070
|
);
|
|
@@ -16987,12 +17089,12 @@ async function smokeWorker(context, layout, observer) {
|
|
|
16987
17089
|
"--show-interactive-dev-session=false",
|
|
16988
17090
|
"--log-level=warn",
|
|
16989
17091
|
"--persist-to",
|
|
16990
|
-
|
|
17092
|
+
join12(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
|
|
16991
17093
|
],
|
|
16992
17094
|
cwd: layout.appRoot,
|
|
16993
17095
|
env: {
|
|
16994
17096
|
CI: "1",
|
|
16995
|
-
WRANGLER_LOG_PATH:
|
|
17097
|
+
WRANGLER_LOG_PATH: join12(
|
|
16996
17098
|
layout.appRoot,
|
|
16997
17099
|
layout.outputRoot,
|
|
16998
17100
|
"wrangler-dev.log"
|
|
@@ -17008,7 +17110,7 @@ async function smokeWorker(context, layout, observer) {
|
|
|
17008
17110
|
);
|
|
17009
17111
|
}
|
|
17010
17112
|
async function approvePnpmBuilds(checkout, packages) {
|
|
17011
|
-
const path =
|
|
17113
|
+
const path = join12(checkout, "pnpm-workspace.yaml");
|
|
17012
17114
|
let source = "";
|
|
17013
17115
|
try {
|
|
17014
17116
|
source = await readFile10(path, "utf8");
|
|
@@ -17104,7 +17206,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17104
17206
|
} catch (error51) {
|
|
17105
17207
|
return failed("prepare", describeError(error51));
|
|
17106
17208
|
}
|
|
17107
|
-
const configPath =
|
|
17209
|
+
const configPath = join13(fork.appRoot, CONFIG_NAME);
|
|
17108
17210
|
try {
|
|
17109
17211
|
await writeFile6(
|
|
17110
17212
|
configPath,
|
|
@@ -17133,7 +17235,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17133
17235
|
observer
|
|
17134
17236
|
);
|
|
17135
17237
|
if (!build.ok) return failed("build", describeFailure(build));
|
|
17136
|
-
const serverFunction =
|
|
17238
|
+
const serverFunction = join13(fork.appRoot, SERVER_FUNCTION);
|
|
17137
17239
|
if (!await pathExists2(serverFunction))
|
|
17138
17240
|
return failed("bundle", `OpenNext AWS produced no ${SERVER_FUNCTION}.`);
|
|
17139
17241
|
const smoke = await context.ports.smoke.run(
|
|
@@ -17155,7 +17257,7 @@ function createOpenNextAwsStrategy() {
|
|
|
17155
17257
|
status: "success",
|
|
17156
17258
|
artifact: {
|
|
17157
17259
|
kind: "opennext-aws",
|
|
17158
|
-
outputRoot:
|
|
17260
|
+
outputRoot: join13(fork.appRoot, ".open-next"),
|
|
17159
17261
|
serverFunction
|
|
17160
17262
|
},
|
|
17161
17263
|
notes: [
|
|
@@ -17168,14 +17270,14 @@ function createOpenNextAwsStrategy() {
|
|
|
17168
17270
|
|
|
17169
17271
|
// ../../packages/build/src/strategy/strategies/opennext-cloudflare.ts
|
|
17170
17272
|
import { readFile as readFile11 } from "fs/promises";
|
|
17171
|
-
import { join as
|
|
17273
|
+
import { join as join14 } from "path";
|
|
17172
17274
|
var PROXY_TO_MIDDLEWARE_VARIANT = "proxy-to-middleware";
|
|
17173
17275
|
var NODE_MIDDLEWARE_REFUSAL = /Node\.js middleware is not (?:currently )?supported/i;
|
|
17174
17276
|
async function buildDeclaresNodeMiddleware(appRoot) {
|
|
17175
17277
|
try {
|
|
17176
17278
|
const manifest2 = JSON.parse(
|
|
17177
17279
|
await readFile11(
|
|
17178
|
-
|
|
17280
|
+
join14(appRoot, ".next", "server", "functions-config-manifest.json"),
|
|
17179
17281
|
"utf8"
|
|
17180
17282
|
)
|
|
17181
17283
|
);
|
|
@@ -17387,7 +17489,7 @@ function createAwsMiddlewareCloudflareAppStrategy() {
|
|
|
17387
17489
|
}
|
|
17388
17490
|
|
|
17389
17491
|
// ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
|
|
17390
|
-
import { join as
|
|
17492
|
+
import { join as join15 } from "path";
|
|
17391
17493
|
var VINEXT_MIN_NEXT_MAJOR = 16;
|
|
17392
17494
|
var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
|
|
17393
17495
|
var VINEXT_OUTPUT = {
|
|
@@ -17542,7 +17644,7 @@ function createVinextCloudflareStrategy(options) {
|
|
|
17542
17644
|
context,
|
|
17543
17645
|
{
|
|
17544
17646
|
appRoot: fork.appRoot,
|
|
17545
|
-
wranglerConfigPath:
|
|
17647
|
+
wranglerConfigPath: join15(
|
|
17546
17648
|
fork.appRoot,
|
|
17547
17649
|
"dist",
|
|
17548
17650
|
"server",
|
|
@@ -17566,7 +17668,7 @@ function createVinextCloudflareStrategy(options) {
|
|
|
17566
17668
|
|
|
17567
17669
|
// ../../packages/build/src/strategy/workspace.ts
|
|
17568
17670
|
import { cp } from "fs/promises";
|
|
17569
|
-
import { basename as basename2, join as
|
|
17671
|
+
import { basename as basename2, join as join16, relative as relative5, resolve as resolve6 } from "path";
|
|
17570
17672
|
var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
|
|
17571
17673
|
function shouldCopy(path) {
|
|
17572
17674
|
const name = basename2(path);
|
|
@@ -17582,7 +17684,7 @@ async function copyCheckout(source, target) {
|
|
|
17582
17684
|
});
|
|
17583
17685
|
}
|
|
17584
17686
|
function forkedAppRoot(checkout, appRoot, forkCheckout) {
|
|
17585
|
-
return
|
|
17687
|
+
return join16(forkCheckout, relative5(checkout, appRoot));
|
|
17586
17688
|
}
|
|
17587
17689
|
|
|
17588
17690
|
// ../../packages/build/src/local-build-executor.ts
|
|
@@ -17590,8 +17692,21 @@ function concurrencyFromEnvironment() {
|
|
|
17590
17692
|
const raw = Number(process.env.NARO_RESOLVER_CONCURRENCY ?? "1");
|
|
17591
17693
|
return Number.isInteger(raw) && raw > 0 ? raw : 1;
|
|
17592
17694
|
}
|
|
17695
|
+
async function withRouteList(artifact, list, appRoot, observer) {
|
|
17696
|
+
try {
|
|
17697
|
+
const routes = await list(appRoot);
|
|
17698
|
+
return routes ? { ...artifact, routes } : artifact;
|
|
17699
|
+
} catch (error51) {
|
|
17700
|
+
await observer.onLog({
|
|
17701
|
+
level: "WARN",
|
|
17702
|
+
stage: "BUILDING",
|
|
17703
|
+
message: `Speed Insights route list skipped: ${error51 instanceof Error ? error51.message : String(error51)}`
|
|
17704
|
+
});
|
|
17705
|
+
return artifact;
|
|
17706
|
+
}
|
|
17707
|
+
}
|
|
17593
17708
|
var defaultLocalBuildDependencies = {
|
|
17594
|
-
createWorkspace: () => mkdtemp(
|
|
17709
|
+
createWorkspace: () => mkdtemp(join17(tmpdir(), "next-deploy-build-")),
|
|
17595
17710
|
removeWorkspace: (path) => rm2(path, { recursive: true, force: true }),
|
|
17596
17711
|
runCommand,
|
|
17597
17712
|
detectPackageManager,
|
|
@@ -17613,6 +17728,7 @@ var defaultLocalBuildDependencies = {
|
|
|
17613
17728
|
vinextCommand: "vinext",
|
|
17614
17729
|
forkWorkspace: copyCheckout,
|
|
17615
17730
|
collectProjectFacts,
|
|
17731
|
+
listNextRoutes,
|
|
17616
17732
|
hashFingerprintInputs: hashStrategyInputs,
|
|
17617
17733
|
computeFingerprint: computeStrategyFingerprint,
|
|
17618
17734
|
targets: { cloudflare: true },
|
|
@@ -17624,7 +17740,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
|
|
|
17624
17740
|
for (; ; ) {
|
|
17625
17741
|
try {
|
|
17626
17742
|
const source = await readFile12(
|
|
17627
|
-
|
|
17743
|
+
join17(dir, "node_modules", "next", "package.json"),
|
|
17628
17744
|
"utf8"
|
|
17629
17745
|
);
|
|
17630
17746
|
const version2 = JSON.parse(source).version;
|
|
@@ -17717,8 +17833,8 @@ var LocalBuildExecutor = class {
|
|
|
17717
17833
|
}
|
|
17718
17834
|
async build(input, observer, cache) {
|
|
17719
17835
|
const workspace = await this.dependencies.createWorkspace();
|
|
17720
|
-
const checkout =
|
|
17721
|
-
const pmCacheDir =
|
|
17836
|
+
const checkout = join17(workspace, "checkout");
|
|
17837
|
+
const pmCacheDir = join17(workspace, ".pm-store");
|
|
17722
17838
|
let cacheSeq = 0;
|
|
17723
17839
|
const commandAvailable = this.dependencies.commandAvailable ?? hasCommand;
|
|
17724
17840
|
const compressor = "zstd";
|
|
@@ -17727,7 +17843,7 @@ var LocalBuildExecutor = class {
|
|
|
17727
17843
|
transport: cache,
|
|
17728
17844
|
fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
|
|
17729
17845
|
runCommand: this.dependencies.runCommand,
|
|
17730
|
-
tmpFile: (suffix) =>
|
|
17846
|
+
tmpFile: (suffix) => join17(workspace, `cache-${String(cacheSeq++)}${suffix}`),
|
|
17731
17847
|
// One latch for the whole build so a disabled cache warns once.
|
|
17732
17848
|
report: { unavailable: false }
|
|
17733
17849
|
} : null;
|
|
@@ -17911,7 +18027,7 @@ var LocalBuildExecutor = class {
|
|
|
17911
18027
|
}
|
|
17912
18028
|
}
|
|
17913
18029
|
await notifyStatus(observer, "BUILDING");
|
|
17914
|
-
const buildCacheDir = resolved.preset.cachePath ?
|
|
18030
|
+
const buildCacheDir = resolved.preset.cachePath ? join17(appRoot, resolved.preset.cachePath) : null;
|
|
17915
18031
|
const buildCacheKey = sanitizeCacheKey(input.branch);
|
|
17916
18032
|
if (cachePorts && buildCacheDir) {
|
|
17917
18033
|
await tryRestore(
|
|
@@ -17935,12 +18051,12 @@ var LocalBuildExecutor = class {
|
|
|
17935
18051
|
entries: fingerprintEntries,
|
|
17936
18052
|
nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown"
|
|
17937
18053
|
});
|
|
17938
|
-
const pristine =
|
|
18054
|
+
const pristine = join17(workspace, "pristine");
|
|
17939
18055
|
const fork = this.dependencies.forkWorkspace ?? defaultFork;
|
|
17940
18056
|
await fork(checkout, pristine);
|
|
17941
18057
|
let forks = 0;
|
|
17942
18058
|
const forkWorkspace = async (label2) => {
|
|
17943
|
-
const target =
|
|
18059
|
+
const target = join17(
|
|
17944
18060
|
workspace,
|
|
17945
18061
|
"forks",
|
|
17946
18062
|
`${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
|
|
@@ -17997,7 +18113,12 @@ var LocalBuildExecutor = class {
|
|
|
17997
18113
|
`Strategy ${resolution.strategy.id} produced a ${resolution.outcome.artifact.kind} artifact, which this control plane cannot deploy.`
|
|
17998
18114
|
);
|
|
17999
18115
|
}
|
|
18000
|
-
artifact =
|
|
18116
|
+
artifact = await withRouteList(
|
|
18117
|
+
resolution.outcome.artifact.artifact,
|
|
18118
|
+
this.dependencies.listNextRoutes ?? listNextRoutes,
|
|
18119
|
+
appRoot,
|
|
18120
|
+
observer
|
|
18121
|
+
);
|
|
18001
18122
|
strategyRecord = resolution.record;
|
|
18002
18123
|
await observer.onMetadata?.({ strategy: resolution.record });
|
|
18003
18124
|
} else {
|
|
@@ -18034,6 +18155,13 @@ var LocalBuildExecutor = class {
|
|
|
18034
18155
|
}
|
|
18035
18156
|
await notifyStatus(observer, "DEPLOYING");
|
|
18036
18157
|
artifact ??= await this.readStatic(appRoot, resolved, identity, observer);
|
|
18158
|
+
if (resolved.preset.id === "next-export")
|
|
18159
|
+
artifact = await withRouteList(
|
|
18160
|
+
artifact,
|
|
18161
|
+
this.dependencies.listNextRoutes ?? listNextRoutes,
|
|
18162
|
+
appRoot,
|
|
18163
|
+
observer
|
|
18164
|
+
);
|
|
18037
18165
|
return {
|
|
18038
18166
|
gitCommitSha,
|
|
18039
18167
|
artifact,
|
|
@@ -18193,12 +18321,12 @@ async function errorFromResponse(response, context) {
|
|
|
18193
18321
|
// ../../packages/sdk/src/session.ts
|
|
18194
18322
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
18195
18323
|
import { homedir } from "os";
|
|
18196
|
-
import { join as
|
|
18324
|
+
import { join as join18 } from "path";
|
|
18197
18325
|
function credentialsDir(home = homedir()) {
|
|
18198
|
-
return
|
|
18326
|
+
return join18(home, ".naro");
|
|
18199
18327
|
}
|
|
18200
18328
|
function credentialsFile(home = homedir()) {
|
|
18201
|
-
return
|
|
18329
|
+
return join18(credentialsDir(home), "credentials.json");
|
|
18202
18330
|
}
|
|
18203
18331
|
function saveSession(session, home = homedir()) {
|
|
18204
18332
|
mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
|
|
@@ -18598,6 +18726,15 @@ var TERMINAL_DEPLOYMENT_STATUSES = [
|
|
|
18598
18726
|
function isTerminalStatus(status) {
|
|
18599
18727
|
return TERMINAL_DEPLOYMENT_STATUSES.includes(status);
|
|
18600
18728
|
}
|
|
18729
|
+
var BACKEND_STEPS = [
|
|
18730
|
+
"database",
|
|
18731
|
+
"keys",
|
|
18732
|
+
"secrets",
|
|
18733
|
+
"worker",
|
|
18734
|
+
"routes",
|
|
18735
|
+
"migrate",
|
|
18736
|
+
"health"
|
|
18737
|
+
];
|
|
18601
18738
|
var STALLED_GRACE_MS = 2 * 6e4;
|
|
18602
18739
|
function isStalledBackend(info3, now = Date.now()) {
|
|
18603
18740
|
if (typeof info3.stalled === "boolean") return info3.stalled;
|
|
@@ -19278,7 +19415,10 @@ var DbApi = class {
|
|
|
19278
19415
|
* `d1_migrations` — all operations or none. `dryRun` returns the SQL and
|
|
19279
19416
|
* the name without applying anything. Save `sql` as `migrations/<name>` so
|
|
19280
19417
|
* `naro db reset` re-applies it; a refused operation's index is in
|
|
19281
|
-
* `details.operation`.
|
|
19418
|
+
* `details.operation`. Policies left under a name the change gives to a
|
|
19419
|
+
* table are removed before it runs and stay removed if it then fails:
|
|
19420
|
+
* that refusal carries them in `details.removedPolicies`, shaped as the
|
|
19421
|
+
* result's `removedPolicies`.
|
|
19282
19422
|
*/
|
|
19283
19423
|
async schema(projectId, operations, options = {}) {
|
|
19284
19424
|
return this.client.request(
|
|
@@ -19297,6 +19437,9 @@ var DbApi = class {
|
|
|
19297
19437
|
}
|
|
19298
19438
|
};
|
|
19299
19439
|
var PROVISION_TIMEOUT_MS = 12e4;
|
|
19440
|
+
function writeQuery(options) {
|
|
19441
|
+
return options.allowUnindexed === true ? "?allow_unindexed=1" : "";
|
|
19442
|
+
}
|
|
19300
19443
|
function backendInfo(body) {
|
|
19301
19444
|
return {
|
|
19302
19445
|
status: body.status,
|
|
@@ -19315,6 +19458,35 @@ function backendInfo(body) {
|
|
|
19315
19458
|
turningOff: body.turningOff === true
|
|
19316
19459
|
};
|
|
19317
19460
|
}
|
|
19461
|
+
function authConfig(body) {
|
|
19462
|
+
return {
|
|
19463
|
+
...body,
|
|
19464
|
+
signupsEnabled: typeof body.signupsEnabled === "boolean" ? body.signupsEnabled : true,
|
|
19465
|
+
passwordMinLength: typeof body.passwordMinLength === "number" ? body.passwordMinLength : 6
|
|
19466
|
+
};
|
|
19467
|
+
}
|
|
19468
|
+
function isRecord(value) {
|
|
19469
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19470
|
+
}
|
|
19471
|
+
async function authChange(answer) {
|
|
19472
|
+
let body;
|
|
19473
|
+
try {
|
|
19474
|
+
body = await answer;
|
|
19475
|
+
} catch (error51) {
|
|
19476
|
+
const details = isNaroError(error51) ? error51.details : void 0;
|
|
19477
|
+
if (isNaroError(error51) && error51.code === "BACKEND_PROVISION_FAILED" && isRecord(details?.auth) && isRecord(details.backend))
|
|
19478
|
+
throw new NaroError(error51.code, error51.message, {
|
|
19479
|
+
...error51.status !== void 0 ? { status: error51.status } : {},
|
|
19480
|
+
details: {
|
|
19481
|
+
...details,
|
|
19482
|
+
auth: authConfig(details.auth),
|
|
19483
|
+
backend: backendInfo(details.backend)
|
|
19484
|
+
}
|
|
19485
|
+
});
|
|
19486
|
+
throw error51;
|
|
19487
|
+
}
|
|
19488
|
+
return { auth: authConfig(body.auth), backend: backendInfo(body.backend) };
|
|
19489
|
+
}
|
|
19318
19490
|
var BackendApi = class {
|
|
19319
19491
|
constructor(client) {
|
|
19320
19492
|
this.client = client;
|
|
@@ -19447,11 +19619,13 @@ var BackendApi = class {
|
|
|
19447
19619
|
* and the callback URLs to register with the providers. Never a secret.
|
|
19448
19620
|
*/
|
|
19449
19621
|
async auth(projectId) {
|
|
19450
|
-
return
|
|
19451
|
-
this.
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
|
|
19622
|
+
return authConfig(
|
|
19623
|
+
await this.client.request(
|
|
19624
|
+
this.path(projectId, "/auth"),
|
|
19625
|
+
{
|
|
19626
|
+
context: "Read sign-in settings"
|
|
19627
|
+
}
|
|
19628
|
+
)
|
|
19455
19629
|
);
|
|
19456
19630
|
}
|
|
19457
19631
|
/**
|
|
@@ -19460,34 +19634,93 @@ var BackendApi = class {
|
|
|
19460
19634
|
* (nothing is saved), `BACKEND_NOT_READY` before the first provision,
|
|
19461
19635
|
* `BACKEND_OFF` while the backend is turned off (nothing is saved),
|
|
19462
19636
|
* `VERIFICATION_NEEDS_EMAIL` for a state where nobody could sign in. A
|
|
19463
|
-
* failed upload
|
|
19464
|
-
*
|
|
19637
|
+
* failed upload throws `BACKEND_PROVISION_FAILED` (502): the settings are
|
|
19638
|
+
* saved, the message says whether the worker runs them yet, `details`
|
|
19639
|
+
* holds `{ auth, backend }`, and `enable` finishes. (A control plane from
|
|
19640
|
+
* before that resolves with `backend.status` `error` instead.) Two
|
|
19641
|
+
* minutes, like enable.
|
|
19465
19642
|
*/
|
|
19466
19643
|
async setAuth(projectId, patch) {
|
|
19467
|
-
|
|
19468
|
-
this.path(projectId, "/auth"),
|
|
19469
|
-
{
|
|
19644
|
+
return authChange(
|
|
19645
|
+
this.client.request(this.path(projectId, "/auth"), {
|
|
19470
19646
|
method: "PUT",
|
|
19471
19647
|
body: patch,
|
|
19472
19648
|
context: "Change sign-in settings",
|
|
19473
19649
|
timeoutMs: PROVISION_TIMEOUT_MS
|
|
19474
|
-
}
|
|
19650
|
+
})
|
|
19475
19651
|
);
|
|
19476
|
-
return { auth: answer.auth, backend: backendInfo(answer.backend) };
|
|
19477
19652
|
}
|
|
19478
19653
|
/** Removes email or one provider; the same answer as `setAuth`. */
|
|
19479
19654
|
async removeAuth(projectId, target) {
|
|
19655
|
+
return authChange(
|
|
19656
|
+
this.client.request(
|
|
19657
|
+
this.path(projectId, `/auth?target=${encodeURIComponent(target)}`),
|
|
19658
|
+
{
|
|
19659
|
+
method: "DELETE",
|
|
19660
|
+
context: "Change sign-in settings",
|
|
19661
|
+
timeoutMs: PROVISION_TIMEOUT_MS
|
|
19662
|
+
}
|
|
19663
|
+
)
|
|
19664
|
+
);
|
|
19665
|
+
}
|
|
19666
|
+
/**
|
|
19667
|
+
* The project's users, newest first (spec 2026-09-26 §2.2): page 1 and 50
|
|
19668
|
+
* a page unless given, at most 1000. `database.read`. `BACKEND_OFF` while
|
|
19669
|
+
* the backend is turned off, `BACKEND_NOT_READY` before it is ready,
|
|
19670
|
+
* `BACKEND_OUTDATED` while its Naro worker predates user management
|
|
19671
|
+
* (`enable` updates it), `BACKEND_UNAVAILABLE` when it cannot be reached.
|
|
19672
|
+
*/
|
|
19673
|
+
async listUsers(projectId, options = {}) {
|
|
19674
|
+
const query = new URLSearchParams();
|
|
19675
|
+
if (options.page !== void 0) query.set("page", String(options.page));
|
|
19676
|
+
if (options.perPage !== void 0)
|
|
19677
|
+
query.set("perPage", String(options.perPage));
|
|
19678
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : "";
|
|
19679
|
+
const body = await this.client.request(
|
|
19680
|
+
this.path(projectId, `/users${suffix}`),
|
|
19681
|
+
{ context: "List users" }
|
|
19682
|
+
);
|
|
19683
|
+
return { users: body.users, total: body.total };
|
|
19684
|
+
}
|
|
19685
|
+
/**
|
|
19686
|
+
* Creates a user with a password, confirmed unless `emailConfirm: false`
|
|
19687
|
+
* — also while sign-ups are closed and while sign-in waits for a
|
|
19688
|
+
* confirmed email. A username-only user answers `email: null`.
|
|
19689
|
+
* `database.write`. The Naro worker's refusals come through as it said
|
|
19690
|
+
* them: `USERNAME_TAKEN` (409), `USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL`
|
|
19691
|
+
* (422), `PASSWORD_TOO_SHORT` ("Use at least {n} characters."),
|
|
19692
|
+
* `VALIDATION_ERROR`, `WRITE_QUOTA_EXCEEDED` (429, `details.retryAfter`).
|
|
19693
|
+
*/
|
|
19694
|
+
async createUser(projectId, input) {
|
|
19695
|
+
const body = { password: input.password };
|
|
19696
|
+
if (input.email !== void 0) body.email = input.email;
|
|
19697
|
+
if (input.username !== void 0) body.username = input.username;
|
|
19698
|
+
if (input.emailConfirm !== void 0)
|
|
19699
|
+
body.emailConfirm = input.emailConfirm;
|
|
19480
19700
|
const answer = await this.client.request(
|
|
19481
|
-
this.path(projectId,
|
|
19482
|
-
{
|
|
19483
|
-
|
|
19484
|
-
|
|
19485
|
-
|
|
19486
|
-
|
|
19701
|
+
this.path(projectId, "/users"),
|
|
19702
|
+
{ method: "POST", body, context: "Create user" }
|
|
19703
|
+
);
|
|
19704
|
+
return answer.user;
|
|
19705
|
+
}
|
|
19706
|
+
/**
|
|
19707
|
+
* Deletes a user with its sign-in methods, and ends its sessions. Answers
|
|
19708
|
+
* the user it was. `USER_NOT_FOUND` (404) for an id that is not one.
|
|
19709
|
+
* `database.write`.
|
|
19710
|
+
*/
|
|
19711
|
+
async deleteUser(projectId, userId) {
|
|
19712
|
+
const answer = await this.client.request(
|
|
19713
|
+
this.path(projectId, `/users/${encodeURIComponent(userId)}`),
|
|
19714
|
+
{ method: "DELETE", context: "Delete user" }
|
|
19487
19715
|
);
|
|
19488
|
-
return
|
|
19716
|
+
return answer.user;
|
|
19489
19717
|
}
|
|
19490
|
-
/**
|
|
19718
|
+
/**
|
|
19719
|
+
* Every table's policies as rules and conditions, one row per table —
|
|
19720
|
+
* the view `setPolicy` writes through (P2-7 §11.2). A policy that view
|
|
19721
|
+
* cannot show reads `{ invalid }`; `rlsPolicies` shows every one. Tables
|
|
19722
|
+
* without one deny anon and signed-in requests.
|
|
19723
|
+
*/
|
|
19491
19724
|
async policies(projectId) {
|
|
19492
19725
|
const body = await this.client.request(
|
|
19493
19726
|
this.path(projectId, "/policies"),
|
|
@@ -19511,8 +19744,9 @@ var BackendApi = class {
|
|
|
19511
19744
|
return answer.policy;
|
|
19512
19745
|
}
|
|
19513
19746
|
/**
|
|
19514
|
-
* Deletes the table
|
|
19515
|
-
*
|
|
19747
|
+
* Deletes every policy on the table (P2-7: the named ones too, whose
|
|
19748
|
+
* names the answer's `policies` lists), so it denies anon and signed-in
|
|
19749
|
+
* requests again. `POLICY_NOT_FOUND` when the table has none.
|
|
19516
19750
|
*/
|
|
19517
19751
|
async removePolicy(projectId, table) {
|
|
19518
19752
|
const answer = await this.client.request(
|
|
@@ -19521,6 +19755,75 @@ var BackendApi = class {
|
|
|
19521
19755
|
);
|
|
19522
19756
|
return answer.policy;
|
|
19523
19757
|
}
|
|
19758
|
+
/*
|
|
19759
|
+
* ---- Named policies (spec 2026-09-24 P2-7 §11.1) ----
|
|
19760
|
+
* As Postgres's CREATE POLICY: any number per table, each permissive or
|
|
19761
|
+
* restrictive, for a command and roles, with USING and WITH CHECK
|
|
19762
|
+
* expressions. The control plane checks them against the live schema
|
|
19763
|
+
* with the worker's own compiler; the worker picks a change up within
|
|
19764
|
+
* about 10 seconds.
|
|
19765
|
+
*
|
|
19766
|
+
* A write — `runPolicySql`, `replaceRlsPolicy`, `dropRlsPolicy`, and
|
|
19767
|
+
* `setPolicy` above — on a backend turned on before named policies is
|
|
19768
|
+
* `409 BACKEND_OUTDATED` "The project backend is older than named
|
|
19769
|
+
* policies. Run `naro backend enable` to update it, then try again."
|
|
19770
|
+
* (`backend.enable` also turns a backend that is off back on.) Reads
|
|
19771
|
+
* and `removePolicy` work there.
|
|
19772
|
+
*/
|
|
19773
|
+
/** The named policies, by table then name; only one table's with `table`. */
|
|
19774
|
+
async rlsPolicies(projectId, options = {}) {
|
|
19775
|
+
const query = options.table === void 0 ? "" : `?table=${encodeURIComponent(options.table)}`;
|
|
19776
|
+
const body = await this.client.request(
|
|
19777
|
+
this.path(projectId, `/rls/policies${query}`),
|
|
19778
|
+
{ context: "List policies" }
|
|
19779
|
+
);
|
|
19780
|
+
return body.policies;
|
|
19781
|
+
}
|
|
19782
|
+
/**
|
|
19783
|
+
* Runs one CREATE POLICY or DROP POLICY statement. A statement that does
|
|
19784
|
+
* not parse is `VALIDATION_FAILED` with Postgres's words and where
|
|
19785
|
+
* ("syntax error at or near … at character N."); a policy already there
|
|
19786
|
+
* is `POLICY_EXISTS`; DROP POLICY of none is `POLICY_NOT_FOUND` unless
|
|
19787
|
+
* it says IF EXISTS.
|
|
19788
|
+
*/
|
|
19789
|
+
async runPolicySql(projectId, sql, options = {}) {
|
|
19790
|
+
const answer = await this.client.request(this.path(projectId, `/rls/policies${writeQuery(options)}`), {
|
|
19791
|
+
method: "POST",
|
|
19792
|
+
body: { sql },
|
|
19793
|
+
context: "Run policy statement"
|
|
19794
|
+
});
|
|
19795
|
+
return "policy" in answer ? { created: answer.policy } : { dropped: answer.dropped };
|
|
19796
|
+
}
|
|
19797
|
+
/**
|
|
19798
|
+
* Replaces the named policy with a CREATE POLICY on the same table,
|
|
19799
|
+
* which may rename it. `POLICY_NOT_FOUND` when there is none to replace.
|
|
19800
|
+
* `name` is the name as typed: the policy of exactly that name, else the
|
|
19801
|
+
* one of its ASCII lower case — what `CREATE POLICY MyPolicy` stored.
|
|
19802
|
+
*/
|
|
19803
|
+
async replaceRlsPolicy(projectId, table, name, sql, options = {}) {
|
|
19804
|
+
const answer = await this.client.request(
|
|
19805
|
+
this.path(
|
|
19806
|
+
projectId,
|
|
19807
|
+
`${this.namedPath(table, name)}${writeQuery(options)}`
|
|
19808
|
+
),
|
|
19809
|
+
{ method: "PUT", body: { sql }, context: "Replace policy" }
|
|
19810
|
+
);
|
|
19811
|
+
return answer.policy;
|
|
19812
|
+
}
|
|
19813
|
+
/**
|
|
19814
|
+
* DROP POLICY: answers the policy dropped; `POLICY_NOT_FOUND` when none.
|
|
19815
|
+
* `name` is the name as typed, as for `replaceRlsPolicy`.
|
|
19816
|
+
*/
|
|
19817
|
+
async dropRlsPolicy(projectId, table, name) {
|
|
19818
|
+
const answer = await this.client.request(
|
|
19819
|
+
this.path(projectId, this.namedPath(table, name)),
|
|
19820
|
+
{ method: "DELETE", context: "Drop policy" }
|
|
19821
|
+
);
|
|
19822
|
+
return answer.policy;
|
|
19823
|
+
}
|
|
19824
|
+
namedPath(table, name) {
|
|
19825
|
+
return `/rls/policies/${encodeURIComponent(table)}/${encodeURIComponent(name)}`;
|
|
19826
|
+
}
|
|
19524
19827
|
};
|
|
19525
19828
|
|
|
19526
19829
|
// ../../packages/sdk/src/database.ts
|
|
@@ -19573,7 +19876,7 @@ function summarizeStatement(statement) {
|
|
|
19573
19876
|
|
|
19574
19877
|
// ../../packages/sdk/src/dotenv.ts
|
|
19575
19878
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
19576
|
-
import { join as
|
|
19879
|
+
import { join as join19 } from "path";
|
|
19577
19880
|
function parseDotenv(text) {
|
|
19578
19881
|
const out = {};
|
|
19579
19882
|
for (const line of text.split(/\r?\n/)) {
|
|
@@ -19591,7 +19894,7 @@ function parseDotenv(text) {
|
|
|
19591
19894
|
}
|
|
19592
19895
|
function readProjectEnv(dir) {
|
|
19593
19896
|
for (const name of [".env.local", ".env.production", ".env"]) {
|
|
19594
|
-
const path =
|
|
19897
|
+
const path = join19(dir, name);
|
|
19595
19898
|
if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
|
|
19596
19899
|
}
|
|
19597
19900
|
return {};
|
|
@@ -19606,7 +19909,7 @@ function serializeDotenv(vars) {
|
|
|
19606
19909
|
|
|
19607
19910
|
// ../../packages/sdk/src/env-scan.ts
|
|
19608
19911
|
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
|
|
19609
|
-
import { join as
|
|
19912
|
+
import { join as join20 } from "path";
|
|
19610
19913
|
var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
|
|
19611
19914
|
".ts",
|
|
19612
19915
|
".tsx",
|
|
@@ -19661,7 +19964,7 @@ function collect(dir, out) {
|
|
|
19661
19964
|
for (const entry of entries) {
|
|
19662
19965
|
if (out.length >= MAX_FILES) return;
|
|
19663
19966
|
if (entry.isSymbolicLink()) continue;
|
|
19664
|
-
const path =
|
|
19967
|
+
const path = join20(dir, entry.name);
|
|
19665
19968
|
if (entry.isDirectory()) {
|
|
19666
19969
|
if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
|
|
19667
19970
|
continue;
|
|
@@ -19693,7 +19996,7 @@ function scanEnvReferences(dir) {
|
|
|
19693
19996
|
}
|
|
19694
19997
|
}
|
|
19695
19998
|
for (const name of EXAMPLE_FILES) {
|
|
19696
|
-
const path =
|
|
19999
|
+
const path = join20(dir, name);
|
|
19697
20000
|
if (!existsSync2(path)) continue;
|
|
19698
20001
|
let parsed;
|
|
19699
20002
|
try {
|
|
@@ -19711,11 +20014,11 @@ function scanEnvReferences(dir) {
|
|
|
19711
20014
|
|
|
19712
20015
|
// ../../packages/sdk/src/framework.ts
|
|
19713
20016
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
19714
|
-
import { join as
|
|
20017
|
+
import { join as join21 } from "path";
|
|
19715
20018
|
function readPackageJson2(dir) {
|
|
19716
20019
|
try {
|
|
19717
20020
|
return JSON.parse(
|
|
19718
|
-
readFileSync4(
|
|
20021
|
+
readFileSync4(join21(dir, "package.json"), "utf8")
|
|
19719
20022
|
);
|
|
19720
20023
|
} catch {
|
|
19721
20024
|
return null;
|
|
@@ -19726,7 +20029,7 @@ function hasDependency(pkg, name) {
|
|
|
19726
20029
|
}
|
|
19727
20030
|
function hasConfig(dir, base2, evidence) {
|
|
19728
20031
|
for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
|
|
19729
|
-
if (existsSync3(
|
|
20032
|
+
if (existsSync3(join21(dir, `${base2}.${ext}`))) {
|
|
19730
20033
|
evidence.push(`${base2}.${ext}`);
|
|
19731
20034
|
return true;
|
|
19732
20035
|
}
|
|
@@ -19834,8 +20137,8 @@ function detectFramework2(dir) {
|
|
|
19834
20137
|
if (!byDependency && !byConfig) continue;
|
|
19835
20138
|
if (byDependency) evidence.push(`package.json: ${detector.dependency}`);
|
|
19836
20139
|
if (detector.framework === "nextjs") {
|
|
19837
|
-
if (existsSync3(
|
|
19838
|
-
if (existsSync3(
|
|
20140
|
+
if (existsSync3(join21(dir, "app"))) evidence.push("app/");
|
|
20141
|
+
if (existsSync3(join21(dir, "pages"))) evidence.push("pages/");
|
|
19839
20142
|
}
|
|
19840
20143
|
return {
|
|
19841
20144
|
framework: detector.framework,
|
|
@@ -19846,7 +20149,7 @@ function detectFramework2(dir) {
|
|
|
19846
20149
|
deployable: true
|
|
19847
20150
|
};
|
|
19848
20151
|
}
|
|
19849
|
-
if (!pkg && existsSync3(
|
|
20152
|
+
if (!pkg && existsSync3(join21(dir, "index.html"))) {
|
|
19850
20153
|
evidence.push("index.html (no package.json)");
|
|
19851
20154
|
return {
|
|
19852
20155
|
framework: "static",
|
|
@@ -19900,6 +20203,276 @@ function systemTablePredicate(column = "name") {
|
|
|
19900
20203
|
}
|
|
19901
20204
|
var SYSTEM_TABLE_SQL_PREDICATE = systemTablePredicate("name");
|
|
19902
20205
|
|
|
20206
|
+
// ../../packages/data-core/src/d1.ts
|
|
20207
|
+
function rowsOf(result) {
|
|
20208
|
+
const rows = result?.results ?? [];
|
|
20209
|
+
return rows.filter(
|
|
20210
|
+
(row) => typeof row === "object" && row !== null && !Array.isArray(row)
|
|
20211
|
+
);
|
|
20212
|
+
}
|
|
20213
|
+
|
|
20214
|
+
// ../../packages/data-core/src/policy-lexer.ts
|
|
20215
|
+
var PolicySyntaxError = class extends Error {
|
|
20216
|
+
constructor(reason, position) {
|
|
20217
|
+
super(`${reason} at character ${String(position)}`);
|
|
20218
|
+
this.reason = reason;
|
|
20219
|
+
this.position = position;
|
|
20220
|
+
}
|
|
20221
|
+
};
|
|
20222
|
+
var OPERATORS = [
|
|
20223
|
+
"->>",
|
|
20224
|
+
"->",
|
|
20225
|
+
"::",
|
|
20226
|
+
"<>",
|
|
20227
|
+
"!=",
|
|
20228
|
+
"<=",
|
|
20229
|
+
">=",
|
|
20230
|
+
"<@",
|
|
20231
|
+
"@>",
|
|
20232
|
+
"&&",
|
|
20233
|
+
"(",
|
|
20234
|
+
")",
|
|
20235
|
+
",",
|
|
20236
|
+
".",
|
|
20237
|
+
";",
|
|
20238
|
+
"=",
|
|
20239
|
+
"<",
|
|
20240
|
+
">",
|
|
20241
|
+
"[",
|
|
20242
|
+
"]",
|
|
20243
|
+
"*",
|
|
20244
|
+
"-"
|
|
20245
|
+
];
|
|
20246
|
+
function isSpace(char) {
|
|
20247
|
+
return char === " " || char === " " || char === "\n" || char === "\r" || char === "\f";
|
|
20248
|
+
}
|
|
20249
|
+
function isIdentStart(char) {
|
|
20250
|
+
return /[A-Za-z_]/.test(char) || char.charCodeAt(0) >= 128;
|
|
20251
|
+
}
|
|
20252
|
+
function isIdentChar(char) {
|
|
20253
|
+
return /[A-Za-z0-9_$]/.test(char) || char.charCodeAt(0) >= 128;
|
|
20254
|
+
}
|
|
20255
|
+
function isDigit(char) {
|
|
20256
|
+
return char >= "0" && char <= "9";
|
|
20257
|
+
}
|
|
20258
|
+
var NUMBER = /^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/;
|
|
20259
|
+
function tokenize(text) {
|
|
20260
|
+
const tokens = [];
|
|
20261
|
+
let index = 0;
|
|
20262
|
+
const at = (offset = 0) => text.charAt(index + offset);
|
|
20263
|
+
while (index < text.length) {
|
|
20264
|
+
const char = at();
|
|
20265
|
+
const position = index + 1;
|
|
20266
|
+
if (char === "\0")
|
|
20267
|
+
throw new PolicySyntaxError("NUL characters are not allowed", position);
|
|
20268
|
+
if (isSpace(char)) {
|
|
20269
|
+
index++;
|
|
20270
|
+
continue;
|
|
20271
|
+
}
|
|
20272
|
+
if (char === "-" && at(1) === "-") {
|
|
20273
|
+
while (index < text.length && at() !== "\n" && at() !== "\r") index++;
|
|
20274
|
+
continue;
|
|
20275
|
+
}
|
|
20276
|
+
if (char === "/" && at(1) === "*") {
|
|
20277
|
+
let depth = 0;
|
|
20278
|
+
while (index < text.length) {
|
|
20279
|
+
if (at() === "/" && at(1) === "*") {
|
|
20280
|
+
depth++;
|
|
20281
|
+
index += 2;
|
|
20282
|
+
} else if (at() === "*" && at(1) === "/") {
|
|
20283
|
+
depth--;
|
|
20284
|
+
index += 2;
|
|
20285
|
+
if (depth === 0) break;
|
|
20286
|
+
} else index++;
|
|
20287
|
+
}
|
|
20288
|
+
if (depth !== 0)
|
|
20289
|
+
throw new PolicySyntaxError("unterminated /* comment", position);
|
|
20290
|
+
continue;
|
|
20291
|
+
}
|
|
20292
|
+
if (char === "'") {
|
|
20293
|
+
let value = "";
|
|
20294
|
+
index++;
|
|
20295
|
+
for (; ; ) {
|
|
20296
|
+
if (index >= text.length)
|
|
20297
|
+
throw new PolicySyntaxError("unterminated quoted string", position);
|
|
20298
|
+
if (at() === "'") {
|
|
20299
|
+
if (at(1) === "'") {
|
|
20300
|
+
value += "'";
|
|
20301
|
+
index += 2;
|
|
20302
|
+
continue;
|
|
20303
|
+
}
|
|
20304
|
+
index++;
|
|
20305
|
+
break;
|
|
20306
|
+
}
|
|
20307
|
+
value += at();
|
|
20308
|
+
index++;
|
|
20309
|
+
}
|
|
20310
|
+
tokens.push({ kind: "string", value, quoted: false, position });
|
|
20311
|
+
continue;
|
|
20312
|
+
}
|
|
20313
|
+
if (char === '"') {
|
|
20314
|
+
let value = "";
|
|
20315
|
+
index++;
|
|
20316
|
+
for (; ; ) {
|
|
20317
|
+
if (index >= text.length)
|
|
20318
|
+
throw new PolicySyntaxError(
|
|
20319
|
+
"unterminated quoted identifier",
|
|
20320
|
+
position
|
|
20321
|
+
);
|
|
20322
|
+
if (at() === '"') {
|
|
20323
|
+
if (at(1) === '"') {
|
|
20324
|
+
value += '"';
|
|
20325
|
+
index += 2;
|
|
20326
|
+
continue;
|
|
20327
|
+
}
|
|
20328
|
+
index++;
|
|
20329
|
+
break;
|
|
20330
|
+
}
|
|
20331
|
+
value += at();
|
|
20332
|
+
index++;
|
|
20333
|
+
}
|
|
20334
|
+
if (value === "")
|
|
20335
|
+
throw new PolicySyntaxError("zero-length quoted identifier", position);
|
|
20336
|
+
tokens.push({ kind: "ident", value, quoted: true, position });
|
|
20337
|
+
continue;
|
|
20338
|
+
}
|
|
20339
|
+
if (char === "$")
|
|
20340
|
+
throw new PolicySyntaxError(
|
|
20341
|
+
"dollar-quoted strings are not supported: use '\u2026'",
|
|
20342
|
+
position
|
|
20343
|
+
);
|
|
20344
|
+
if (isDigit(char) || char === "." && isDigit(at(1))) {
|
|
20345
|
+
const match = NUMBER.exec(text.slice(index));
|
|
20346
|
+
const value = match?.[0] ?? char;
|
|
20347
|
+
index += value.length;
|
|
20348
|
+
if (index < text.length && isIdentChar(at()))
|
|
20349
|
+
throw new PolicySyntaxError(
|
|
20350
|
+
`trailing junk after numeric literal ${value}`,
|
|
20351
|
+
position
|
|
20352
|
+
);
|
|
20353
|
+
tokens.push({ kind: "number", value, quoted: false, position });
|
|
20354
|
+
continue;
|
|
20355
|
+
}
|
|
20356
|
+
if (isIdentStart(char)) {
|
|
20357
|
+
let end = index + 1;
|
|
20358
|
+
while (end < text.length && isIdentChar(text.charAt(end))) end++;
|
|
20359
|
+
const value = text.slice(index, end);
|
|
20360
|
+
if (text.charAt(end) === "'")
|
|
20361
|
+
throw new PolicySyntaxError(
|
|
20362
|
+
`${value}'\u2026' strings are not supported: use a plain '\u2026' string`,
|
|
20363
|
+
position
|
|
20364
|
+
);
|
|
20365
|
+
index = end;
|
|
20366
|
+
tokens.push({ kind: "ident", value, quoted: false, position });
|
|
20367
|
+
continue;
|
|
20368
|
+
}
|
|
20369
|
+
const operator = OPERATORS.find(
|
|
20370
|
+
(candidate) => text.startsWith(candidate, index)
|
|
20371
|
+
);
|
|
20372
|
+
if (!operator)
|
|
20373
|
+
throw new PolicySyntaxError(
|
|
20374
|
+
`unexpected character ${JSON.stringify(char)}`,
|
|
20375
|
+
position
|
|
20376
|
+
);
|
|
20377
|
+
index += operator.length;
|
|
20378
|
+
tokens.push({ kind: "op", value: operator, quoted: false, position });
|
|
20379
|
+
}
|
|
20380
|
+
tokens.push({
|
|
20381
|
+
kind: "eof",
|
|
20382
|
+
value: "",
|
|
20383
|
+
quoted: false,
|
|
20384
|
+
position: text.length + 1
|
|
20385
|
+
});
|
|
20386
|
+
return tokens;
|
|
20387
|
+
}
|
|
20388
|
+
function isKeyword(token, word) {
|
|
20389
|
+
return token !== void 0 && token.kind === "ident" && !token.quoted && asciiLower(token.value) === word;
|
|
20390
|
+
}
|
|
20391
|
+
function isOperator(token, op) {
|
|
20392
|
+
return token !== void 0 && token.kind === "op" && token.value === op;
|
|
20393
|
+
}
|
|
20394
|
+
function describeToken(token) {
|
|
20395
|
+
if (token.kind === "eof") return "end of input";
|
|
20396
|
+
if (token.kind === "string") return `'${token.value}'`;
|
|
20397
|
+
if (token.kind === "ident" && token.quoted) return `"${token.value}"`;
|
|
20398
|
+
return `"${token.value}"`;
|
|
20399
|
+
}
|
|
20400
|
+
var TokenStream = class {
|
|
20401
|
+
constructor(tokens) {
|
|
20402
|
+
this.tokens = tokens;
|
|
20403
|
+
}
|
|
20404
|
+
#index = 0;
|
|
20405
|
+
peek(offset = 0) {
|
|
20406
|
+
const token = this.tokens[Math.min(this.#index + offset, this.tokens.length - 1)];
|
|
20407
|
+
if (!token) throw new Error("TokenStream: no eof token");
|
|
20408
|
+
return token;
|
|
20409
|
+
}
|
|
20410
|
+
next() {
|
|
20411
|
+
const token = this.peek();
|
|
20412
|
+
if (token.kind !== "eof") this.#index++;
|
|
20413
|
+
return token;
|
|
20414
|
+
}
|
|
20415
|
+
/** Takes the keyword when it is next. */
|
|
20416
|
+
acceptKeyword(word) {
|
|
20417
|
+
if (!isKeyword(this.peek(), word)) return false;
|
|
20418
|
+
this.next();
|
|
20419
|
+
return true;
|
|
20420
|
+
}
|
|
20421
|
+
acceptOperator(op) {
|
|
20422
|
+
if (!isOperator(this.peek(), op)) return false;
|
|
20423
|
+
this.next();
|
|
20424
|
+
return true;
|
|
20425
|
+
}
|
|
20426
|
+
expectKeyword(word) {
|
|
20427
|
+
const token = this.peek();
|
|
20428
|
+
if (!isKeyword(token, word))
|
|
20429
|
+
throw new PolicySyntaxError(
|
|
20430
|
+
`expected ${word.toUpperCase()}, found ${describeToken(token)}`,
|
|
20431
|
+
token.position
|
|
20432
|
+
);
|
|
20433
|
+
return this.next();
|
|
20434
|
+
}
|
|
20435
|
+
expectOperator(op) {
|
|
20436
|
+
const token = this.peek();
|
|
20437
|
+
if (!isOperator(token, op))
|
|
20438
|
+
throw new PolicySyntaxError(
|
|
20439
|
+
`expected ${op}, found ${describeToken(token)}`,
|
|
20440
|
+
token.position
|
|
20441
|
+
);
|
|
20442
|
+
return this.next();
|
|
20443
|
+
}
|
|
20444
|
+
/** Postgres's own words for anything else. */
|
|
20445
|
+
unexpected(token = this.peek()) {
|
|
20446
|
+
throw new PolicySyntaxError(
|
|
20447
|
+
token.kind === "eof" ? "syntax error at end of input" : `syntax error at or near ${describeToken(token)}`,
|
|
20448
|
+
token.position
|
|
20449
|
+
);
|
|
20450
|
+
}
|
|
20451
|
+
};
|
|
20452
|
+
|
|
20453
|
+
// ../../packages/data-core/src/schema.ts
|
|
20454
|
+
var SCHEMA_SQL = [
|
|
20455
|
+
`SELECT t.name AS table_name, t.type AS table_type, c.cid AS cid, c.name AS column_name, c.type AS column_type, c."notnull" AS not_null, c.dflt_value AS default_value, c.pk AS pk, c.hidden AS hidden FROM pragma_table_list AS t JOIN pragma_table_xinfo(t.name, t.schema) AS c WHERE t.schema = 'main' AND t.type IN ('table', 'virtual') AND ${systemTablePredicate("t.name")} ORDER BY t.name, c.cid`,
|
|
20456
|
+
// Phase 2-2 (적대적 리뷰 반영): each table's CREATE TABLE text, where
|
|
20457
|
+
// alone a column's collation is written. A separate statement, so the
|
|
20458
|
+
// text comes once per table rather than once per column. D1 answers
|
|
20459
|
+
// `sqlite_master` reads (the database routes' `_tables.ts` reads it too).
|
|
20460
|
+
`SELECT name AS table_name, sql AS table_sql FROM sqlite_master WHERE type = 'table' AND ${systemTablePredicate("name")}`
|
|
20461
|
+
];
|
|
20462
|
+
var TABLE_NAMES_SQL = `SELECT name AS table_name FROM pragma_table_list WHERE schema = 'main' AND type IN ('table', 'virtual') AND ${systemTablePredicate("name")}`;
|
|
20463
|
+
function tableNamesFromRows(rows) {
|
|
20464
|
+
const names = [];
|
|
20465
|
+
for (const row of rows) {
|
|
20466
|
+
const name = row.table_name;
|
|
20467
|
+
if (typeof name === "string" && name !== "" && !isSystemTable(name))
|
|
20468
|
+
names.push(name);
|
|
20469
|
+
}
|
|
20470
|
+
return names;
|
|
20471
|
+
}
|
|
20472
|
+
async function readTableNames(db) {
|
|
20473
|
+
return tableNamesFromRows(rowsOf(await db.prepare(TABLE_NAMES_SQL).all()));
|
|
20474
|
+
}
|
|
20475
|
+
|
|
19903
20476
|
// ../../packages/data-core/src/values.ts
|
|
19904
20477
|
function isPlainObject2(value) {
|
|
19905
20478
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
@@ -19910,6 +20483,1121 @@ function isPlainObject2(value) {
|
|
|
19910
20483
|
function isValue(value) {
|
|
19911
20484
|
return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
19912
20485
|
}
|
|
20486
|
+
function utf8ByteLength(text) {
|
|
20487
|
+
let bytes = 0;
|
|
20488
|
+
for (let index = 0; index < text.length; index++) {
|
|
20489
|
+
const unit = text.charCodeAt(index);
|
|
20490
|
+
if (unit < 128) bytes += 1;
|
|
20491
|
+
else if (unit < 2048) bytes += 2;
|
|
20492
|
+
else if (unit >= 55296 && unit <= 56319 && index + 1 < text.length) {
|
|
20493
|
+
const next = text.charCodeAt(index + 1);
|
|
20494
|
+
if (next >= 56320 && next <= 57343) {
|
|
20495
|
+
bytes += 4;
|
|
20496
|
+
index++;
|
|
20497
|
+
} else bytes += 3;
|
|
20498
|
+
} else bytes += 3;
|
|
20499
|
+
}
|
|
20500
|
+
return bytes;
|
|
20501
|
+
}
|
|
20502
|
+
|
|
20503
|
+
// ../../packages/data-core/src/sql-fragments.ts
|
|
20504
|
+
function globLiteral(char) {
|
|
20505
|
+
return char === "*" || char === "?" || char === "[" ? `[${char}]` : char;
|
|
20506
|
+
}
|
|
20507
|
+
function likeToGlob(pattern) {
|
|
20508
|
+
let glob = "";
|
|
20509
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
20510
|
+
const char = pattern.charAt(index);
|
|
20511
|
+
if (char === "\\" && index + 1 < pattern.length) {
|
|
20512
|
+
index++;
|
|
20513
|
+
glob += globLiteral(pattern.charAt(index));
|
|
20514
|
+
} else if (char === "%") glob += "*";
|
|
20515
|
+
else if (char === "_") glob += "?";
|
|
20516
|
+
else glob += globLiteral(char);
|
|
20517
|
+
}
|
|
20518
|
+
return glob;
|
|
20519
|
+
}
|
|
20520
|
+
var MAX_PATTERN_BYTES = 50;
|
|
20521
|
+
function boundPattern(pattern, caseInsensitive) {
|
|
20522
|
+
return caseInsensitive ? pattern : likeToGlob(pattern);
|
|
20523
|
+
}
|
|
20524
|
+
function patternTooLong(pattern, caseInsensitive) {
|
|
20525
|
+
if (pattern.length > MAX_PATTERN_BYTES * 2) return true;
|
|
20526
|
+
return utf8ByteLength(boundPattern(pattern, caseInsensitive)) > MAX_PATTERN_BYTES;
|
|
20527
|
+
}
|
|
20528
|
+
|
|
20529
|
+
// ../../packages/data-core/src/policy-expr.ts
|
|
20530
|
+
var MAX_EXPRESSION_BYTES = 3 * 16384;
|
|
20531
|
+
var MAX_EXPRESSION_NODES = 1600;
|
|
20532
|
+
var MAX_EXPRESSION_DEPTH = 32;
|
|
20533
|
+
var MAX_SUBQUERIES = 4;
|
|
20534
|
+
var MAX_SUBQUERY_DEPTH = 2;
|
|
20535
|
+
var MAX_ARRAY_OPERATORS = 4;
|
|
20536
|
+
var MAX_POLICY_ARRAY_VALUES = 100;
|
|
20537
|
+
var MAX_IN_VALUES = 100;
|
|
20538
|
+
var MAX_POLICY_PATTERN_LENGTH = MAX_PATTERN_BYTES;
|
|
20539
|
+
var JWT_CLAIMS = ["sub", "role", "email", "email_verified"];
|
|
20540
|
+
var POLICY_FUNCTIONS = [
|
|
20541
|
+
"lower",
|
|
20542
|
+
"upper",
|
|
20543
|
+
"length",
|
|
20544
|
+
"coalesce",
|
|
20545
|
+
"now"
|
|
20546
|
+
];
|
|
20547
|
+
var VALUE_KINDS = /* @__PURE__ */ new Set([
|
|
20548
|
+
"literal",
|
|
20549
|
+
"column",
|
|
20550
|
+
"auth",
|
|
20551
|
+
"jwt",
|
|
20552
|
+
"func",
|
|
20553
|
+
"cast"
|
|
20554
|
+
]);
|
|
20555
|
+
function isValueExpr(expr) {
|
|
20556
|
+
return VALUE_KINDS.has(expr.kind);
|
|
20557
|
+
}
|
|
20558
|
+
function isConditionExpr(expr) {
|
|
20559
|
+
if (!isValueExpr(expr)) return true;
|
|
20560
|
+
if (expr.kind === "column") return true;
|
|
20561
|
+
return expr.kind === "literal" && (expr.value === null || typeof expr.value === "boolean");
|
|
20562
|
+
}
|
|
20563
|
+
var RESERVED = /* @__PURE__ */ new Set([
|
|
20564
|
+
"all",
|
|
20565
|
+
"and",
|
|
20566
|
+
"array",
|
|
20567
|
+
"as",
|
|
20568
|
+
"between",
|
|
20569
|
+
"check",
|
|
20570
|
+
"current_timestamp",
|
|
20571
|
+
"distinct",
|
|
20572
|
+
"exists",
|
|
20573
|
+
"false",
|
|
20574
|
+
"for",
|
|
20575
|
+
`from`,
|
|
20576
|
+
"ilike",
|
|
20577
|
+
"in",
|
|
20578
|
+
"is",
|
|
20579
|
+
"like",
|
|
20580
|
+
"not",
|
|
20581
|
+
"null",
|
|
20582
|
+
"on",
|
|
20583
|
+
"or",
|
|
20584
|
+
"select",
|
|
20585
|
+
"to",
|
|
20586
|
+
"true",
|
|
20587
|
+
"using",
|
|
20588
|
+
"where",
|
|
20589
|
+
"with"
|
|
20590
|
+
]);
|
|
20591
|
+
var CLAUSE_WORDS = /* @__PURE__ */ new Set([
|
|
20592
|
+
"join",
|
|
20593
|
+
"group",
|
|
20594
|
+
"order",
|
|
20595
|
+
"limit",
|
|
20596
|
+
"union",
|
|
20597
|
+
"having",
|
|
20598
|
+
"left",
|
|
20599
|
+
"inner",
|
|
20600
|
+
"cross"
|
|
20601
|
+
]);
|
|
20602
|
+
var SPECIAL_WORDS = /* @__PURE__ */ new Set([
|
|
20603
|
+
...RESERVED,
|
|
20604
|
+
...CLAUSE_WORDS,
|
|
20605
|
+
"symmetric",
|
|
20606
|
+
"auth"
|
|
20607
|
+
]);
|
|
20608
|
+
var TEXT_TYPES = /* @__PURE__ */ new Set(["text", "varchar", "uuid"]);
|
|
20609
|
+
function count(state, token) {
|
|
20610
|
+
if (++state.nodes > MAX_EXPRESSION_NODES)
|
|
20611
|
+
throw new PolicySyntaxError(
|
|
20612
|
+
`the expression has more than ${String(MAX_EXPRESSION_NODES)} parts`,
|
|
20613
|
+
token.position
|
|
20614
|
+
);
|
|
20615
|
+
}
|
|
20616
|
+
function nested(state, token, read) {
|
|
20617
|
+
if (++state.depth > MAX_EXPRESSION_DEPTH)
|
|
20618
|
+
throw new PolicySyntaxError(
|
|
20619
|
+
`the expression nests deeper than ${String(MAX_EXPRESSION_DEPTH)} levels`,
|
|
20620
|
+
token.position
|
|
20621
|
+
);
|
|
20622
|
+
try {
|
|
20623
|
+
return read();
|
|
20624
|
+
} finally {
|
|
20625
|
+
state.depth--;
|
|
20626
|
+
}
|
|
20627
|
+
}
|
|
20628
|
+
function truthProblem(operand) {
|
|
20629
|
+
const text = "is text, where 'true' reads as false";
|
|
20630
|
+
switch (operand.kind) {
|
|
20631
|
+
case "column":
|
|
20632
|
+
return void 0;
|
|
20633
|
+
case "literal":
|
|
20634
|
+
return typeof operand.value === "string" ? `a '\u2026' string ${text}: write TRUE or FALSE, or compare the string with =` : void 0;
|
|
20635
|
+
case "jwt": {
|
|
20636
|
+
const claim = printValue(operand);
|
|
20637
|
+
return `${claim} ${text}: compare it with text, ${claim} = 'true'`;
|
|
20638
|
+
}
|
|
20639
|
+
case "auth":
|
|
20640
|
+
return operand.fn === "uid" ? "auth.uid() is text: compare it, auth.uid() = user_id, or test auth.uid() IS NOT NULL" : "auth.role() is text: compare it, auth.role() = 'authenticated'";
|
|
20641
|
+
case "cast":
|
|
20642
|
+
return `a ::text cast ${text}: compare it with text, (\u2026)::text = 'true'`;
|
|
20643
|
+
case "func":
|
|
20644
|
+
switch (operand.name) {
|
|
20645
|
+
case "lower":
|
|
20646
|
+
case "upper":
|
|
20647
|
+
return `${operand.name}(\u2026) ${text}: compare it with text, ${operand.name}(\u2026) = 'true'`;
|
|
20648
|
+
case "coalesce":
|
|
20649
|
+
return "coalesce(\u2026) is not a boolean: compare it, coalesce(\u2026) = 'true'";
|
|
20650
|
+
case "length":
|
|
20651
|
+
return "length(\u2026) is a number: compare it, length(\u2026) > 0";
|
|
20652
|
+
case "now":
|
|
20653
|
+
return "now() is a time: compare it, created_at < now()";
|
|
20654
|
+
}
|
|
20655
|
+
}
|
|
20656
|
+
}
|
|
20657
|
+
function truthTestProblem(operand) {
|
|
20658
|
+
const problem = truthProblem(operand);
|
|
20659
|
+
return problem === void 0 ? void 0 : `IS TRUE and IS FALSE test a boolean, and ${problem}`;
|
|
20660
|
+
}
|
|
20661
|
+
function operandValues(operand) {
|
|
20662
|
+
return operand.kind === "func" && operand.name === "coalesce" ? operand.args.flatMap(operandValues) : [operand];
|
|
20663
|
+
}
|
|
20664
|
+
function isBooleanConstant(value) {
|
|
20665
|
+
return value.kind === "literal" && typeof value.value === "boolean";
|
|
20666
|
+
}
|
|
20667
|
+
function isTextValue(value) {
|
|
20668
|
+
switch (value.kind) {
|
|
20669
|
+
case "literal":
|
|
20670
|
+
return typeof value.value === "string";
|
|
20671
|
+
case "auth":
|
|
20672
|
+
case "jwt":
|
|
20673
|
+
case "cast":
|
|
20674
|
+
return true;
|
|
20675
|
+
case "func":
|
|
20676
|
+
return value.name !== "length" && value.name !== "coalesce";
|
|
20677
|
+
case "column":
|
|
20678
|
+
return false;
|
|
20679
|
+
}
|
|
20680
|
+
}
|
|
20681
|
+
function pair(left, right, inner = false) {
|
|
20682
|
+
return { left, right, inner };
|
|
20683
|
+
}
|
|
20684
|
+
function comparedPairs(expr) {
|
|
20685
|
+
switch (expr.kind) {
|
|
20686
|
+
case "compare":
|
|
20687
|
+
case "distinct":
|
|
20688
|
+
return [pair(expr.left, expr.right)];
|
|
20689
|
+
case "in":
|
|
20690
|
+
return expr.list.map((item) => pair(expr.operand, item));
|
|
20691
|
+
case "inSelect":
|
|
20692
|
+
return expr.subquery.select === "*" ? [] : [pair(expr.operand, expr.subquery.select, true)];
|
|
20693
|
+
case "between":
|
|
20694
|
+
return [pair(expr.operand, expr.low), pair(expr.operand, expr.high)];
|
|
20695
|
+
case "like":
|
|
20696
|
+
return [pair(expr.operand, { kind: "literal", value: expr.pattern })];
|
|
20697
|
+
default:
|
|
20698
|
+
return [];
|
|
20699
|
+
}
|
|
20700
|
+
}
|
|
20701
|
+
function booleanComparisonProblem(expr) {
|
|
20702
|
+
for (const { left, right } of comparedPairs(expr)) {
|
|
20703
|
+
const met = [...operandValues(left), ...operandValues(right)];
|
|
20704
|
+
if (!met.some(isBooleanConstant)) continue;
|
|
20705
|
+
const texts = met.filter(isTextValue);
|
|
20706
|
+
const text = texts.find((value) => value.kind !== "literal") ?? texts[0];
|
|
20707
|
+
if (text === void 0) continue;
|
|
20708
|
+
const problem = text.kind === "literal" ? "a '\u2026' string is text, where 'true' reads as false: write TRUE or FALSE on both sides, or a '\u2026' string on both" : truthProblem(text);
|
|
20709
|
+
return `a comparison with TRUE or FALSE compares a boolean, and ${String(problem)}`;
|
|
20710
|
+
}
|
|
20711
|
+
return void 0;
|
|
20712
|
+
}
|
|
20713
|
+
function comparable(expr, token) {
|
|
20714
|
+
const problem = booleanComparisonProblem(expr);
|
|
20715
|
+
if (problem !== void 0)
|
|
20716
|
+
throw new PolicySyntaxError(problem, token.position);
|
|
20717
|
+
return expr;
|
|
20718
|
+
}
|
|
20719
|
+
function requireCondition(expr, token) {
|
|
20720
|
+
if (!isConditionExpr(expr)) {
|
|
20721
|
+
const problem = isValueExpr(expr) ? truthProblem(expr) : void 0;
|
|
20722
|
+
throw new PolicySyntaxError(
|
|
20723
|
+
`this is a value, not a condition: ${problem ?? "compare it with =, IN, LIKE, IS \u2026 (a column alone reads as its truth value)"}`,
|
|
20724
|
+
token.position
|
|
20725
|
+
);
|
|
20726
|
+
}
|
|
20727
|
+
return expr;
|
|
20728
|
+
}
|
|
20729
|
+
function requireValue(expr, token) {
|
|
20730
|
+
if (!isValueExpr(expr))
|
|
20731
|
+
throw new PolicySyntaxError(
|
|
20732
|
+
"a comparison compares values: combine conditions with AND, OR and NOT",
|
|
20733
|
+
token.position
|
|
20734
|
+
);
|
|
20735
|
+
return expr;
|
|
20736
|
+
}
|
|
20737
|
+
function readOr(state) {
|
|
20738
|
+
const first = state.stream.peek();
|
|
20739
|
+
const args = [readAnd(state)];
|
|
20740
|
+
while (isKeyword(state.stream.peek(), "or")) {
|
|
20741
|
+
const token = state.stream.next();
|
|
20742
|
+
count(state, token);
|
|
20743
|
+
args.push(readAnd(state));
|
|
20744
|
+
}
|
|
20745
|
+
if (args.length === 1) return args[0];
|
|
20746
|
+
for (const arg of args) requireCondition(arg, first);
|
|
20747
|
+
return { kind: "or", args };
|
|
20748
|
+
}
|
|
20749
|
+
function readAnd(state) {
|
|
20750
|
+
const first = state.stream.peek();
|
|
20751
|
+
const args = [readNot(state)];
|
|
20752
|
+
while (isKeyword(state.stream.peek(), "and")) {
|
|
20753
|
+
const token = state.stream.next();
|
|
20754
|
+
count(state, token);
|
|
20755
|
+
args.push(readNot(state));
|
|
20756
|
+
}
|
|
20757
|
+
if (args.length === 1) return args[0];
|
|
20758
|
+
for (const arg of args) requireCondition(arg, first);
|
|
20759
|
+
return { kind: "and", args };
|
|
20760
|
+
}
|
|
20761
|
+
function readNot(state) {
|
|
20762
|
+
const token = state.stream.peek();
|
|
20763
|
+
if (isKeyword(token, "not")) {
|
|
20764
|
+
state.stream.next();
|
|
20765
|
+
count(state, token);
|
|
20766
|
+
return nested(state, token, () => {
|
|
20767
|
+
const inner = state.stream.peek();
|
|
20768
|
+
return { kind: "not", arg: requireCondition(readNot(state), inner) };
|
|
20769
|
+
});
|
|
20770
|
+
}
|
|
20771
|
+
return readPredicate(state);
|
|
20772
|
+
}
|
|
20773
|
+
function readPredicate(state) {
|
|
20774
|
+
const { stream } = state;
|
|
20775
|
+
const token = stream.peek();
|
|
20776
|
+
if (isKeyword(token, "exists")) {
|
|
20777
|
+
stream.next();
|
|
20778
|
+
count(state, token);
|
|
20779
|
+
stream.expectOperator("(");
|
|
20780
|
+
const subquery = readSubquery(state, token, "exists");
|
|
20781
|
+
stream.expectOperator(")");
|
|
20782
|
+
return { kind: "exists", subquery };
|
|
20783
|
+
}
|
|
20784
|
+
const left = readValue(state);
|
|
20785
|
+
const next = stream.peek();
|
|
20786
|
+
const compare2 = ["=", "<>", "!=", "<", "<=", ">", ">="].find(
|
|
20787
|
+
(op) => isOperator(next, op)
|
|
20788
|
+
);
|
|
20789
|
+
if (compare2) {
|
|
20790
|
+
stream.next();
|
|
20791
|
+
count(state, next);
|
|
20792
|
+
const right = readValue(state);
|
|
20793
|
+
return comparable(
|
|
20794
|
+
{
|
|
20795
|
+
kind: "compare",
|
|
20796
|
+
op: compare2 === "!=" ? "<>" : compare2,
|
|
20797
|
+
left: requireValue(left, token),
|
|
20798
|
+
right: requireValue(right, next)
|
|
20799
|
+
},
|
|
20800
|
+
next
|
|
20801
|
+
);
|
|
20802
|
+
}
|
|
20803
|
+
if (isKeyword(next, "is")) {
|
|
20804
|
+
stream.next();
|
|
20805
|
+
count(state, next);
|
|
20806
|
+
const operand = requireValue(left, token);
|
|
20807
|
+
const not2 = stream.acceptKeyword("not");
|
|
20808
|
+
if (stream.acceptKeyword("null"))
|
|
20809
|
+
return { kind: "is", not: not2, operand, value: null };
|
|
20810
|
+
const truth = stream.acceptKeyword("true") ? true : stream.acceptKeyword("false") ? false : void 0;
|
|
20811
|
+
if (truth !== void 0) {
|
|
20812
|
+
const problem = truthTestProblem(operand);
|
|
20813
|
+
if (problem !== void 0)
|
|
20814
|
+
throw new PolicySyntaxError(problem, next.position);
|
|
20815
|
+
return { kind: "is", not: not2, operand, value: truth };
|
|
20816
|
+
}
|
|
20817
|
+
if (stream.acceptKeyword("distinct")) {
|
|
20818
|
+
stream.expectKeyword(`from`);
|
|
20819
|
+
const right = requireValue(readValue(state), stream.peek());
|
|
20820
|
+
return comparable({ kind: "distinct", not: not2, left: operand, right }, next);
|
|
20821
|
+
}
|
|
20822
|
+
stream.unexpected();
|
|
20823
|
+
}
|
|
20824
|
+
if (isOperator(next, "@>") || isOperator(next, "<@") || isOperator(next, "&&"))
|
|
20825
|
+
return readArray(state, left, token);
|
|
20826
|
+
let not = false;
|
|
20827
|
+
let word = next;
|
|
20828
|
+
if (isKeyword(next, "not")) {
|
|
20829
|
+
const after = stream.peek(1);
|
|
20830
|
+
if (isKeyword(after, "in") || isKeyword(after, "like") || isKeyword(after, "ilike") || isKeyword(after, "between")) {
|
|
20831
|
+
stream.next();
|
|
20832
|
+
not = true;
|
|
20833
|
+
word = after;
|
|
20834
|
+
} else return left;
|
|
20835
|
+
}
|
|
20836
|
+
if (isKeyword(word, "in")) {
|
|
20837
|
+
stream.next();
|
|
20838
|
+
count(state, word);
|
|
20839
|
+
const operand = requireValue(left, token);
|
|
20840
|
+
stream.expectOperator("(");
|
|
20841
|
+
if (isKeyword(stream.peek(), "select")) {
|
|
20842
|
+
const subquery = readSubquery(state, word, "in");
|
|
20843
|
+
stream.expectOperator(")");
|
|
20844
|
+
return comparable({ kind: "inSelect", not, operand, subquery }, word);
|
|
20845
|
+
}
|
|
20846
|
+
const list = [];
|
|
20847
|
+
do {
|
|
20848
|
+
if (list.length >= MAX_IN_VALUES)
|
|
20849
|
+
throw new PolicySyntaxError(
|
|
20850
|
+
`IN lists at most ${String(MAX_IN_VALUES)} values`,
|
|
20851
|
+
stream.peek().position
|
|
20852
|
+
);
|
|
20853
|
+
const item = stream.peek();
|
|
20854
|
+
list.push(requireValue(readValue(state), item));
|
|
20855
|
+
} while (stream.acceptOperator(","));
|
|
20856
|
+
stream.expectOperator(")");
|
|
20857
|
+
return comparable({ kind: "in", not, operand, list }, word);
|
|
20858
|
+
}
|
|
20859
|
+
if (isKeyword(word, "like") || isKeyword(word, "ilike")) {
|
|
20860
|
+
stream.next();
|
|
20861
|
+
count(state, word);
|
|
20862
|
+
const operand = requireValue(left, token);
|
|
20863
|
+
const pattern = stream.peek();
|
|
20864
|
+
if (pattern.kind !== "string")
|
|
20865
|
+
throw new PolicySyntaxError(
|
|
20866
|
+
"a LIKE or ILIKE pattern must be a '\u2026' string",
|
|
20867
|
+
pattern.position
|
|
20868
|
+
);
|
|
20869
|
+
stream.next();
|
|
20870
|
+
const ci = isKeyword(word, "ilike");
|
|
20871
|
+
checkPattern(pattern, ci);
|
|
20872
|
+
return comparable(
|
|
20873
|
+
{ kind: "like", not, ci, operand, pattern: pattern.value },
|
|
20874
|
+
word
|
|
20875
|
+
);
|
|
20876
|
+
}
|
|
20877
|
+
if (isKeyword(word, "between")) {
|
|
20878
|
+
stream.next();
|
|
20879
|
+
count(state, word);
|
|
20880
|
+
const operand = requireValue(left, token);
|
|
20881
|
+
if (stream.acceptKeyword("symmetric"))
|
|
20882
|
+
throw new PolicySyntaxError(
|
|
20883
|
+
"BETWEEN SYMMETRIC is not supported",
|
|
20884
|
+
word.position
|
|
20885
|
+
);
|
|
20886
|
+
const low = requireValue(readValue(state), stream.peek());
|
|
20887
|
+
stream.expectKeyword("and");
|
|
20888
|
+
const high = requireValue(readValue(state), stream.peek());
|
|
20889
|
+
return comparable({ kind: "between", not, operand, low, high }, word);
|
|
20890
|
+
}
|
|
20891
|
+
return left;
|
|
20892
|
+
}
|
|
20893
|
+
var PATTERN_SHOWN = 24;
|
|
20894
|
+
function policyPatternProblem(pattern, ci) {
|
|
20895
|
+
if (!patternTooLong(pattern, ci)) return void 0;
|
|
20896
|
+
const chars = [...pattern];
|
|
20897
|
+
const shown = `${commentText(chars.slice(0, PATTERN_SHOWN).join(""))}${chars.length > PATTERN_SHOWN ? "\u2026" : ""}`;
|
|
20898
|
+
const bytes = utf8ByteLength(boundPattern(pattern, ci));
|
|
20899
|
+
return `${ci ? "an ILIKE" : "a LIKE"} pattern may be at most ${String(MAX_POLICY_PATTERN_LENGTH)} bytes of UTF-8, D1's limit, and '${shown}' is ${String(bytes)}${ci ? "" : " (LIKE counts each *, ? and [ as 3 bytes)"}`;
|
|
20900
|
+
}
|
|
20901
|
+
function checkPattern(token, ci) {
|
|
20902
|
+
const pattern = token.value;
|
|
20903
|
+
const problem = policyPatternProblem(pattern, ci);
|
|
20904
|
+
if (problem !== void 0)
|
|
20905
|
+
throw new PolicySyntaxError(problem, token.position);
|
|
20906
|
+
let run = 0;
|
|
20907
|
+
for (let index = pattern.length - 1; index >= 0; index--) {
|
|
20908
|
+
if (pattern[index] !== "\\") break;
|
|
20909
|
+
run++;
|
|
20910
|
+
}
|
|
20911
|
+
if (run % 2 === 1)
|
|
20912
|
+
throw new PolicySyntaxError(
|
|
20913
|
+
"a LIKE pattern must not end with an unescaped backslash",
|
|
20914
|
+
token.position
|
|
20915
|
+
);
|
|
20916
|
+
}
|
|
20917
|
+
function readArray(state, left, start) {
|
|
20918
|
+
const { stream } = state;
|
|
20919
|
+
const opToken = stream.next();
|
|
20920
|
+
count(state, opToken);
|
|
20921
|
+
if (left.kind !== "column")
|
|
20922
|
+
throw new PolicySyntaxError(
|
|
20923
|
+
`the left side of ${opToken.value} must be a column`,
|
|
20924
|
+
start.position
|
|
20925
|
+
);
|
|
20926
|
+
if (++state.arrays > MAX_ARRAY_OPERATORS)
|
|
20927
|
+
throw new PolicySyntaxError(
|
|
20928
|
+
`an expression may use @>, <@ and && at most ${String(MAX_ARRAY_OPERATORS)} times`,
|
|
20929
|
+
opToken.position
|
|
20930
|
+
);
|
|
20931
|
+
stream.expectKeyword("array");
|
|
20932
|
+
stream.expectOperator("[");
|
|
20933
|
+
const values = [];
|
|
20934
|
+
do {
|
|
20935
|
+
const item = stream.peek();
|
|
20936
|
+
if (values.length >= MAX_POLICY_ARRAY_VALUES)
|
|
20937
|
+
throw new PolicySyntaxError(
|
|
20938
|
+
`ARRAY[\u2026] lists at most ${String(MAX_POLICY_ARRAY_VALUES)} values`,
|
|
20939
|
+
item.position
|
|
20940
|
+
);
|
|
20941
|
+
const value = readValue(state);
|
|
20942
|
+
if (value.kind === "auth" && value.fn === "uid") values.push(value);
|
|
20943
|
+
else if (value.kind === "literal" && (typeof value.value === "string" || typeof value.value === "number"))
|
|
20944
|
+
values.push(value.value);
|
|
20945
|
+
else
|
|
20946
|
+
throw new PolicySyntaxError(
|
|
20947
|
+
"ARRAY[\u2026] holds strings, numbers and auth.uid()",
|
|
20948
|
+
item.position
|
|
20949
|
+
);
|
|
20950
|
+
} while (stream.acceptOperator(","));
|
|
20951
|
+
stream.expectOperator("]");
|
|
20952
|
+
return {
|
|
20953
|
+
kind: "array",
|
|
20954
|
+
op: opToken.value,
|
|
20955
|
+
operand: left,
|
|
20956
|
+
values
|
|
20957
|
+
};
|
|
20958
|
+
}
|
|
20959
|
+
function readSubquery(state, start, kind) {
|
|
20960
|
+
const { stream } = state;
|
|
20961
|
+
if (++state.subqueries > MAX_SUBQUERIES)
|
|
20962
|
+
throw new PolicySyntaxError(
|
|
20963
|
+
`an expression may have at most ${String(MAX_SUBQUERIES)} subqueries`,
|
|
20964
|
+
start.position
|
|
20965
|
+
);
|
|
20966
|
+
if (++state.subqueryDepth > MAX_SUBQUERY_DEPTH)
|
|
20967
|
+
throw new PolicySyntaxError(
|
|
20968
|
+
`subqueries nest at most ${String(MAX_SUBQUERY_DEPTH)} deep`,
|
|
20969
|
+
start.position
|
|
20970
|
+
);
|
|
20971
|
+
try {
|
|
20972
|
+
stream.expectKeyword("select");
|
|
20973
|
+
let select;
|
|
20974
|
+
const item = stream.peek();
|
|
20975
|
+
if (stream.acceptOperator("*")) {
|
|
20976
|
+
if (kind === "in")
|
|
20977
|
+
throw new PolicySyntaxError(
|
|
20978
|
+
"IN (SELECT \u2026) selects one value, not *",
|
|
20979
|
+
item.position
|
|
20980
|
+
);
|
|
20981
|
+
select = "*";
|
|
20982
|
+
} else select = requireValue(readValue(state), item);
|
|
20983
|
+
if (isOperator(stream.peek(), ","))
|
|
20984
|
+
throw new PolicySyntaxError(
|
|
20985
|
+
"a subquery here selects one value",
|
|
20986
|
+
stream.peek().position
|
|
20987
|
+
);
|
|
20988
|
+
stream.expectKeyword(`from`);
|
|
20989
|
+
const table = readTableName(stream);
|
|
20990
|
+
let alias = null;
|
|
20991
|
+
if (stream.acceptKeyword("as")) alias = readIdentifier(stream, "an alias");
|
|
20992
|
+
else {
|
|
20993
|
+
const next = stream.peek();
|
|
20994
|
+
if (next.kind === "ident" && (next.quoted || !isReservedWord(next)))
|
|
20995
|
+
alias = readIdentifier(stream, "an alias");
|
|
20996
|
+
}
|
|
20997
|
+
let where = null;
|
|
20998
|
+
if (stream.acceptKeyword("where")) {
|
|
20999
|
+
const token = stream.peek();
|
|
21000
|
+
where = requireCondition(
|
|
21001
|
+
nested(state, token, () => readOr(state)),
|
|
21002
|
+
token
|
|
21003
|
+
);
|
|
21004
|
+
}
|
|
21005
|
+
const after = stream.peek();
|
|
21006
|
+
for (const word of ["join", "group", "order", "limit", "union", "having"])
|
|
21007
|
+
if (isKeyword(after, word) || isOperator(after, ","))
|
|
21008
|
+
throw new PolicySyntaxError(
|
|
21009
|
+
"a subquery in a policy reads one table: SELECT \u2026 FROM t [alias] [WHERE \u2026]",
|
|
21010
|
+
after.position
|
|
21011
|
+
);
|
|
21012
|
+
return { table, alias, select, where };
|
|
21013
|
+
} finally {
|
|
21014
|
+
state.subqueryDepth--;
|
|
21015
|
+
}
|
|
21016
|
+
}
|
|
21017
|
+
function isReservedWord(token) {
|
|
21018
|
+
return token.kind === "ident" && !token.quoted && (RESERVED.has(asciiLower(token.value)) || CLAUSE_WORDS.has(asciiLower(token.value)));
|
|
21019
|
+
}
|
|
21020
|
+
function readIdentifier(stream, what) {
|
|
21021
|
+
const token = stream.peek();
|
|
21022
|
+
if (token.kind !== "ident" || !token.quoted && isReservedWord(token))
|
|
21023
|
+
throw new PolicySyntaxError(
|
|
21024
|
+
`expected ${what}, found ${token.kind === "eof" ? "end of input" : JSON.stringify(token.value)}`,
|
|
21025
|
+
token.position
|
|
21026
|
+
);
|
|
21027
|
+
stream.next();
|
|
21028
|
+
return token.value;
|
|
21029
|
+
}
|
|
21030
|
+
function readTableName(stream) {
|
|
21031
|
+
const first = stream.peek();
|
|
21032
|
+
const name = readIdentifier(stream, "a table name");
|
|
21033
|
+
if (!stream.acceptOperator(".")) return name;
|
|
21034
|
+
if (first.quoted ? name !== "public" : asciiLower(name) !== "public")
|
|
21035
|
+
throw new PolicySyntaxError(
|
|
21036
|
+
`schema ${name} does not exist: naro's tables are in public`,
|
|
21037
|
+
first.position
|
|
21038
|
+
);
|
|
21039
|
+
return readIdentifier(stream, "a table name");
|
|
21040
|
+
}
|
|
21041
|
+
function readNumber(token, negative) {
|
|
21042
|
+
const value = Number(token.value) * (negative ? -1 : 1);
|
|
21043
|
+
if (!Number.isFinite(value))
|
|
21044
|
+
throw new PolicySyntaxError(
|
|
21045
|
+
`${token.value} is out of range`,
|
|
21046
|
+
token.position
|
|
21047
|
+
);
|
|
21048
|
+
if (/[.eE]/.test(token.value)) return { kind: "literal", value };
|
|
21049
|
+
if (!Number.isSafeInteger(value))
|
|
21050
|
+
throw new PolicySyntaxError(
|
|
21051
|
+
`${token.value} is out of range: integers are at most 9007199254740991`,
|
|
21052
|
+
token.position
|
|
21053
|
+
);
|
|
21054
|
+
return { kind: "literal", value: value === 0 ? 0 : value };
|
|
21055
|
+
}
|
|
21056
|
+
function readValue(state) {
|
|
21057
|
+
const { stream } = state;
|
|
21058
|
+
const token = stream.peek();
|
|
21059
|
+
count(state, token);
|
|
21060
|
+
let value = nested(state, token, () => readPrimary(state));
|
|
21061
|
+
while (isOperator(stream.peek(), "::")) {
|
|
21062
|
+
const cast = stream.next();
|
|
21063
|
+
count(state, cast);
|
|
21064
|
+
const type = stream.peek();
|
|
21065
|
+
if (type.kind !== "ident" || type.quoted || !TEXT_TYPES.has(asciiLower(type.value)))
|
|
21066
|
+
throw new PolicySyntaxError(
|
|
21067
|
+
"casts are ::text, ::varchar and ::uuid (all read as text)",
|
|
21068
|
+
type.position
|
|
21069
|
+
);
|
|
21070
|
+
stream.next();
|
|
21071
|
+
value = { kind: "cast", operand: requireValue(value, token) };
|
|
21072
|
+
}
|
|
21073
|
+
return value;
|
|
21074
|
+
}
|
|
21075
|
+
function readAuth(state, start, allowBareJwt) {
|
|
21076
|
+
const { stream } = state;
|
|
21077
|
+
const name = stream.next();
|
|
21078
|
+
const fn = name.kind === "ident" ? asciiLower(name.value) : "";
|
|
21079
|
+
if (!isOperator(stream.peek(), "(")) {
|
|
21080
|
+
if (fn === "uid" || fn === "role" || fn === "jwt")
|
|
21081
|
+
throw new PolicySyntaxError(
|
|
21082
|
+
`auth.${fn} needs parentheses: auth.${fn}()`,
|
|
21083
|
+
start.position
|
|
21084
|
+
);
|
|
21085
|
+
throw new PolicySyntaxError(
|
|
21086
|
+
`auth.${name.value} is not available: use auth.uid(), auth.role() or auth.jwt()`,
|
|
21087
|
+
start.position
|
|
21088
|
+
);
|
|
21089
|
+
}
|
|
21090
|
+
if (fn !== "uid" && fn !== "role" && fn !== "jwt")
|
|
21091
|
+
throw new PolicySyntaxError(
|
|
21092
|
+
`auth.${name.value}() is not available: use auth.uid(), auth.role() or auth.jwt()`,
|
|
21093
|
+
start.position
|
|
21094
|
+
);
|
|
21095
|
+
stream.expectOperator("(");
|
|
21096
|
+
stream.expectOperator(")");
|
|
21097
|
+
if (fn !== "jwt") return { kind: "auth", fn };
|
|
21098
|
+
const access9 = readJwtAccess(stream);
|
|
21099
|
+
if (access9) return access9;
|
|
21100
|
+
if (allowBareJwt) return "jwt";
|
|
21101
|
+
throw new PolicySyntaxError(
|
|
21102
|
+
"auth.jwt() is read one claim at a time: auth.jwt() ->> 'email'",
|
|
21103
|
+
start.position
|
|
21104
|
+
);
|
|
21105
|
+
}
|
|
21106
|
+
function readJwtAccess(stream) {
|
|
21107
|
+
const arrow = stream.peek();
|
|
21108
|
+
if (!isOperator(arrow, "->") && !isOperator(arrow, "->>")) return void 0;
|
|
21109
|
+
stream.next();
|
|
21110
|
+
const claim = stream.peek();
|
|
21111
|
+
if (claim.kind !== "string")
|
|
21112
|
+
throw new PolicySyntaxError(
|
|
21113
|
+
"a claim is a '\u2026' string: auth.jwt() ->> 'email'",
|
|
21114
|
+
claim.position
|
|
21115
|
+
);
|
|
21116
|
+
stream.next();
|
|
21117
|
+
if (!JWT_CLAIMS.includes(claim.value))
|
|
21118
|
+
throw new PolicySyntaxError(
|
|
21119
|
+
`auth.jwt() has ${JWT_CLAIMS.slice(0, -1).join(", ")} and ${String(JWT_CLAIMS.at(-1))}; ${JSON.stringify(claim.value)} is not one of them`,
|
|
21120
|
+
claim.position
|
|
21121
|
+
);
|
|
21122
|
+
const next = stream.peek();
|
|
21123
|
+
if (isOperator(next, "->") || isOperator(next, "->>"))
|
|
21124
|
+
throw new PolicySyntaxError(
|
|
21125
|
+
"naro's auth.jwt() claims are not nested",
|
|
21126
|
+
next.position
|
|
21127
|
+
);
|
|
21128
|
+
return {
|
|
21129
|
+
kind: "jwt",
|
|
21130
|
+
op: arrow.value,
|
|
21131
|
+
claim: claim.value
|
|
21132
|
+
};
|
|
21133
|
+
}
|
|
21134
|
+
function readPrimary(state) {
|
|
21135
|
+
const { stream } = state;
|
|
21136
|
+
const token = stream.next();
|
|
21137
|
+
switch (token.kind) {
|
|
21138
|
+
case "string":
|
|
21139
|
+
return { kind: "literal", value: token.value };
|
|
21140
|
+
case "number":
|
|
21141
|
+
return readNumber(token, false);
|
|
21142
|
+
case "op": {
|
|
21143
|
+
if (token.value === "-") {
|
|
21144
|
+
const number4 = stream.peek();
|
|
21145
|
+
if (number4.kind !== "number")
|
|
21146
|
+
throw new PolicySyntaxError(
|
|
21147
|
+
"only a number can be negated",
|
|
21148
|
+
token.position
|
|
21149
|
+
);
|
|
21150
|
+
stream.next();
|
|
21151
|
+
return readNumber(number4, true);
|
|
21152
|
+
}
|
|
21153
|
+
if (token.value === "(") {
|
|
21154
|
+
if (isKeyword(stream.peek(), "select")) {
|
|
21155
|
+
const select = stream.next();
|
|
21156
|
+
const auth = stream.peek();
|
|
21157
|
+
if (!isKeyword(auth, "auth") || !isOperator(stream.peek(1), "."))
|
|
21158
|
+
throw new PolicySyntaxError(
|
|
21159
|
+
"a scalar subquery here must be (select auth.uid()), (select auth.role()) or (select auth.jwt() ->> '\u2026'): read other tables with EXISTS (\u2026) or IN (SELECT \u2026)",
|
|
21160
|
+
select.position
|
|
21161
|
+
);
|
|
21162
|
+
stream.next();
|
|
21163
|
+
stream.next();
|
|
21164
|
+
const inner2 = readAuth(state, auth, true);
|
|
21165
|
+
stream.expectOperator(")");
|
|
21166
|
+
if (inner2 !== "jwt") return inner2;
|
|
21167
|
+
const access9 = readJwtAccess(stream);
|
|
21168
|
+
if (!access9)
|
|
21169
|
+
throw new PolicySyntaxError(
|
|
21170
|
+
"auth.jwt() is read one claim at a time: (select auth.jwt()) ->> 'email'",
|
|
21171
|
+
auth.position
|
|
21172
|
+
);
|
|
21173
|
+
return access9;
|
|
21174
|
+
}
|
|
21175
|
+
const inner = readOr(state);
|
|
21176
|
+
stream.expectOperator(")");
|
|
21177
|
+
return inner;
|
|
21178
|
+
}
|
|
21179
|
+
return stream.unexpected(token);
|
|
21180
|
+
}
|
|
21181
|
+
case "ident": {
|
|
21182
|
+
if (!token.quoted) {
|
|
21183
|
+
const word = asciiLower(token.value);
|
|
21184
|
+
if (word === "true") return { kind: "literal", value: true };
|
|
21185
|
+
if (word === "false") return { kind: "literal", value: false };
|
|
21186
|
+
if (word === "null") return { kind: "literal", value: null };
|
|
21187
|
+
if (word === "current_timestamp")
|
|
21188
|
+
return { kind: "func", name: "now", args: [] };
|
|
21189
|
+
if (word === "auth" && isOperator(stream.peek(), ".")) {
|
|
21190
|
+
stream.next();
|
|
21191
|
+
const auth = readAuth(state, token, false);
|
|
21192
|
+
if (auth === "jwt") return stream.unexpected();
|
|
21193
|
+
return auth;
|
|
21194
|
+
}
|
|
21195
|
+
if (isOperator(stream.peek(), "(")) return readCall(state, token);
|
|
21196
|
+
if (RESERVED.has(word)) return stream.unexpected(token);
|
|
21197
|
+
}
|
|
21198
|
+
if (isOperator(stream.peek(), ".")) {
|
|
21199
|
+
stream.next();
|
|
21200
|
+
const name = stream.peek();
|
|
21201
|
+
if (name.kind !== "ident" || !name.quoted && isReservedWord(name))
|
|
21202
|
+
return stream.unexpected(name);
|
|
21203
|
+
stream.next();
|
|
21204
|
+
if (isOperator(stream.peek(), "(") || isOperator(stream.peek(), "."))
|
|
21205
|
+
throw new PolicySyntaxError(
|
|
21206
|
+
`${token.value}.${name.value} is not a column: naro reads column or table.column`,
|
|
21207
|
+
token.position
|
|
21208
|
+
);
|
|
21209
|
+
return { kind: "column", qualifier: token.value, name: name.value };
|
|
21210
|
+
}
|
|
21211
|
+
return { kind: "column", qualifier: null, name: token.value };
|
|
21212
|
+
}
|
|
21213
|
+
default:
|
|
21214
|
+
return stream.unexpected(token);
|
|
21215
|
+
}
|
|
21216
|
+
}
|
|
21217
|
+
function readCall(state, name) {
|
|
21218
|
+
const { stream } = state;
|
|
21219
|
+
const word = asciiLower(name.value);
|
|
21220
|
+
if (!POLICY_FUNCTIONS.includes(word))
|
|
21221
|
+
throw new PolicySyntaxError(
|
|
21222
|
+
`function ${name.value} is not available in policies: use lower, upper, coalesce, length or now`,
|
|
21223
|
+
name.position
|
|
21224
|
+
);
|
|
21225
|
+
stream.expectOperator("(");
|
|
21226
|
+
const args = [];
|
|
21227
|
+
if (!isOperator(stream.peek(), ")"))
|
|
21228
|
+
do {
|
|
21229
|
+
const item = stream.peek();
|
|
21230
|
+
args.push(requireValue(readValue(state), item));
|
|
21231
|
+
} while (stream.acceptOperator(","));
|
|
21232
|
+
stream.expectOperator(")");
|
|
21233
|
+
const arity = {
|
|
21234
|
+
lower: [1, 1],
|
|
21235
|
+
upper: [1, 1],
|
|
21236
|
+
length: [1, 1],
|
|
21237
|
+
coalesce: [1, 16],
|
|
21238
|
+
now: [0, 0]
|
|
21239
|
+
};
|
|
21240
|
+
const [min, max] = arity[word];
|
|
21241
|
+
if (args.length < min || args.length > max)
|
|
21242
|
+
throw new PolicySyntaxError(
|
|
21243
|
+
min === max ? `${word}() takes ${String(min)} argument${min === 1 ? "" : "s"}` : `${word}() takes ${String(min)} to ${String(max)} arguments`,
|
|
21244
|
+
name.position
|
|
21245
|
+
);
|
|
21246
|
+
return { kind: "func", name: word, args };
|
|
21247
|
+
}
|
|
21248
|
+
function newState(stream) {
|
|
21249
|
+
return {
|
|
21250
|
+
stream,
|
|
21251
|
+
nodes: 0,
|
|
21252
|
+
depth: 0,
|
|
21253
|
+
subqueries: 0,
|
|
21254
|
+
subqueryDepth: 0,
|
|
21255
|
+
arrays: 0
|
|
21256
|
+
};
|
|
21257
|
+
}
|
|
21258
|
+
function readCondition(stream) {
|
|
21259
|
+
const token = stream.peek();
|
|
21260
|
+
const expr = readOr(newState(stream));
|
|
21261
|
+
return requireCondition(expr, token);
|
|
21262
|
+
}
|
|
21263
|
+
function checkExpressionSize(expr, position) {
|
|
21264
|
+
const text = printExpr(expr);
|
|
21265
|
+
if (utf8ByteLength(text) > MAX_EXPRESSION_BYTES)
|
|
21266
|
+
throw new PolicySyntaxError(
|
|
21267
|
+
`the expression is longer than ${String(MAX_EXPRESSION_BYTES)} bytes`,
|
|
21268
|
+
position
|
|
21269
|
+
);
|
|
21270
|
+
return text;
|
|
21271
|
+
}
|
|
21272
|
+
function parseExpression(text) {
|
|
21273
|
+
try {
|
|
21274
|
+
const stream = new TokenStream(tokenize(text));
|
|
21275
|
+
if (stream.peek().kind === "eof")
|
|
21276
|
+
throw new PolicySyntaxError("the expression is empty", 1);
|
|
21277
|
+
const expr = readCondition(stream);
|
|
21278
|
+
if (stream.peek().kind !== "eof") stream.unexpected();
|
|
21279
|
+
return { ok: true, expr, text: checkExpressionSize(expr, 1) };
|
|
21280
|
+
} catch (error51) {
|
|
21281
|
+
if (error51 instanceof PolicySyntaxError)
|
|
21282
|
+
return { ok: false, reason: error51.reason, position: error51.position };
|
|
21283
|
+
throw error51;
|
|
21284
|
+
}
|
|
21285
|
+
}
|
|
21286
|
+
function quoteIdent(name) {
|
|
21287
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !SPECIAL_WORDS.has(asciiLower(name)) ? name : `"${name.replaceAll('"', '""')}"`;
|
|
21288
|
+
}
|
|
21289
|
+
function commentText(text) {
|
|
21290
|
+
let out = "";
|
|
21291
|
+
for (const char of text) {
|
|
21292
|
+
const code = char.charCodeAt(0);
|
|
21293
|
+
const control = code < 32 || code >= 127 && code <= 159 || code === 8232 || code === 8233;
|
|
21294
|
+
out += control ? `\\u${code.toString(16).padStart(4, "0")}` : char;
|
|
21295
|
+
}
|
|
21296
|
+
return out;
|
|
21297
|
+
}
|
|
21298
|
+
function quoteString(text) {
|
|
21299
|
+
return `'${text.replaceAll("'", "''")}'`;
|
|
21300
|
+
}
|
|
21301
|
+
function printNumber(value) {
|
|
21302
|
+
if (Object.is(value, -0)) return "-0.0";
|
|
21303
|
+
const text = String(value);
|
|
21304
|
+
return Number.isSafeInteger(value) || /[.e]/.test(text) ? text : `${text}.0`;
|
|
21305
|
+
}
|
|
21306
|
+
function printLiteral(value) {
|
|
21307
|
+
if (value === null) return "NULL";
|
|
21308
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
21309
|
+
if (typeof value === "number") return printNumber(value);
|
|
21310
|
+
return quoteString(value);
|
|
21311
|
+
}
|
|
21312
|
+
function printValue(expr) {
|
|
21313
|
+
switch (expr.kind) {
|
|
21314
|
+
case "literal":
|
|
21315
|
+
return printLiteral(expr.value);
|
|
21316
|
+
case "column":
|
|
21317
|
+
return expr.qualifier === null ? quoteIdent(expr.name) : `${quoteIdent(expr.qualifier)}.${quoteIdent(expr.name)}`;
|
|
21318
|
+
case "auth":
|
|
21319
|
+
return `auth.${expr.fn}()`;
|
|
21320
|
+
case "jwt":
|
|
21321
|
+
return `(auth.jwt() ${expr.op} ${quoteString(expr.claim)})`;
|
|
21322
|
+
case "func":
|
|
21323
|
+
return expr.name === "now" ? "now()" : `${expr.name}(${expr.args.map(printValue).join(", ")})`;
|
|
21324
|
+
case "cast":
|
|
21325
|
+
return `(${printValue(expr.operand)})::text`;
|
|
21326
|
+
}
|
|
21327
|
+
}
|
|
21328
|
+
function printSubquery(subquery) {
|
|
21329
|
+
const select = subquery.select === "*" ? "*" : printValue(subquery.select);
|
|
21330
|
+
const alias = subquery.alias === null ? "" : ` ${quoteIdent(subquery.alias)}`;
|
|
21331
|
+
const where = subquery.where === null ? "" : ` WHERE ${printExpr(subquery.where)}`;
|
|
21332
|
+
return `SELECT ${select} FROM ${quoteIdent(subquery.table)}${alias}${where}`;
|
|
21333
|
+
}
|
|
21334
|
+
function printElement(element) {
|
|
21335
|
+
if (typeof element === "object") return printValue(element);
|
|
21336
|
+
return typeof element === "number" ? printNumber(element) : quoteString(element);
|
|
21337
|
+
}
|
|
21338
|
+
function printExpr(expr) {
|
|
21339
|
+
if (isValueExpr(expr)) return printValue(expr);
|
|
21340
|
+
switch (expr.kind) {
|
|
21341
|
+
case "and":
|
|
21342
|
+
case "or":
|
|
21343
|
+
return `(${expr.args.map(printExpr).join(expr.kind === "and" ? " AND " : " OR ")})`;
|
|
21344
|
+
case "not":
|
|
21345
|
+
return `(NOT ${printExpr(expr.arg)})`;
|
|
21346
|
+
case "compare":
|
|
21347
|
+
return `(${printValue(expr.left)} ${expr.op} ${printValue(expr.right)})`;
|
|
21348
|
+
case "distinct":
|
|
21349
|
+
return `(${printValue(expr.left)} IS ${expr.not ? "NOT " : ""}DISTINCT FROM ${printValue(expr.right)})`;
|
|
21350
|
+
case "is":
|
|
21351
|
+
return `(${printValue(expr.operand)} IS ${expr.not ? "NOT " : ""}${printLiteral(expr.value)})`;
|
|
21352
|
+
case "in":
|
|
21353
|
+
return `(${printValue(expr.operand)} ${expr.not ? "NOT " : ""}IN (${expr.list.map(printValue).join(", ")}))`;
|
|
21354
|
+
case "inSelect":
|
|
21355
|
+
return `(${printValue(expr.operand)} ${expr.not ? "NOT " : ""}IN (${printSubquery(expr.subquery)}))`;
|
|
21356
|
+
case "like":
|
|
21357
|
+
return `(${printValue(expr.operand)} ${expr.not ? "NOT " : ""}${expr.ci ? "ILIKE" : "LIKE"} ${quoteString(expr.pattern)})`;
|
|
21358
|
+
case "between":
|
|
21359
|
+
return `(${printValue(expr.operand)} ${expr.not ? "NOT " : ""}BETWEEN ${printValue(expr.low)} AND ${printValue(expr.high)})`;
|
|
21360
|
+
case "array":
|
|
21361
|
+
return `(${printValue(expr.operand)} ${expr.op} ARRAY[${expr.values.map(printElement).join(", ")}])`;
|
|
21362
|
+
case "exists":
|
|
21363
|
+
return `(EXISTS (${printSubquery(expr.subquery)}))`;
|
|
21364
|
+
}
|
|
21365
|
+
}
|
|
21366
|
+
function unqualifiedInSubquery(expr) {
|
|
21367
|
+
let found;
|
|
21368
|
+
function value(node, inside) {
|
|
21369
|
+
if (found !== void 0) return;
|
|
21370
|
+
switch (node.kind) {
|
|
21371
|
+
case "column":
|
|
21372
|
+
if (inside && node.qualifier === null) found = node;
|
|
21373
|
+
return;
|
|
21374
|
+
case "func":
|
|
21375
|
+
for (const arg of node.args) value(arg, inside);
|
|
21376
|
+
return;
|
|
21377
|
+
case "cast":
|
|
21378
|
+
value(node.operand, inside);
|
|
21379
|
+
return;
|
|
21380
|
+
default:
|
|
21381
|
+
return;
|
|
21382
|
+
}
|
|
21383
|
+
}
|
|
21384
|
+
function subquery(node) {
|
|
21385
|
+
if (node.select !== "*") value(node.select, true);
|
|
21386
|
+
if (node.where !== null) visit(node.where, true);
|
|
21387
|
+
}
|
|
21388
|
+
function visit(node, inside) {
|
|
21389
|
+
if (found !== void 0) return;
|
|
21390
|
+
if (isValueExpr(node)) {
|
|
21391
|
+
value(node, inside);
|
|
21392
|
+
return;
|
|
21393
|
+
}
|
|
21394
|
+
switch (node.kind) {
|
|
21395
|
+
case "and":
|
|
21396
|
+
case "or":
|
|
21397
|
+
for (const arg of node.args) visit(arg, inside);
|
|
21398
|
+
return;
|
|
21399
|
+
case "not":
|
|
21400
|
+
visit(node.arg, inside);
|
|
21401
|
+
return;
|
|
21402
|
+
case "compare":
|
|
21403
|
+
case "distinct":
|
|
21404
|
+
value(node.left, inside);
|
|
21405
|
+
value(node.right, inside);
|
|
21406
|
+
return;
|
|
21407
|
+
case "is":
|
|
21408
|
+
case "like":
|
|
21409
|
+
case "array":
|
|
21410
|
+
value(node.operand, inside);
|
|
21411
|
+
return;
|
|
21412
|
+
case "in":
|
|
21413
|
+
value(node.operand, inside);
|
|
21414
|
+
for (const item of node.list) value(item, inside);
|
|
21415
|
+
return;
|
|
21416
|
+
case "between":
|
|
21417
|
+
for (const item of [node.operand, node.low, node.high])
|
|
21418
|
+
value(item, inside);
|
|
21419
|
+
return;
|
|
21420
|
+
case "inSelect":
|
|
21421
|
+
value(node.operand, inside);
|
|
21422
|
+
subquery(node.subquery);
|
|
21423
|
+
return;
|
|
21424
|
+
case "exists":
|
|
21425
|
+
subquery(node.subquery);
|
|
21426
|
+
return;
|
|
21427
|
+
}
|
|
21428
|
+
}
|
|
21429
|
+
visit(expr, false);
|
|
21430
|
+
return found;
|
|
21431
|
+
}
|
|
21432
|
+
function unqualifiedProblem(ref) {
|
|
21433
|
+
const name = quoteIdent(ref.name);
|
|
21434
|
+
return `column ${name} has no table or alias in front of it inside a subquery: naro policy create writes one on every column there, so that a column added to a table later cannot change which table the policy reads it from \u2014 drop the policy and create it again`;
|
|
21435
|
+
}
|
|
21436
|
+
|
|
21437
|
+
// ../../packages/data-core/src/named-policy.ts
|
|
21438
|
+
var POLICY_COMMANDS = [
|
|
21439
|
+
"all",
|
|
21440
|
+
"select",
|
|
21441
|
+
"insert",
|
|
21442
|
+
"update",
|
|
21443
|
+
"delete"
|
|
21444
|
+
];
|
|
21445
|
+
var POLICY_ROLES = ["anon", "authenticated"];
|
|
21446
|
+
var MAX_POLICY_NAME_LENGTH = 63;
|
|
21447
|
+
var MAX_POLICY_SQL_BYTES = 16 * 1024;
|
|
21448
|
+
function rolesText(roles) {
|
|
21449
|
+
const set2 = new Set(roles);
|
|
21450
|
+
if (set2.has("anon") && set2.has("authenticated")) return "public";
|
|
21451
|
+
return set2.has("anon") ? "anon" : "authenticated";
|
|
21452
|
+
}
|
|
21453
|
+
function readRoles(names) {
|
|
21454
|
+
const set2 = /* @__PURE__ */ new Set();
|
|
21455
|
+
if (names.length === 0)
|
|
21456
|
+
return { ok: false, reason: "Roles are public, anon and authenticated." };
|
|
21457
|
+
for (const name of names) {
|
|
21458
|
+
if (name === "public") {
|
|
21459
|
+
set2.add("anon");
|
|
21460
|
+
set2.add("authenticated");
|
|
21461
|
+
} else if (name === "anon" || name === "authenticated") set2.add(name);
|
|
21462
|
+
else
|
|
21463
|
+
return {
|
|
21464
|
+
ok: false,
|
|
21465
|
+
reason: name === "service_role" ? "Roles are public, anon and authenticated: the service key skips every policy." : "Roles are public, anon and authenticated."
|
|
21466
|
+
};
|
|
21467
|
+
}
|
|
21468
|
+
return { ok: true, roles: POLICY_ROLES.filter((role) => set2.has(role)) };
|
|
21469
|
+
}
|
|
21470
|
+
function quotePolicyName(name) {
|
|
21471
|
+
return /^[a-z_][a-z0-9_]*$/.test(name) && quoteIdent(name) === name ? name : `"${name.replaceAll('"', '""')}"`;
|
|
21472
|
+
}
|
|
21473
|
+
function policyShapeProblem(policy) {
|
|
21474
|
+
const { name, command, using, check: check2 } = policy;
|
|
21475
|
+
if (name === "" || [...name].length > MAX_POLICY_NAME_LENGTH || name.includes("\0"))
|
|
21476
|
+
return `Policy names are 1 to ${String(MAX_POLICY_NAME_LENGTH)} characters.`;
|
|
21477
|
+
const upper = command.toUpperCase();
|
|
21478
|
+
if ((command === "select" || command === "delete") && check2 !== null)
|
|
21479
|
+
return "WITH CHECK cannot be applied to SELECT or DELETE.";
|
|
21480
|
+
if (command === "insert" && using !== null)
|
|
21481
|
+
return "Only WITH CHECK expression allowed for INSERT.";
|
|
21482
|
+
if ((command === "select" || command === "delete") && using === null)
|
|
21483
|
+
return `A ${upper} policy needs USING (\u2026).`;
|
|
21484
|
+
if (command === "insert" && check2 === null)
|
|
21485
|
+
return "An INSERT policy needs WITH CHECK (\u2026).";
|
|
21486
|
+
if (using === null && check2 === null)
|
|
21487
|
+
return `${command === "all" ? "An ALL" : "An UPDATE"} policy needs USING (\u2026), WITH CHECK (\u2026) or both.`;
|
|
21488
|
+
return void 0;
|
|
21489
|
+
}
|
|
21490
|
+
function ownerExpression(column) {
|
|
21491
|
+
return printExpr({
|
|
21492
|
+
kind: "compare",
|
|
21493
|
+
op: "=",
|
|
21494
|
+
left: { kind: "column", qualifier: null, name: column },
|
|
21495
|
+
right: { kind: "auth", fn: "uid" }
|
|
21496
|
+
});
|
|
21497
|
+
}
|
|
21498
|
+
function ownerClauses(column, command) {
|
|
21499
|
+
const text = ownerExpression(column);
|
|
21500
|
+
return command === "insert" ? { using: null, check: text } : { using: text, check: null };
|
|
21501
|
+
}
|
|
21502
|
+
var NAMED_POLICY_TABLE = "_naro_policies_v2";
|
|
21503
|
+
var NAMED_POLICY_TABLE_SQL = `CREATE TABLE IF NOT EXISTS ${NAMED_POLICY_TABLE} (table_name TEXT NOT NULL, name TEXT NOT NULL, kind TEXT NOT NULL DEFAULT 'permissive', command TEXT NOT NULL DEFAULT 'all', roles TEXT NOT NULL DEFAULT 'public', using_expr TEXT, check_expr TEXT, owner_column TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), PRIMARY KEY (table_name, name))`;
|
|
21504
|
+
var NAMED_POLICY_SQL = {
|
|
21505
|
+
list: `SELECT table_name, name, kind, command, roles, using_expr, check_expr, owner_column FROM ${NAMED_POLICY_TABLE} ORDER BY table_name, name`,
|
|
21506
|
+
insert: `INSERT INTO ${NAMED_POLICY_TABLE} (table_name, name, kind, command, roles, using_expr, check_expr, owner_column) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
21507
|
+
replace: `UPDATE ${NAMED_POLICY_TABLE} SET table_name = ?, name = ?, kind = ?, command = ?, roles = ?, using_expr = ?, check_expr = ?, owner_column = ?, updated_at = datetime('now') WHERE table_name = ? AND name = ?`,
|
|
21508
|
+
remove: `DELETE FROM ${NAMED_POLICY_TABLE} WHERE table_name = ? COLLATE NOCASE AND name = ?`,
|
|
21509
|
+
removeTable: `DELETE FROM ${NAMED_POLICY_TABLE} WHERE table_name = ? COLLATE NOCASE`
|
|
21510
|
+
};
|
|
21511
|
+
function policyValues(policy) {
|
|
21512
|
+
return [
|
|
21513
|
+
policy.table,
|
|
21514
|
+
policy.name,
|
|
21515
|
+
policy.permissive ? "permissive" : "restrictive",
|
|
21516
|
+
policy.command,
|
|
21517
|
+
rolesText(policy.roles),
|
|
21518
|
+
policy.using,
|
|
21519
|
+
policy.check,
|
|
21520
|
+
policy.owner
|
|
21521
|
+
];
|
|
21522
|
+
}
|
|
21523
|
+
function storedExpression(value, field) {
|
|
21524
|
+
if (value === null || value === void 0) return { expr: null, text: null };
|
|
21525
|
+
if (typeof value !== "string")
|
|
21526
|
+
return { expr: null, text: null, problem: `${field} is not text` };
|
|
21527
|
+
const parsed = parseExpression(value);
|
|
21528
|
+
if (!parsed.ok)
|
|
21529
|
+
return {
|
|
21530
|
+
expr: null,
|
|
21531
|
+
text: value,
|
|
21532
|
+
problem: `${field}: ${parsed.reason} at character ${String(parsed.position)}`
|
|
21533
|
+
};
|
|
21534
|
+
const unbound = unqualifiedInSubquery(parsed.expr);
|
|
21535
|
+
if (unbound !== void 0)
|
|
21536
|
+
return {
|
|
21537
|
+
expr: null,
|
|
21538
|
+
text: value,
|
|
21539
|
+
problem: `${field}: ${unqualifiedProblem(unbound)}`
|
|
21540
|
+
};
|
|
21541
|
+
return { expr: parsed.expr, text: parsed.text };
|
|
21542
|
+
}
|
|
21543
|
+
function loadPolicy(row, log2) {
|
|
21544
|
+
const table = row.table_name;
|
|
21545
|
+
if (typeof table !== "string" || table === "") return void 0;
|
|
21546
|
+
const problems = [];
|
|
21547
|
+
const name = typeof row.name === "string" && row.name !== "" ? row.name : "(unnamed)";
|
|
21548
|
+
if (name === "(unnamed)") problems.push("the name is missing");
|
|
21549
|
+
const kind = row.kind;
|
|
21550
|
+
if (kind !== "permissive" && kind !== "restrictive")
|
|
21551
|
+
problems.push("kind is not permissive or restrictive");
|
|
21552
|
+
const command = POLICY_COMMANDS.includes(row.command) ? row.command : void 0;
|
|
21553
|
+
if (!command)
|
|
21554
|
+
problems.push("command is not one of all, select, insert, update, delete");
|
|
21555
|
+
const roles = typeof row.roles === "string" && ["public", "anon", "authenticated"].includes(row.roles) ? readRoles([row.roles]) : void 0;
|
|
21556
|
+
if (!roles?.ok) problems.push("roles is not public, anon or authenticated");
|
|
21557
|
+
const using = storedExpression(row.using_expr, "using");
|
|
21558
|
+
const check2 = storedExpression(row.check_expr, "check");
|
|
21559
|
+
if (using.problem) problems.push(using.problem);
|
|
21560
|
+
if (check2.problem) problems.push(check2.problem);
|
|
21561
|
+
const owner = row.owner_column;
|
|
21562
|
+
const policy = {
|
|
21563
|
+
table,
|
|
21564
|
+
name,
|
|
21565
|
+
permissive: kind !== "restrictive",
|
|
21566
|
+
// What cannot be read applies everywhere: it refuses, never allows.
|
|
21567
|
+
command: command ?? "all",
|
|
21568
|
+
roles: roles?.ok ? roles.roles : [...POLICY_ROLES],
|
|
21569
|
+
using: using.text,
|
|
21570
|
+
check: check2.text,
|
|
21571
|
+
owner: typeof owner === "string" ? owner : null,
|
|
21572
|
+
invalid: null,
|
|
21573
|
+
usingExpr: using.expr,
|
|
21574
|
+
checkExpr: check2.expr
|
|
21575
|
+
};
|
|
21576
|
+
if (command && problems.length === 0) {
|
|
21577
|
+
const shape = policyShapeProblem(policy);
|
|
21578
|
+
if (shape) problems.push(shape);
|
|
21579
|
+
}
|
|
21580
|
+
if (policy.owner !== null && policy.owner !== "" && command && problems.length === 0) {
|
|
21581
|
+
const expected = ownerClauses(policy.owner, command);
|
|
21582
|
+
if (policy.using !== expected.using || policy.check !== expected.check)
|
|
21583
|
+
problems.push(
|
|
21584
|
+
"an owner policy's expressions are not its owner condition"
|
|
21585
|
+
);
|
|
21586
|
+
}
|
|
21587
|
+
if (problems.length > 0) {
|
|
21588
|
+
policy.invalid = problems.join("; ");
|
|
21589
|
+
log2?.(
|
|
21590
|
+
`naro data-core: policy ${name} on ${table} cannot be read: ${policy.invalid}`
|
|
21591
|
+
);
|
|
21592
|
+
}
|
|
21593
|
+
return policy;
|
|
21594
|
+
}
|
|
21595
|
+
function foldPolicyName(name, quoted) {
|
|
21596
|
+
return quoted ? name : asciiLower(name);
|
|
21597
|
+
}
|
|
21598
|
+
function policySqlTooLong(text) {
|
|
21599
|
+
return utf8ByteLength(text) > MAX_POLICY_SQL_BYTES;
|
|
21600
|
+
}
|
|
19913
21601
|
|
|
19914
21602
|
// ../../packages/data-core/src/query.ts
|
|
19915
21603
|
var FILTER_OPERATORS = [
|
|
@@ -19941,7 +21629,11 @@ var ARRAY_OPERATORS = [
|
|
|
19941
21629
|
"containedBy",
|
|
19942
21630
|
"overlaps"
|
|
19943
21631
|
];
|
|
19944
|
-
|
|
21632
|
+
function patternProblem(operator, pattern) {
|
|
21633
|
+
if (!patternTooLong(pattern, operator === "ilike")) return void 0;
|
|
21634
|
+
const limit = `must be at most ${String(MAX_PATTERN_BYTES)} bytes of UTF-8, D1's limit for a pattern`;
|
|
21635
|
+
return operator === "ilike" ? limit : `${limit} (like counts each *, ? and [ as 3 bytes)`;
|
|
21636
|
+
}
|
|
19945
21637
|
var MAX_IDENTIFIER_LENGTH = 128;
|
|
19946
21638
|
var Invalid = class extends Error {
|
|
19947
21639
|
constructor(path, reason) {
|
|
@@ -19956,7 +21648,7 @@ function invalid(path, reason) {
|
|
|
19956
21648
|
function own(object2, key) {
|
|
19957
21649
|
return Object.hasOwn(object2, key) ? object2[key] : void 0;
|
|
19958
21650
|
}
|
|
19959
|
-
function
|
|
21651
|
+
function isOperator2(value) {
|
|
19960
21652
|
return FILTER_OPERATORS.includes(value);
|
|
19961
21653
|
}
|
|
19962
21654
|
function readObject(value, path) {
|
|
@@ -19981,7 +21673,7 @@ function readName(value, path) {
|
|
|
19981
21673
|
);
|
|
19982
21674
|
return value;
|
|
19983
21675
|
}
|
|
19984
|
-
function
|
|
21676
|
+
function readValue2(value, path, auth = false) {
|
|
19985
21677
|
if (auth && isAuthUid(value)) return { auth: "uid" };
|
|
19986
21678
|
if (!isValue(value))
|
|
19987
21679
|
invalid(
|
|
@@ -19993,8 +21685,8 @@ function readValue(value, path, auth = false) {
|
|
|
19993
21685
|
function longerThan(text, max) {
|
|
19994
21686
|
if (text.length <= max) return false;
|
|
19995
21687
|
if (text.length > max * 2) return true;
|
|
19996
|
-
let
|
|
19997
|
-
for (const _ of text) if (++
|
|
21688
|
+
let count2 = 0;
|
|
21689
|
+
for (const _ of text) if (++count2 > max) return true;
|
|
19998
21690
|
return false;
|
|
19999
21691
|
}
|
|
20000
21692
|
function endsWithLoneEscape(pattern) {
|
|
@@ -20009,7 +21701,7 @@ function readFilter(value, path, auth) {
|
|
|
20009
21701
|
const object2 = readObject(value, path);
|
|
20010
21702
|
checkKeys(object2, ["operator", "column", "value"], path);
|
|
20011
21703
|
const operator = own(object2, "operator");
|
|
20012
|
-
if (!
|
|
21704
|
+
if (!isOperator2(operator))
|
|
20013
21705
|
invalid(
|
|
20014
21706
|
`${path}.operator`,
|
|
20015
21707
|
`must be one of ${FILTER_OPERATORS.join(", ")}`
|
|
@@ -20022,7 +21714,7 @@ function readFilter(value, path, auth) {
|
|
|
20022
21714
|
case "in": {
|
|
20023
21715
|
if (!Array.isArray(raw)) invalid(valuePath, "must be an array");
|
|
20024
21716
|
const values = raw.map(
|
|
20025
|
-
(item, index) =>
|
|
21717
|
+
(item, index) => readValue2(item, `${valuePath}[${index}]`, auth)
|
|
20026
21718
|
);
|
|
20027
21719
|
return { operator, column, value: values };
|
|
20028
21720
|
}
|
|
@@ -20031,18 +21723,16 @@ function readFilter(value, path, auth) {
|
|
|
20031
21723
|
invalid(valuePath, "must be null, true or false");
|
|
20032
21724
|
return { operator, column, value: raw };
|
|
20033
21725
|
case "like":
|
|
20034
|
-
case "ilike":
|
|
21726
|
+
case "ilike": {
|
|
20035
21727
|
if (typeof raw !== "string") invalid(valuePath, "must be a string");
|
|
20036
|
-
|
|
20037
|
-
|
|
20038
|
-
valuePath,
|
|
20039
|
-
`must be at most ${String(MAX_PATTERN_LENGTH)} characters`
|
|
20040
|
-
);
|
|
21728
|
+
const tooLong = patternProblem(operator, raw);
|
|
21729
|
+
if (tooLong !== void 0) invalid(valuePath, tooLong);
|
|
20041
21730
|
if (raw.includes("\0"))
|
|
20042
21731
|
invalid(valuePath, "must not contain a NUL character");
|
|
20043
21732
|
if (endsWithLoneEscape(raw))
|
|
20044
21733
|
invalid(valuePath, "must not end with an unescaped backslash");
|
|
20045
21734
|
return { operator, column, value: raw };
|
|
21735
|
+
}
|
|
20046
21736
|
case "contains":
|
|
20047
21737
|
case "containedBy":
|
|
20048
21738
|
case "overlaps":
|
|
@@ -20061,7 +21751,7 @@ function readFilter(value, path, auth) {
|
|
|
20061
21751
|
)
|
|
20062
21752
|
};
|
|
20063
21753
|
default:
|
|
20064
|
-
return { operator, column, value:
|
|
21754
|
+
return { operator, column, value: readValue2(raw, valuePath, auth) };
|
|
20065
21755
|
}
|
|
20066
21756
|
}
|
|
20067
21757
|
var NODE_KEYS = ["operator", "or", "and", "not"];
|
|
@@ -20107,18 +21797,579 @@ function parsePolicyNode(value, path) {
|
|
|
20107
21797
|
}
|
|
20108
21798
|
}
|
|
20109
21799
|
|
|
20110
|
-
// ../../packages/data-core/src/schema.ts
|
|
20111
|
-
var SCHEMA_SQL = [
|
|
20112
|
-
`SELECT t.name AS table_name, t.type AS table_type, c.cid AS cid, c.name AS column_name, c.type AS column_type, c."notnull" AS not_null, c.dflt_value AS default_value, c.pk AS pk, c.hidden AS hidden FROM pragma_table_list AS t JOIN pragma_table_xinfo(t.name, t.schema) AS c WHERE t.schema = 'main' AND t.type IN ('table', 'virtual') AND ${systemTablePredicate("t.name")} ORDER BY t.name, c.cid`,
|
|
20113
|
-
// Phase 2-2 (적대적 리뷰 반영): each table's CREATE TABLE text, where
|
|
20114
|
-
// alone a column's collation is written. A separate statement, so the
|
|
20115
|
-
// text comes once per table rather than once per column. D1 answers
|
|
20116
|
-
// `sqlite_master` reads (the database routes' `_tables.ts` reads it too).
|
|
20117
|
-
`SELECT name AS table_name, sql AS table_sql FROM sqlite_master WHERE type = 'table' AND ${systemTablePredicate("name")}`
|
|
20118
|
-
];
|
|
20119
|
-
|
|
20120
21800
|
// ../../packages/data-core/src/condition.ts
|
|
21801
|
+
var CONDITION_RULE = "condition";
|
|
20121
21802
|
var MAX_CONDITION_BYTES = 16 * 1024;
|
|
21803
|
+
var TREES = {
|
|
21804
|
+
select: ["using"],
|
|
21805
|
+
delete: ["using"],
|
|
21806
|
+
insert: ["check"],
|
|
21807
|
+
update: ["using", "check"]
|
|
21808
|
+
};
|
|
21809
|
+
function parseCondition(action, value) {
|
|
21810
|
+
if (!isPlainObject2(value))
|
|
21811
|
+
return { ok: false, path: "", reason: "must be an object" };
|
|
21812
|
+
const trees = TREES[action];
|
|
21813
|
+
for (const key of Object.keys(value))
|
|
21814
|
+
if (key !== "role" && !trees.includes(key))
|
|
21815
|
+
return key.length > MAX_IDENTIFIER_LENGTH ? {
|
|
21816
|
+
ok: false,
|
|
21817
|
+
path: "",
|
|
21818
|
+
reason: "has a field that is not a known field"
|
|
21819
|
+
} : { ok: false, path: key, reason: "is not a known field" };
|
|
21820
|
+
const condition = {};
|
|
21821
|
+
if (Object.hasOwn(value, "role")) {
|
|
21822
|
+
const role = value.role;
|
|
21823
|
+
if (role !== "any" && role !== "authenticated")
|
|
21824
|
+
return {
|
|
21825
|
+
ok: false,
|
|
21826
|
+
path: "role",
|
|
21827
|
+
reason: 'must be "any" or "authenticated"'
|
|
21828
|
+
};
|
|
21829
|
+
if (role === "authenticated") condition.role = role;
|
|
21830
|
+
}
|
|
21831
|
+
const [required2] = trees;
|
|
21832
|
+
for (const key of trees) {
|
|
21833
|
+
if (!Object.hasOwn(value, key)) {
|
|
21834
|
+
if (key === required2)
|
|
21835
|
+
return { ok: false, path: key, reason: "is required" };
|
|
21836
|
+
continue;
|
|
21837
|
+
}
|
|
21838
|
+
const parsed = parsePolicyNode(value[key], key);
|
|
21839
|
+
if (!parsed.ok) return parsed;
|
|
21840
|
+
condition[key] = parsed.node;
|
|
21841
|
+
}
|
|
21842
|
+
return { ok: true, condition };
|
|
21843
|
+
}
|
|
21844
|
+
function conditionsTooLarge(texts) {
|
|
21845
|
+
let total = 0;
|
|
21846
|
+
for (const text of texts)
|
|
21847
|
+
if (typeof text === "string") total += utf8ByteLength(text);
|
|
21848
|
+
return total > MAX_CONDITION_BYTES;
|
|
21849
|
+
}
|
|
21850
|
+
function isInvalidCondition(value) {
|
|
21851
|
+
return isPlainObject2(value) && typeof value.invalid === "string";
|
|
21852
|
+
}
|
|
21853
|
+
function checkOf(action, condition) {
|
|
21854
|
+
if (action === "insert") return condition.check;
|
|
21855
|
+
if (action === "update") return condition.check ?? condition.using;
|
|
21856
|
+
return void 0;
|
|
21857
|
+
}
|
|
21858
|
+
var MAX_CONDITION_VALUES = 50;
|
|
21859
|
+
var ARRAY_OPERATORS2 = ["contains", "containedBy", "overlaps"];
|
|
21860
|
+
function treeValues(node, rebinds) {
|
|
21861
|
+
if ("not" in node) return treeValues(node.not, rebinds);
|
|
21862
|
+
if ("or" in node || "and" in node)
|
|
21863
|
+
return ("or" in node ? node.or : node.and).reduce(
|
|
21864
|
+
(sum, child) => sum + treeValues(child, rebinds),
|
|
21865
|
+
0
|
|
21866
|
+
);
|
|
21867
|
+
const value = node.value;
|
|
21868
|
+
const own2 = Array.isArray(value) ? ARRAY_OPERATORS2.includes(node.operator) ? new Set(value.map((item) => JSON.stringify(item))).size : value.length : node.operator === "is" && value !== null ? 0 : 1;
|
|
21869
|
+
if (!rebinds || Array.isArray(value) && value.length === 0) return own2;
|
|
21870
|
+
return own2 + (ARRAY_OPERATORS2.includes(node.operator) ? 5 : 1);
|
|
21871
|
+
}
|
|
21872
|
+
function conditionValueCount(action, condition) {
|
|
21873
|
+
let count2 = 0;
|
|
21874
|
+
if (action !== "insert" && condition.using)
|
|
21875
|
+
count2 += treeValues(condition.using, false);
|
|
21876
|
+
const check2 = checkOf(action, condition);
|
|
21877
|
+
if (check2) count2 += treeValues(check2, action === "update");
|
|
21878
|
+
return count2;
|
|
21879
|
+
}
|
|
21880
|
+
|
|
21881
|
+
// ../../packages/data-core/src/legacy-policy.ts
|
|
21882
|
+
var RULES = ["none", "public", "authenticated", "owner"];
|
|
21883
|
+
var POLICY_ACTIONS2 = ["select", "insert", "update", "delete"];
|
|
21884
|
+
function isRule(value) {
|
|
21885
|
+
return RULES.includes(value);
|
|
21886
|
+
}
|
|
21887
|
+
function sugarName(action) {
|
|
21888
|
+
return `naro_${action}`;
|
|
21889
|
+
}
|
|
21890
|
+
var CONVERTED_RULE = "rls";
|
|
21891
|
+
var ROW_AS_READ = "table_name = ? AND select_rule IS ? AND insert_rule IS ? AND update_rule IS ? AND delete_rule IS ? AND owner_column IS ? AND select_condition IS ? AND insert_condition IS ? AND update_condition IS ? AND delete_condition IS ? AND updated_at IS ?";
|
|
21892
|
+
var LEGACY_ROW_COLUMNS = [
|
|
21893
|
+
"table_name",
|
|
21894
|
+
"select_rule",
|
|
21895
|
+
"insert_rule",
|
|
21896
|
+
"update_rule",
|
|
21897
|
+
"delete_rule",
|
|
21898
|
+
"owner_column",
|
|
21899
|
+
"select_condition",
|
|
21900
|
+
"insert_condition",
|
|
21901
|
+
"update_condition",
|
|
21902
|
+
"delete_condition",
|
|
21903
|
+
"updated_at"
|
|
21904
|
+
];
|
|
21905
|
+
function legacyRowAsRead(row) {
|
|
21906
|
+
const values = [];
|
|
21907
|
+
for (const column of LEGACY_ROW_COLUMNS) {
|
|
21908
|
+
const value = row[column];
|
|
21909
|
+
if (value === null || value === void 0) values.push(null);
|
|
21910
|
+
else if (typeof value === "string") values.push(value);
|
|
21911
|
+
else return void 0;
|
|
21912
|
+
}
|
|
21913
|
+
return values;
|
|
21914
|
+
}
|
|
21915
|
+
var POLICY_SQL = {
|
|
21916
|
+
list: "SELECT * FROM _naro_policies",
|
|
21917
|
+
upsert: "INSERT INTO _naro_policies (table_name, select_rule, insert_rule, update_rule, delete_rule, owner_column, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now')) ON CONFLICT(table_name) DO UPDATE SET select_rule = excluded.select_rule, insert_rule = excluded.insert_rule, update_rule = excluded.update_rule, delete_rule = excluded.delete_rule, owner_column = excluded.owner_column, updated_at = excluded.updated_at",
|
|
21918
|
+
upsertWithConditions: "INSERT INTO _naro_policies (table_name, select_rule, insert_rule, update_rule, delete_rule, owner_column, select_condition, insert_condition, update_condition, delete_condition, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now')) ON CONFLICT(table_name) DO UPDATE SET select_rule = excluded.select_rule, insert_rule = excluded.insert_rule, update_rule = excluded.update_rule, delete_rule = excluded.delete_rule, owner_column = excluded.owner_column, select_condition = excluded.select_condition, insert_condition = excluded.insert_condition, update_condition = excluded.update_condition, delete_condition = excluded.delete_condition, updated_at = excluded.updated_at",
|
|
21919
|
+
remove: "DELETE FROM _naro_policies WHERE table_name = ?",
|
|
21920
|
+
/**
|
|
21921
|
+
* Migration 4: the rules of a converted row, if it is still as read —
|
|
21922
|
+
* binds `legacyRowAsRead(row)`.
|
|
21923
|
+
*/
|
|
21924
|
+
markConverted: `UPDATE _naro_policies SET select_rule = '${CONVERTED_RULE}', insert_rule = '${CONVERTED_RULE}', update_rule = '${CONVERTED_RULE}', delete_rule = '${CONVERTED_RULE}' WHERE ${ROW_AS_READ}`,
|
|
21925
|
+
/**
|
|
21926
|
+
* Migration 4: one named policy a row converts to, written only if the
|
|
21927
|
+
* row is still as read — the same gate as `markConverted`, so a row the
|
|
21928
|
+
* old control plane changed in between is neither converted nor marked.
|
|
21929
|
+
* Binds `policyValues` and then `legacyRowAsRead(row)`.
|
|
21930
|
+
*/
|
|
21931
|
+
insertConverted: `INSERT OR IGNORE INTO _naro_policies_v2 (table_name, name, kind, command, roles, using_expr, check_expr, owner_column) SELECT ?, ?, ?, ?, ?, ?, ?, ? WHERE EXISTS (SELECT 1 FROM _naro_policies WHERE ${ROW_AS_READ})`
|
|
21932
|
+
};
|
|
21933
|
+
function readStoredCondition(action, text, tooLarge) {
|
|
21934
|
+
if (text === void 0 || text === null) return "is missing";
|
|
21935
|
+
if (typeof text !== "string") return "is not JSON";
|
|
21936
|
+
if (tooLarge)
|
|
21937
|
+
return `the conditions of the policy are larger than ${String(MAX_CONDITION_BYTES)} bytes`;
|
|
21938
|
+
let value;
|
|
21939
|
+
try {
|
|
21940
|
+
value = JSON.parse(text);
|
|
21941
|
+
} catch {
|
|
21942
|
+
return "is not JSON";
|
|
21943
|
+
}
|
|
21944
|
+
const parsed = parseCondition(action, value);
|
|
21945
|
+
if (parsed.ok) return parsed.condition;
|
|
21946
|
+
return parsed.path === "" ? parsed.reason : `${parsed.path} ${parsed.reason}`;
|
|
21947
|
+
}
|
|
21948
|
+
function policyFromRow(row, log2) {
|
|
21949
|
+
const table = row.table_name;
|
|
21950
|
+
if (typeof table !== "string" || table === "") return void 0;
|
|
21951
|
+
const texts = POLICY_ACTIONS2.map(
|
|
21952
|
+
(action) => row[`${action}_rule`] === CONDITION_RULE ? row[`${action}_condition`] : void 0
|
|
21953
|
+
);
|
|
21954
|
+
const tooLarge = conditionsTooLarge(
|
|
21955
|
+
texts.filter((text) => typeof text === "string")
|
|
21956
|
+
);
|
|
21957
|
+
const read = POLICY_ACTIONS2.map(
|
|
21958
|
+
(action, index) => {
|
|
21959
|
+
const value = row[`${action}_rule`];
|
|
21960
|
+
if (value !== CONDITION_RULE) return isRule(value) ? value : "none";
|
|
21961
|
+
return readStoredCondition(action, texts[index], tooLarge);
|
|
21962
|
+
}
|
|
21963
|
+
);
|
|
21964
|
+
const values = POLICY_ACTIONS2.reduce((sum, action, index) => {
|
|
21965
|
+
const item = read[index];
|
|
21966
|
+
return typeof item === "object" ? sum + conditionValueCount(action, item) : sum;
|
|
21967
|
+
}, 0);
|
|
21968
|
+
const rule = (action, index) => {
|
|
21969
|
+
let item = read[index] ?? "none";
|
|
21970
|
+
if (typeof item === "object" && values > MAX_CONDITION_VALUES)
|
|
21971
|
+
item = `the conditions of the policy use more than ${String(MAX_CONDITION_VALUES)} values`;
|
|
21972
|
+
if (typeof item === "object" || isRule(item)) return item;
|
|
21973
|
+
log2?.(
|
|
21974
|
+
`naro data-core: the ${action} condition of ${table} cannot be read: ${item}`
|
|
21975
|
+
);
|
|
21976
|
+
return { invalid: item };
|
|
21977
|
+
};
|
|
21978
|
+
const owner = row.owner_column;
|
|
21979
|
+
return {
|
|
21980
|
+
table,
|
|
21981
|
+
select: rule("select", 0),
|
|
21982
|
+
insert: rule("insert", 1),
|
|
21983
|
+
update: rule("update", 2),
|
|
21984
|
+
delete: rule("delete", 3),
|
|
21985
|
+
ownerColumn: typeof owner === "string" && owner !== "" ? owner : null
|
|
21986
|
+
};
|
|
21987
|
+
}
|
|
21988
|
+
function findPolicy(policies, table) {
|
|
21989
|
+
const exact = policies.find((policy) => policy.table === table);
|
|
21990
|
+
if (exact) return exact;
|
|
21991
|
+
const folded = asciiLower(table);
|
|
21992
|
+
const matches = policies.filter(
|
|
21993
|
+
(policy) => asciiLower(policy.table) === folded
|
|
21994
|
+
);
|
|
21995
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
21996
|
+
}
|
|
21997
|
+
function legacyInForce(policies, tables) {
|
|
21998
|
+
const picked = /* @__PURE__ */ new Set();
|
|
21999
|
+
for (const table of tables) {
|
|
22000
|
+
const policy = findPolicy(policies, table);
|
|
22001
|
+
if (policy) picked.add(policy);
|
|
22002
|
+
}
|
|
22003
|
+
return policies.filter((policy) => picked.has(policy));
|
|
22004
|
+
}
|
|
22005
|
+
function valueExpr(value) {
|
|
22006
|
+
if (typeof value === "object" && value !== null)
|
|
22007
|
+
return { kind: "auth", fn: "uid" };
|
|
22008
|
+
return { kind: "literal", value };
|
|
22009
|
+
}
|
|
22010
|
+
var COMPARISONS = {
|
|
22011
|
+
eq: "=",
|
|
22012
|
+
neq: "<>",
|
|
22013
|
+
gt: ">",
|
|
22014
|
+
gte: ">=",
|
|
22015
|
+
lt: "<",
|
|
22016
|
+
lte: "<="
|
|
22017
|
+
};
|
|
22018
|
+
var ARRAYS = { contains: "@>", containedBy: "<@", overlaps: "&&" };
|
|
22019
|
+
var UID = { kind: "auth", fn: "uid" };
|
|
22020
|
+
function uidIsNull(not) {
|
|
22021
|
+
return { kind: "is", not, operand: UID, value: null };
|
|
22022
|
+
}
|
|
22023
|
+
function anonDiffers(expr) {
|
|
22024
|
+
if (expr.kind === "array")
|
|
22025
|
+
return expr.values.some((value) => typeof value === "object");
|
|
22026
|
+
if (expr.kind !== "in") return false;
|
|
22027
|
+
const uids = expr.list.filter((item) => item.kind === "auth").length;
|
|
22028
|
+
return uids > 0 && uids < expr.list.length;
|
|
22029
|
+
}
|
|
22030
|
+
function filterNodeToExpr(node, negated = false) {
|
|
22031
|
+
if ("not" in node)
|
|
22032
|
+
return { kind: "not", arg: filterNodeToExpr(node.not, !negated) };
|
|
22033
|
+
if ("or" in node || "and" in node) {
|
|
22034
|
+
const children = ("or" in node ? node.or : node.and).map(
|
|
22035
|
+
(child) => filterNodeToExpr(child, negated)
|
|
22036
|
+
);
|
|
22037
|
+
if (children.length === 1) return children[0];
|
|
22038
|
+
return { kind: "or" in node ? "or" : "and", args: children };
|
|
22039
|
+
}
|
|
22040
|
+
const leaf = leafToExpr(node);
|
|
22041
|
+
if (!anonDiffers(leaf)) return leaf;
|
|
22042
|
+
return negated ? { kind: "or", args: [leaf, uidIsNull(false)] } : { kind: "and", args: [leaf, uidIsNull(true)] };
|
|
22043
|
+
}
|
|
22044
|
+
function leafToExpr(node) {
|
|
22045
|
+
const column = {
|
|
22046
|
+
kind: "column",
|
|
22047
|
+
qualifier: null,
|
|
22048
|
+
name: node.column
|
|
22049
|
+
};
|
|
22050
|
+
const value = node.value;
|
|
22051
|
+
switch (node.operator) {
|
|
22052
|
+
case "eq":
|
|
22053
|
+
case "neq":
|
|
22054
|
+
case "gt":
|
|
22055
|
+
case "gte":
|
|
22056
|
+
case "lt":
|
|
22057
|
+
case "lte":
|
|
22058
|
+
return {
|
|
22059
|
+
kind: "compare",
|
|
22060
|
+
op: COMPARISONS[node.operator],
|
|
22061
|
+
left: column,
|
|
22062
|
+
right: valueExpr(Array.isArray(value) ? null : value)
|
|
22063
|
+
};
|
|
22064
|
+
case "in": {
|
|
22065
|
+
const list = Array.isArray(value) ? value : [value];
|
|
22066
|
+
if (list.length === 0) return { kind: "literal", value: false };
|
|
22067
|
+
return {
|
|
22068
|
+
kind: "in",
|
|
22069
|
+
not: false,
|
|
22070
|
+
operand: column,
|
|
22071
|
+
list: list.map(valueExpr)
|
|
22072
|
+
};
|
|
22073
|
+
}
|
|
22074
|
+
case "is":
|
|
22075
|
+
return {
|
|
22076
|
+
kind: "is",
|
|
22077
|
+
not: false,
|
|
22078
|
+
operand: column,
|
|
22079
|
+
value: value === true || value === false ? value : null
|
|
22080
|
+
};
|
|
22081
|
+
case "like":
|
|
22082
|
+
case "ilike":
|
|
22083
|
+
return {
|
|
22084
|
+
kind: "like",
|
|
22085
|
+
not: false,
|
|
22086
|
+
ci: node.operator === "ilike",
|
|
22087
|
+
operand: column,
|
|
22088
|
+
pattern: typeof value === "string" ? value : ""
|
|
22089
|
+
};
|
|
22090
|
+
case "contains":
|
|
22091
|
+
case "containedBy":
|
|
22092
|
+
case "overlaps":
|
|
22093
|
+
return {
|
|
22094
|
+
kind: "array",
|
|
22095
|
+
op: ARRAYS[node.operator],
|
|
22096
|
+
operand: { kind: "column", qualifier: null, name: node.column },
|
|
22097
|
+
values: (Array.isArray(value) ? value : []).map(
|
|
22098
|
+
(item) => typeof item === "object" && item !== null ? { kind: "auth", fn: "uid" } : typeof item === "number" ? item : String(item)
|
|
22099
|
+
)
|
|
22100
|
+
};
|
|
22101
|
+
}
|
|
22102
|
+
}
|
|
22103
|
+
var TRUE_TEXT = printExpr({ kind: "literal", value: true });
|
|
22104
|
+
function treeText(node) {
|
|
22105
|
+
return node === void 0 ? null : printExpr(filterNodeToExpr(node));
|
|
22106
|
+
}
|
|
22107
|
+
function namedFromRule(table, action, rule, ownerColumn) {
|
|
22108
|
+
const base2 = {
|
|
22109
|
+
table,
|
|
22110
|
+
name: sugarName(action),
|
|
22111
|
+
permissive: true,
|
|
22112
|
+
command: action,
|
|
22113
|
+
owner: null
|
|
22114
|
+
};
|
|
22115
|
+
const everyone = [...POLICY_ROLES];
|
|
22116
|
+
if (rule === "none" || typeof rule === "string" && !isRule(rule))
|
|
22117
|
+
return null;
|
|
22118
|
+
if (rule === "public" || rule === "authenticated")
|
|
22119
|
+
return {
|
|
22120
|
+
...base2,
|
|
22121
|
+
roles: rule === "public" ? everyone : ["authenticated"],
|
|
22122
|
+
using: action === "insert" ? null : TRUE_TEXT,
|
|
22123
|
+
check: action === "insert" ? TRUE_TEXT : null
|
|
22124
|
+
};
|
|
22125
|
+
if (rule === "owner") {
|
|
22126
|
+
const column = ownerColumn ?? "";
|
|
22127
|
+
return {
|
|
22128
|
+
...base2,
|
|
22129
|
+
roles: ["authenticated"],
|
|
22130
|
+
...column === "" ? { using: null, check: null } : ownerClauses(column, action),
|
|
22131
|
+
owner: column
|
|
22132
|
+
};
|
|
22133
|
+
}
|
|
22134
|
+
if (isInvalidCondition(rule) || (action === "insert" ? rule.check : rule.using) === void 0)
|
|
22135
|
+
return { ...base2, roles: everyone, using: null, check: null };
|
|
22136
|
+
return {
|
|
22137
|
+
...base2,
|
|
22138
|
+
roles: rule.role === "authenticated" ? ["authenticated"] : everyone,
|
|
22139
|
+
using: action === "insert" ? null : treeText(rule.using),
|
|
22140
|
+
check: action === "insert" || action === "update" ? treeText(rule.check) : null
|
|
22141
|
+
};
|
|
22142
|
+
}
|
|
22143
|
+
function namedFromLegacy(policy) {
|
|
22144
|
+
return POLICY_ACTIONS2.flatMap((action) => {
|
|
22145
|
+
const named = namedFromRule(
|
|
22146
|
+
policy.table,
|
|
22147
|
+
action,
|
|
22148
|
+
policy[action],
|
|
22149
|
+
policy.ownerColumn
|
|
22150
|
+
);
|
|
22151
|
+
return named ? [named] : [];
|
|
22152
|
+
});
|
|
22153
|
+
}
|
|
22154
|
+
function loadNamed(policies, log2) {
|
|
22155
|
+
const columns = [
|
|
22156
|
+
"table_name",
|
|
22157
|
+
"name",
|
|
22158
|
+
"kind",
|
|
22159
|
+
"command",
|
|
22160
|
+
"roles",
|
|
22161
|
+
"using_expr",
|
|
22162
|
+
"check_expr",
|
|
22163
|
+
"owner_column"
|
|
22164
|
+
];
|
|
22165
|
+
return policies.flatMap((policy) => {
|
|
22166
|
+
const values = policyValues(policy);
|
|
22167
|
+
const loaded = loadPolicy(
|
|
22168
|
+
Object.fromEntries(
|
|
22169
|
+
columns.map((column, index) => [column, values[index]])
|
|
22170
|
+
),
|
|
22171
|
+
log2
|
|
22172
|
+
);
|
|
22173
|
+
return loaded ? [loaded] : [];
|
|
22174
|
+
});
|
|
22175
|
+
}
|
|
22176
|
+
function unreadableConversions(policy) {
|
|
22177
|
+
const found = [];
|
|
22178
|
+
for (const action of POLICY_ACTIONS2) {
|
|
22179
|
+
const rule = policy[action];
|
|
22180
|
+
const named = namedFromRule(policy.table, action, rule, policy.ownerColumn);
|
|
22181
|
+
if (!named) continue;
|
|
22182
|
+
const [loaded] = loadNamed([named]);
|
|
22183
|
+
if (!loaded || loaded.invalid === null) continue;
|
|
22184
|
+
found.push({
|
|
22185
|
+
table: named.table,
|
|
22186
|
+
policy: named.name,
|
|
22187
|
+
reason: isInvalidCondition(rule) ? `the old ${action} condition could not be read: ${rule.invalid}` : rule === "owner" && named.owner === "" ? `the old ${action} rule is owner, and the row has no owner column` : loaded.invalid
|
|
22188
|
+
});
|
|
22189
|
+
}
|
|
22190
|
+
return found;
|
|
22191
|
+
}
|
|
22192
|
+
|
|
22193
|
+
// ../../packages/data-core/src/policy-index.ts
|
|
22194
|
+
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`;
|
|
22195
|
+
|
|
22196
|
+
// ../../packages/data-core/src/policy-statement.ts
|
|
22197
|
+
function readPolicyName(stream) {
|
|
22198
|
+
const token = stream.peek();
|
|
22199
|
+
if (token.kind !== "ident")
|
|
22200
|
+
throw new PolicySyntaxError(
|
|
22201
|
+
`expected a policy name, found ${describeToken(token)}`,
|
|
22202
|
+
token.position
|
|
22203
|
+
);
|
|
22204
|
+
stream.next();
|
|
22205
|
+
return foldPolicyName(token.value, token.quoted);
|
|
22206
|
+
}
|
|
22207
|
+
function readRoleName(token) {
|
|
22208
|
+
if (token.kind !== "ident")
|
|
22209
|
+
throw new PolicySyntaxError(
|
|
22210
|
+
`expected a role, found ${describeToken(token)}`,
|
|
22211
|
+
token.position
|
|
22212
|
+
);
|
|
22213
|
+
return token.quoted ? token.value : asciiLower(token.value);
|
|
22214
|
+
}
|
|
22215
|
+
function readCommand(stream) {
|
|
22216
|
+
const token = stream.next();
|
|
22217
|
+
for (const command of [
|
|
22218
|
+
"all",
|
|
22219
|
+
"select",
|
|
22220
|
+
"insert",
|
|
22221
|
+
"update",
|
|
22222
|
+
"delete"
|
|
22223
|
+
])
|
|
22224
|
+
if (isKeyword(token, command)) return command;
|
|
22225
|
+
throw new PolicySyntaxError(
|
|
22226
|
+
`expected ALL, SELECT, INSERT, UPDATE or DELETE, found ${describeToken(token)}`,
|
|
22227
|
+
token.position
|
|
22228
|
+
);
|
|
22229
|
+
}
|
|
22230
|
+
function readClause(stream) {
|
|
22231
|
+
const open = stream.expectOperator("(");
|
|
22232
|
+
const expr = readCondition(stream);
|
|
22233
|
+
stream.expectOperator(")");
|
|
22234
|
+
checkExpressionSize(expr, open.position);
|
|
22235
|
+
return { text: printExpr(expr), position: open.position };
|
|
22236
|
+
}
|
|
22237
|
+
function readCreate(stream, start) {
|
|
22238
|
+
stream.expectKeyword("policy");
|
|
22239
|
+
const name = readPolicyName(stream);
|
|
22240
|
+
stream.expectKeyword("on");
|
|
22241
|
+
const table = readTableName(stream);
|
|
22242
|
+
let permissive = true;
|
|
22243
|
+
if (stream.acceptKeyword("as")) {
|
|
22244
|
+
const token = stream.next();
|
|
22245
|
+
if (isKeyword(token, "restrictive")) permissive = false;
|
|
22246
|
+
else if (!isKeyword(token, "permissive"))
|
|
22247
|
+
throw new PolicySyntaxError(
|
|
22248
|
+
`expected PERMISSIVE or RESTRICTIVE, found ${describeToken(token)}`,
|
|
22249
|
+
token.position
|
|
22250
|
+
);
|
|
22251
|
+
}
|
|
22252
|
+
let command = "all";
|
|
22253
|
+
if (stream.acceptKeyword("for")) command = readCommand(stream);
|
|
22254
|
+
let roles = readRoles(["public"]);
|
|
22255
|
+
if (isKeyword(stream.peek(), "to")) {
|
|
22256
|
+
const to = stream.next();
|
|
22257
|
+
const names = [];
|
|
22258
|
+
do
|
|
22259
|
+
names.push(readRoleName(stream.next()));
|
|
22260
|
+
while (stream.acceptOperator(","));
|
|
22261
|
+
roles = readRoles(names);
|
|
22262
|
+
if (!roles.ok)
|
|
22263
|
+
throw new PolicySyntaxError(roles.reason.replace(/\.$/, ""), to.position);
|
|
22264
|
+
}
|
|
22265
|
+
let using = null;
|
|
22266
|
+
let check2 = null;
|
|
22267
|
+
let position = start.position;
|
|
22268
|
+
if (stream.acceptKeyword("using")) {
|
|
22269
|
+
const clause = readClause(stream);
|
|
22270
|
+
using = clause.text;
|
|
22271
|
+
position = clause.position;
|
|
22272
|
+
}
|
|
22273
|
+
if (isKeyword(stream.peek(), "with")) {
|
|
22274
|
+
stream.next();
|
|
22275
|
+
stream.expectKeyword("check");
|
|
22276
|
+
const clause = readClause(stream);
|
|
22277
|
+
check2 = clause.text;
|
|
22278
|
+
position = clause.position;
|
|
22279
|
+
}
|
|
22280
|
+
if (!roles.ok) throw new Error("unreachable");
|
|
22281
|
+
const policy = {
|
|
22282
|
+
table,
|
|
22283
|
+
name,
|
|
22284
|
+
permissive,
|
|
22285
|
+
command,
|
|
22286
|
+
roles: roles.roles,
|
|
22287
|
+
using,
|
|
22288
|
+
check: check2,
|
|
22289
|
+
owner: null
|
|
22290
|
+
};
|
|
22291
|
+
const problem = policyShapeProblem(policy);
|
|
22292
|
+
if (problem)
|
|
22293
|
+
throw new PolicySyntaxError(problem.replace(/\.$/, ""), position);
|
|
22294
|
+
return { kind: "create", policy };
|
|
22295
|
+
}
|
|
22296
|
+
function readDrop(stream) {
|
|
22297
|
+
stream.expectKeyword("policy");
|
|
22298
|
+
let ifExists = false;
|
|
22299
|
+
if (stream.acceptKeyword("if")) {
|
|
22300
|
+
stream.expectKeyword("exists");
|
|
22301
|
+
ifExists = true;
|
|
22302
|
+
}
|
|
22303
|
+
const name = readPolicyName(stream);
|
|
22304
|
+
stream.expectKeyword("on");
|
|
22305
|
+
const table = readTableName(stream);
|
|
22306
|
+
if (!stream.acceptKeyword("cascade")) stream.acceptKeyword("restrict");
|
|
22307
|
+
return { kind: "drop", name, table, ifExists };
|
|
22308
|
+
}
|
|
22309
|
+
function parsePolicyStatement(text) {
|
|
22310
|
+
if (policySqlTooLong(text))
|
|
22311
|
+
return {
|
|
22312
|
+
ok: false,
|
|
22313
|
+
reason: `the statement is longer than ${String(MAX_POLICY_SQL_BYTES)} bytes`,
|
|
22314
|
+
position: 1
|
|
22315
|
+
};
|
|
22316
|
+
try {
|
|
22317
|
+
const stream = new TokenStream(tokenize(text));
|
|
22318
|
+
const start = stream.next();
|
|
22319
|
+
let statement;
|
|
22320
|
+
if (isKeyword(start, "create")) statement = readCreate(stream, start);
|
|
22321
|
+
else if (isKeyword(start, "drop")) statement = readDrop(stream);
|
|
22322
|
+
else if (isKeyword(start, "alter"))
|
|
22323
|
+
throw new PolicySyntaxError(
|
|
22324
|
+
"ALTER POLICY is not supported here yet: DROP POLICY and CREATE POLICY again",
|
|
22325
|
+
start.position
|
|
22326
|
+
);
|
|
22327
|
+
else if (start.kind === "eof")
|
|
22328
|
+
throw new PolicySyntaxError("the statement is empty", 1);
|
|
22329
|
+
else
|
|
22330
|
+
throw new PolicySyntaxError(
|
|
22331
|
+
"only CREATE POLICY and DROP POLICY are accepted here",
|
|
22332
|
+
start.position
|
|
22333
|
+
);
|
|
22334
|
+
stream.acceptOperator(";");
|
|
22335
|
+
const rest = stream.peek();
|
|
22336
|
+
if (rest.kind !== "eof")
|
|
22337
|
+
throw new PolicySyntaxError(
|
|
22338
|
+
isKeyword(rest, "create") || isKeyword(rest, "drop") ? "one statement at a time" : `syntax error at or near ${describeToken(rest)}`,
|
|
22339
|
+
rest.position
|
|
22340
|
+
);
|
|
22341
|
+
return { ok: true, statement };
|
|
22342
|
+
} catch (error51) {
|
|
22343
|
+
if (error51 instanceof PolicySyntaxError)
|
|
22344
|
+
return { ok: false, reason: error51.reason, position: error51.position };
|
|
22345
|
+
throw error51;
|
|
22346
|
+
}
|
|
22347
|
+
}
|
|
22348
|
+
function splitPolicyStatements(text) {
|
|
22349
|
+
let tokens;
|
|
22350
|
+
try {
|
|
22351
|
+
tokens = tokenize(text);
|
|
22352
|
+
} catch (error51) {
|
|
22353
|
+
if (error51 instanceof PolicySyntaxError)
|
|
22354
|
+
return { ok: false, reason: error51.reason, position: error51.position };
|
|
22355
|
+
throw error51;
|
|
22356
|
+
}
|
|
22357
|
+
const statements = [];
|
|
22358
|
+
let start;
|
|
22359
|
+
for (const token of tokens) {
|
|
22360
|
+
if (token.kind === "eof") break;
|
|
22361
|
+
start ??= token.position - 1;
|
|
22362
|
+
if (token.kind === "op" && token.value === ";") {
|
|
22363
|
+
statements.push(text.slice(start, token.position).trim());
|
|
22364
|
+
start = void 0;
|
|
22365
|
+
}
|
|
22366
|
+
}
|
|
22367
|
+
if (start !== void 0) {
|
|
22368
|
+
const last = text.slice(start).trim();
|
|
22369
|
+
if (last !== "") statements.push(last);
|
|
22370
|
+
}
|
|
22371
|
+
return { ok: true, statements };
|
|
22372
|
+
}
|
|
20122
22373
|
|
|
20123
22374
|
// ../../packages/data-core/src/redirect-origins.ts
|
|
20124
22375
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
@@ -20165,6 +22416,34 @@ function formatRedirectEntry(entry) {
|
|
|
20165
22416
|
}
|
|
20166
22417
|
|
|
20167
22418
|
// ../../packages/data-core/src/system-sql.ts
|
|
22419
|
+
async function convertLegacyPolicies(db, found) {
|
|
22420
|
+
const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
|
|
22421
|
+
if (rows.length === 0) return [];
|
|
22422
|
+
const read = rows.map((row) => ({
|
|
22423
|
+
policy: policyFromRow(row),
|
|
22424
|
+
asRead: legacyRowAsRead(row)
|
|
22425
|
+
}));
|
|
22426
|
+
const inForce = new Set(
|
|
22427
|
+
legacyInForce(
|
|
22428
|
+
read.flatMap(({ policy }) => policy ? [policy] : []),
|
|
22429
|
+
await readTableNames(db)
|
|
22430
|
+
)
|
|
22431
|
+
);
|
|
22432
|
+
const statements = [];
|
|
22433
|
+
for (const { policy, asRead } of read) {
|
|
22434
|
+
if (!policy || !asRead) continue;
|
|
22435
|
+
if (inForce.has(policy)) {
|
|
22436
|
+
for (const named of namedFromLegacy(policy))
|
|
22437
|
+
statements.push({
|
|
22438
|
+
sql: POLICY_SQL.insertConverted,
|
|
22439
|
+
params: [...policyValues(named), ...asRead]
|
|
22440
|
+
});
|
|
22441
|
+
found.unreadable.push(...unreadableConversions(policy));
|
|
22442
|
+
}
|
|
22443
|
+
statements.push({ sql: POLICY_SQL.markConverted, params: asRead });
|
|
22444
|
+
}
|
|
22445
|
+
return statements;
|
|
22446
|
+
}
|
|
20168
22447
|
var MIGRATIONS_TABLE = "CREATE TABLE IF NOT EXISTS _naro_migrations (version INTEGER PRIMARY KEY, name TEXT NOT NULL, applied_at TEXT NOT NULL DEFAULT (datetime('now')))";
|
|
20169
22448
|
var SYSTEM_MIGRATIONS = [
|
|
20170
22449
|
{
|
|
@@ -20206,6 +22485,15 @@ var SYSTEM_MIGRATIONS = [
|
|
|
20206
22485
|
"CREATE INDEX IF NOT EXISTS _naro_auth_email_log_address ON _naro_auth_email_log (address_hash, sent_at)",
|
|
20207
22486
|
"CREATE TABLE IF NOT EXISTS _naro_auth_contested (user_id TEXT PRIMARY KEY, contested_at INTEGER NOT NULL)"
|
|
20208
22487
|
]
|
|
22488
|
+
},
|
|
22489
|
+
{
|
|
22490
|
+
// P2-7 §9: named policies, many per table. The old rows are converted
|
|
22491
|
+
// in the same batch and their rules set to a word old workers read as
|
|
22492
|
+
// `none`.
|
|
22493
|
+
version: 4,
|
|
22494
|
+
name: "named_policies",
|
|
22495
|
+
statements: [NAMED_POLICY_TABLE_SQL],
|
|
22496
|
+
compute: convertLegacyPolicies
|
|
20209
22497
|
}
|
|
20210
22498
|
];
|
|
20211
22499
|
|
|
@@ -20271,12 +22559,12 @@ function sqlSkeleton(sql) {
|
|
|
20271
22559
|
let index = 0;
|
|
20272
22560
|
while (index < sql.length) {
|
|
20273
22561
|
const char = sql[index] ?? "";
|
|
20274
|
-
const
|
|
20275
|
-
if (
|
|
22562
|
+
const pair2 = sql.slice(index, index + 2);
|
|
22563
|
+
if (pair2 === "--") {
|
|
20276
22564
|
const end = sql.indexOf("\n", index);
|
|
20277
22565
|
index = end < 0 ? sql.length : end;
|
|
20278
22566
|
out += " ";
|
|
20279
|
-
} else if (
|
|
22567
|
+
} else if (pair2 === "/*") {
|
|
20280
22568
|
const end = sql.indexOf("*/", index + 2);
|
|
20281
22569
|
index = end < 0 ? sql.length : end + 2;
|
|
20282
22570
|
out += " ";
|
|
@@ -20454,12 +22742,12 @@ import {
|
|
|
20454
22742
|
rmSync as rmSync2,
|
|
20455
22743
|
writeFileSync as writeFileSync2
|
|
20456
22744
|
} from "fs";
|
|
20457
|
-
import { join as
|
|
22745
|
+
import { join as join22 } from "path";
|
|
20458
22746
|
function linkDir(dir) {
|
|
20459
|
-
return
|
|
22747
|
+
return join22(dir, ".naro");
|
|
20460
22748
|
}
|
|
20461
22749
|
function linkFile(dir) {
|
|
20462
|
-
return
|
|
22750
|
+
return join22(linkDir(dir), "project.json");
|
|
20463
22751
|
}
|
|
20464
22752
|
function readProjectLink(dir) {
|
|
20465
22753
|
try {
|
|
@@ -20481,7 +22769,7 @@ function writeProjectLink(dir, link) {
|
|
|
20481
22769
|
writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
|
|
20482
22770
|
`);
|
|
20483
22771
|
writeFileSync2(
|
|
20484
|
-
|
|
22772
|
+
join22(linkDir(dir), "README.txt"),
|
|
20485
22773
|
"This directory links the project to Naro and should not be committed.\n"
|
|
20486
22774
|
);
|
|
20487
22775
|
}
|
|
@@ -20491,9 +22779,9 @@ function removeProjectLink(dir) {
|
|
|
20491
22779
|
return true;
|
|
20492
22780
|
}
|
|
20493
22781
|
function ensureGitignoreHasNaro(dir) {
|
|
20494
|
-
const gitignorePath =
|
|
22782
|
+
const gitignorePath = join22(dir, ".gitignore");
|
|
20495
22783
|
const hasGitignore = existsSync4(gitignorePath);
|
|
20496
|
-
if (!hasGitignore && !existsSync4(
|
|
22784
|
+
if (!hasGitignore && !existsSync4(join22(dir, ".git"))) return false;
|
|
20497
22785
|
const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
|
|
20498
22786
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
|
|
20499
22787
|
if (covered) return false;
|
|
@@ -20834,10 +23122,10 @@ function buildResetPlan(input) {
|
|
|
20834
23122
|
const triggers = sorted(
|
|
20835
23123
|
objects.filter((object2) => object2.type === "trigger").map((object2) => object2.name)
|
|
20836
23124
|
);
|
|
20837
|
-
const
|
|
23125
|
+
const tableNames2 = sorted(
|
|
20838
23126
|
objects.filter((object2) => object2.type === "table" || object2.type === "virtual").map((object2) => object2.name)
|
|
20839
23127
|
);
|
|
20840
|
-
const canonical = new Map(
|
|
23128
|
+
const canonical = new Map(tableNames2.map((name) => [asciiLower(name), name]));
|
|
20841
23129
|
const edges = [];
|
|
20842
23130
|
for (const edge of input.foreignKeys) {
|
|
20843
23131
|
const child = canonical.get(asciiLower(edge.child));
|
|
@@ -20845,7 +23133,7 @@ function buildResetPlan(input) {
|
|
|
20845
23133
|
if (child !== void 0 && parent !== void 0 && child !== parent)
|
|
20846
23134
|
edges.push({ child, parent });
|
|
20847
23135
|
}
|
|
20848
|
-
const ordered = tableGroups(
|
|
23136
|
+
const ordered = tableGroups(tableNames2, edges);
|
|
20849
23137
|
refuseOversizedCycle(ordered);
|
|
20850
23138
|
const clear = clearInternal(input.internal);
|
|
20851
23139
|
const groups = [
|
|
@@ -20864,7 +23152,7 @@ function buildResetPlan(input) {
|
|
|
20864
23152
|
|
|
20865
23153
|
// src/env-consent.ts
|
|
20866
23154
|
import { existsSync as existsSync6 } from "fs";
|
|
20867
|
-
import { join as
|
|
23155
|
+
import { join as join23 } from "path";
|
|
20868
23156
|
var DOTENV_FILES = [".env.local", ".env.production", ".env"];
|
|
20869
23157
|
var PUBLIC_PREFIXES = ["NEXT_PUBLIC_", "VITE_", "REACT_APP_", "PUBLIC_"];
|
|
20870
23158
|
function maskEnvValue(key, value) {
|
|
@@ -20873,7 +23161,7 @@ function maskEnvValue(key, value) {
|
|
|
20873
23161
|
return value.length <= 8 ? "****" : `${value.slice(0, 4)}****`;
|
|
20874
23162
|
}
|
|
20875
23163
|
function findDotenvFile(dir) {
|
|
20876
|
-
for (const name of DOTENV_FILES) if (existsSync6(
|
|
23164
|
+
for (const name of DOTENV_FILES) if (existsSync6(join23(dir, name))) return name;
|
|
20877
23165
|
return null;
|
|
20878
23166
|
}
|
|
20879
23167
|
function envAddGuidance(keys) {
|
|
@@ -20944,6 +23232,24 @@ async function tryExec(exec, args, cwd) {
|
|
|
20944
23232
|
return null;
|
|
20945
23233
|
}
|
|
20946
23234
|
}
|
|
23235
|
+
var COMMIT_SHA = /^[0-9a-f]{40}$/;
|
|
23236
|
+
var MAX_COMMIT_MESSAGE = 300;
|
|
23237
|
+
async function detectCommit(exec, dir) {
|
|
23238
|
+
const sha = (await tryExec(exec, ["rev-parse", "HEAD"], dir))?.toLowerCase();
|
|
23239
|
+
if (!sha || !COMMIT_SHA.test(sha)) return null;
|
|
23240
|
+
const subject = await tryExec(exec, ["log", "-1", "--format=%s"], dir);
|
|
23241
|
+
let dirty;
|
|
23242
|
+
try {
|
|
23243
|
+
dirty = (await exec(["status", "--porcelain"], dir)).trim().length > 0;
|
|
23244
|
+
} catch {
|
|
23245
|
+
dirty = true;
|
|
23246
|
+
}
|
|
23247
|
+
return {
|
|
23248
|
+
sha,
|
|
23249
|
+
message: subject === null ? null : [...subject].slice(0, MAX_COMMIT_MESSAGE).join(""),
|
|
23250
|
+
dirty
|
|
23251
|
+
};
|
|
23252
|
+
}
|
|
20947
23253
|
async function detectGitInfo(dir, exec = defaultExec) {
|
|
20948
23254
|
const remote = await tryExec(exec, ["remote", "get-url", "origin"], dir);
|
|
20949
23255
|
const branch = await tryExec(
|
|
@@ -20952,10 +23258,12 @@ async function detectGitInfo(dir, exec = defaultExec) {
|
|
|
20952
23258
|
dir
|
|
20953
23259
|
);
|
|
20954
23260
|
const prefix = await tryExec(exec, ["rev-parse", "--show-prefix"], dir);
|
|
23261
|
+
const commit = await detectCommit(exec, dir);
|
|
20955
23262
|
return {
|
|
20956
23263
|
repositoryUrl: remote !== null && SUPPORTED_REMOTE.some((re) => re.test(remote)) ? remote : null,
|
|
20957
23264
|
branch: branch !== null && branch !== "HEAD" ? branch : null,
|
|
20958
|
-
prefix: prefix === null ? null : prefix.replace(/\/+$/, "") || null
|
|
23265
|
+
prefix: prefix === null ? null : prefix.replace(/\/+$/, "") || null,
|
|
23266
|
+
commit
|
|
20959
23267
|
};
|
|
20960
23268
|
}
|
|
20961
23269
|
|
|
@@ -21039,7 +23347,8 @@ function manifest(artifact) {
|
|
|
21039
23347
|
compatibilityDate: artifact.compatibilityDate,
|
|
21040
23348
|
compatibilityFlags: artifact.compatibilityFlags,
|
|
21041
23349
|
assetsBinding: artifact.assetsBinding,
|
|
21042
|
-
...artifact.assetsConfig ? { assetsConfig: artifact.assetsConfig } : {}
|
|
23350
|
+
...artifact.assetsConfig ? { assetsConfig: artifact.assetsConfig } : {},
|
|
23351
|
+
...artifact.routes ? { routes: artifact.routes } : {}
|
|
21043
23352
|
};
|
|
21044
23353
|
}
|
|
21045
23354
|
async function uploadArtifact(artifact, opts) {
|
|
@@ -21121,8 +23430,14 @@ function eventsObserver(cp3, deploymentId, token, fetchImpl = fetch, logIngestUr
|
|
|
21121
23430
|
onLog: (log2) => batcher.add(log2).catch(() => {
|
|
21122
23431
|
}),
|
|
21123
23432
|
onMetadata: async (metadata) => {
|
|
23433
|
+
const {
|
|
23434
|
+
gitCommitSha: _sha,
|
|
23435
|
+
gitCommitMessage: _message,
|
|
23436
|
+
...rest
|
|
23437
|
+
} = metadata;
|
|
23438
|
+
if (Object.keys(rest).length === 0) return;
|
|
21124
23439
|
await flushLogs();
|
|
21125
|
-
await post({ type: "metadata", metadata }).catch(() => {
|
|
23440
|
+
await post({ type: "metadata", metadata: rest }).catch(() => {
|
|
21126
23441
|
});
|
|
21127
23442
|
}
|
|
21128
23443
|
};
|
|
@@ -21134,7 +23449,7 @@ function slugify2(value) {
|
|
|
21134
23449
|
async function projectName(sourceDir) {
|
|
21135
23450
|
try {
|
|
21136
23451
|
const pkg = JSON.parse(
|
|
21137
|
-
await readFile13(
|
|
23452
|
+
await readFile13(join24(sourceDir, "package.json"), "utf8")
|
|
21138
23453
|
);
|
|
21139
23454
|
if (pkg.name) return pkg.name;
|
|
21140
23455
|
} catch {
|
|
@@ -21145,7 +23460,7 @@ var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.tu
|
|
|
21145
23460
|
function resolveBin(name) {
|
|
21146
23461
|
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
21147
23462
|
for (; ; ) {
|
|
21148
|
-
const candidate =
|
|
23463
|
+
const candidate = join24(dir, "node_modules", ".bin", name);
|
|
21149
23464
|
if (existsSync7(candidate)) return candidate;
|
|
21150
23465
|
const parent = dirname4(dir);
|
|
21151
23466
|
if (parent === dir) return name;
|
|
@@ -21172,7 +23487,7 @@ async function detectPackageManagerLenient(root) {
|
|
|
21172
23487
|
}
|
|
21173
23488
|
function localDependencies(sourceDir) {
|
|
21174
23489
|
return {
|
|
21175
|
-
createWorkspace: () => mkdtemp2(
|
|
23490
|
+
createWorkspace: () => mkdtemp2(join24(tmpdir2(), "naro-build-")),
|
|
21176
23491
|
removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
|
|
21177
23492
|
async runCommand(spec, observer) {
|
|
21178
23493
|
if (spec.command === "git" && spec.args[0] === "clone") {
|
|
@@ -21277,7 +23592,8 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
21277
23592
|
...git.repositoryUrl != null && { repositoryUrl: git.repositoryUrl },
|
|
21278
23593
|
...branch != null && { branch },
|
|
21279
23594
|
...repositoryRoot != null && { rootDirectory: repositoryRoot },
|
|
21280
|
-
...Object.keys(envToSend).length > 0 && { env: envToSend }
|
|
23595
|
+
...Object.keys(envToSend).length > 0 && { env: envToSend },
|
|
23596
|
+
...git.commit != null && { commit: git.commit }
|
|
21281
23597
|
});
|
|
21282
23598
|
if (ctx.envSkipped?.length)
|
|
21283
23599
|
log2(`\u26A0 skipped env: ${ctx.envSkipped.join(", ")}`);
|
|
@@ -21380,6 +23696,7 @@ export {
|
|
|
21380
23696
|
resolveAuth,
|
|
21381
23697
|
openBrowser,
|
|
21382
23698
|
isTerminalStatus,
|
|
23699
|
+
BACKEND_STEPS,
|
|
21383
23700
|
isStalledBackend,
|
|
21384
23701
|
POLICY_RULES,
|
|
21385
23702
|
POLICY_ACTIONS,
|
|
@@ -21392,7 +23709,12 @@ export {
|
|
|
21392
23709
|
summarizeStatement,
|
|
21393
23710
|
serializeDotenv,
|
|
21394
23711
|
systemTablePredicate,
|
|
23712
|
+
quoteIdent,
|
|
23713
|
+
commentText,
|
|
23714
|
+
quotePolicyName,
|
|
21395
23715
|
parsePolicyNode,
|
|
23716
|
+
parsePolicyStatement,
|
|
23717
|
+
splitPolicyStatements,
|
|
21396
23718
|
parseRedirectEntry,
|
|
21397
23719
|
formatRedirectEntry,
|
|
21398
23720
|
policiesForTypes,
|