@valbuild/server 0.60.21 → 0.60.23
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/dist/declarations/src/LocalValServer.d.ts +3 -1
- package/dist/declarations/src/Service.d.ts +3 -9
- package/dist/declarations/src/ValServer.d.ts +4 -5
- package/dist/declarations/src/createValApiRouter.d.ts +7 -1
- package/dist/valbuild-server.cjs.dev.js +178 -355
- package/dist/valbuild-server.cjs.prod.js +178 -355
- package/dist/valbuild-server.esm.js +178 -354
- package/package.json +4 -4
@@ -12,7 +12,6 @@ import z, { z as z$1 } from 'zod';
|
|
12
12
|
import { MIME_TYPES_TO_EXT, filenameToMimeType, VAL_ENABLE_COOKIE_NAME, VAL_STATE_COOKIE as VAL_STATE_COOKIE$1, VAL_SESSION_COOKIE as VAL_SESSION_COOKIE$1 } from '@valbuild/shared/internal';
|
13
13
|
import sizeOf from 'image-size';
|
14
14
|
import crypto from 'crypto';
|
15
|
-
import minimatch from 'minimatch';
|
16
15
|
import { createUIRequestHandler } from '@valbuild/ui/server';
|
17
16
|
|
18
17
|
class ValSyntaxError {
|
@@ -662,7 +661,7 @@ const patchSourceFile = (sourceFile, patch) => {
|
|
662
661
|
return applyPatch(sourceFile, ops$1, patch);
|
663
662
|
};
|
664
663
|
|
665
|
-
const readValFile = async (id,
|
664
|
+
const readValFile = async (id, rootDirPath, runtime, options) => {
|
666
665
|
const context = runtime.newContext();
|
667
666
|
|
668
667
|
// avoid failures when console.log is called
|
@@ -714,7 +713,7 @@ globalThis.valModule = {
|
|
714
713
|
`;
|
715
714
|
const result = context.evalCode(code,
|
716
715
|
// Synthetic module name
|
717
|
-
path__default.join(
|
716
|
+
path__default.join(rootDirPath, "<val>"));
|
718
717
|
const fatalErrors = [];
|
719
718
|
if (result.error) {
|
720
719
|
const error = result.error.consume(context.dump);
|
@@ -1159,18 +1158,16 @@ async function createService(projectRoot, opts, host = {
|
|
1159
1158
|
const sourceFileHandler = new ValSourceFileHandler(projectRoot, compilerOptions, host);
|
1160
1159
|
const module = await newQuickJSWASMModule();
|
1161
1160
|
const runtime = await newValQuickJSRuntime(module, loader || new ValModuleLoader(projectRoot, compilerOptions, sourceFileHandler, host, opts.disableCache === undefined ? process.env.NODE_ENV === "development" ? false : true : opts.disableCache));
|
1162
|
-
return new Service(
|
1161
|
+
return new Service(projectRoot, sourceFileHandler, runtime);
|
1163
1162
|
}
|
1164
1163
|
class Service {
|
1165
|
-
constructor({
|
1166
|
-
valConfigPath
|
1167
|
-
}, sourceFileHandler, runtime) {
|
1164
|
+
constructor(projectRoot, sourceFileHandler, runtime) {
|
1168
1165
|
this.sourceFileHandler = sourceFileHandler;
|
1169
1166
|
this.runtime = runtime;
|
1170
|
-
this.
|
1167
|
+
this.projectRoot = projectRoot;
|
1171
1168
|
}
|
1172
1169
|
async get(moduleId, modulePath, options) {
|
1173
|
-
const valModule = await readValFile(moduleId, this.
|
1170
|
+
const valModule = await readValFile(moduleId, this.projectRoot, this.runtime, options ?? {
|
1174
1171
|
validate: true,
|
1175
1172
|
source: true,
|
1176
1173
|
schema: true
|
@@ -1194,7 +1191,7 @@ class Service {
|
|
1194
1191
|
}
|
1195
1192
|
}
|
1196
1193
|
async patch(moduleId, patch) {
|
1197
|
-
await patchValFile(moduleId, this.
|
1194
|
+
await patchValFile(moduleId, this.projectRoot, patch, this.sourceFileHandler, this.runtime);
|
1198
1195
|
}
|
1199
1196
|
dispose() {
|
1200
1197
|
this.runtime.dispose();
|
@@ -1385,9 +1382,8 @@ function getValidationErrorMetadata(validationError) {
|
|
1385
1382
|
|
1386
1383
|
const ops = new JSONOps();
|
1387
1384
|
class ValServer {
|
1388
|
-
constructor(cwd,
|
1385
|
+
constructor(cwd, options, callbacks) {
|
1389
1386
|
this.cwd = cwd;
|
1390
|
-
this.host = host;
|
1391
1387
|
this.options = options;
|
1392
1388
|
this.callbacks = callbacks;
|
1393
1389
|
}
|
@@ -1423,19 +1419,10 @@ class ValServer {
|
|
1423
1419
|
redirectTo: redirectToRes
|
1424
1420
|
};
|
1425
1421
|
}
|
1426
|
-
getAllModules(treePath) {
|
1427
|
-
const moduleIds = this.host.readDirectory(this.cwd, ["ts", "js"], ["node_modules", ".*"], ["**/*.val.ts", "**/*.val.js"]).filter(file => {
|
1428
|
-
if (treePath) {
|
1429
|
-
return file.replace(this.cwd, "").startsWith(treePath);
|
1430
|
-
}
|
1431
|
-
return true;
|
1432
|
-
}).map(file => file.replace(this.cwd, "").replace(".val.js", "").replace(".val.ts", "").split(path__default.sep).join("/"));
|
1433
|
-
return moduleIds;
|
1434
|
-
}
|
1435
1422
|
async getTree(treePath,
|
1436
1423
|
// TODO: use the params: patch, schema, source now we return everything, every time
|
1437
1424
|
query, cookies, requestHeaders) {
|
1438
|
-
const ensureRes = await this.
|
1425
|
+
const ensureRes = await debugTiming("ensureInitialized", () => this.ensureInitialized("getTree", cookies));
|
1439
1426
|
if (result.isErr(ensureRes)) {
|
1440
1427
|
return ensureRes.error;
|
1441
1428
|
}
|
@@ -1443,7 +1430,7 @@ class ValServer {
|
|
1443
1430
|
const execValidations = query.validate === "true";
|
1444
1431
|
const includeSource = query.source === "true";
|
1445
1432
|
const includeSchema = query.schema === "true";
|
1446
|
-
const moduleIds = this.getAllModules(treePath);
|
1433
|
+
const moduleIds = await debugTiming("getAllModules", () => this.getAllModules(treePath));
|
1447
1434
|
let {
|
1448
1435
|
patchIdsByModuleId,
|
1449
1436
|
patchesById,
|
@@ -1454,7 +1441,7 @@ class ValServer {
|
|
1454
1441
|
fileUpdates: {}
|
1455
1442
|
};
|
1456
1443
|
if (applyPatches) {
|
1457
|
-
const res = await this.readPatches(cookies);
|
1444
|
+
const res = await debugTiming("readPatches", () => this.readPatches(cookies));
|
1458
1445
|
if (result.isErr(res)) {
|
1459
1446
|
return res.error;
|
1460
1447
|
}
|
@@ -1462,9 +1449,9 @@ class ValServer {
|
|
1462
1449
|
patchesById = res.value.patchesById;
|
1463
1450
|
fileUpdates = res.value.fileUpdates;
|
1464
1451
|
}
|
1465
|
-
const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
|
1452
|
+
const possiblyPatchedContent = await debugTiming("applyAllPatchesThenValidate", () => Promise.all(moduleIds.map(async moduleId => {
|
1466
1453
|
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
|
1467
|
-
}));
|
1454
|
+
})));
|
1468
1455
|
const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
|
1469
1456
|
const module = {
|
1470
1457
|
schema: serializedModuleContent.schema,
|
@@ -1483,14 +1470,14 @@ class ValServer {
|
|
1483
1470
|
};
|
1484
1471
|
}
|
1485
1472
|
async postValidate(rawBody, cookies, requestHeaders) {
|
1486
|
-
const ensureRes = await this.
|
1473
|
+
const ensureRes = await this.ensureInitialized("postValidate", cookies);
|
1487
1474
|
if (result.isErr(ensureRes)) {
|
1488
1475
|
return ensureRes.error;
|
1489
1476
|
}
|
1490
1477
|
return this.validateThenMaybeCommit(rawBody, false, cookies, requestHeaders);
|
1491
1478
|
}
|
1492
1479
|
async postCommit(rawBody, cookies, requestHeaders) {
|
1493
|
-
const ensureRes = await this.
|
1480
|
+
const ensureRes = await this.ensureInitialized("postCommit", cookies);
|
1494
1481
|
if (result.isErr(ensureRes)) {
|
1495
1482
|
return ensureRes.error;
|
1496
1483
|
}
|
@@ -1516,7 +1503,6 @@ class ValServer {
|
|
1516
1503
|
}
|
1517
1504
|
|
1518
1505
|
/* */
|
1519
|
-
|
1520
1506
|
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, validate, includeSource, includeSchema) {
|
1521
1507
|
const serializedModuleContent = await this.getModule(moduleId, {
|
1522
1508
|
validate: validate,
|
@@ -1535,38 +1521,40 @@ class ValServer {
|
|
1535
1521
|
return serializedModuleContent;
|
1536
1522
|
}
|
1537
1523
|
let source = maybeSource;
|
1538
|
-
|
1539
|
-
const
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1524
|
+
await debugTiming("applyPatches:" + moduleId, async () => {
|
1525
|
+
for (const patchId of patchIdsByModuleId[moduleId] ?? []) {
|
1526
|
+
const patch = patchesById[patchId];
|
1527
|
+
if (!patch) {
|
1528
|
+
continue;
|
1529
|
+
}
|
1530
|
+
const patchRes = applyPatch(source, ops, patch.filter(Internal.notFileOp));
|
1531
|
+
if (result.isOk(patchRes)) {
|
1532
|
+
source = patchRes.value;
|
1533
|
+
} else {
|
1534
|
+
console.error("Val: got an unexpected error while applying patch. Is there a mismatch in Val versions? Perhaps Val is misconfigured?", {
|
1535
|
+
patchId,
|
1536
|
+
moduleId,
|
1537
|
+
patch: JSON.stringify(patch, null, 2),
|
1538
|
+
error: patchRes.error
|
1539
|
+
});
|
1540
|
+
return {
|
1541
|
+
path: moduleId,
|
1542
|
+
schema,
|
1543
|
+
source,
|
1544
|
+
errors: {
|
1545
|
+
fatal: [{
|
1546
|
+
message: "Unexpected error applying patch",
|
1547
|
+
type: "invalid-patch"
|
1548
|
+
}]
|
1549
|
+
}
|
1550
|
+
};
|
1551
|
+
}
|
1564
1552
|
}
|
1565
|
-
}
|
1553
|
+
});
|
1566
1554
|
if (validate) {
|
1567
|
-
const validationErrors = deserializeSchema(schema).validate(moduleId, source);
|
1555
|
+
const validationErrors = await debugTiming("validate:" + moduleId, async () => deserializeSchema(schema).validate(moduleId, source));
|
1568
1556
|
if (validationErrors) {
|
1569
|
-
const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
|
1557
|
+
const revalidated = await debugTiming("revalidate image/file:" + moduleId, async () => this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders));
|
1570
1558
|
return {
|
1571
1559
|
path: moduleId,
|
1572
1560
|
schema,
|
@@ -1781,7 +1769,7 @@ class ValServer {
|
|
1781
1769
|
fileUpdates
|
1782
1770
|
} = res.value;
|
1783
1771
|
const validationErrorsByModuleId = {};
|
1784
|
-
for (const moduleIdStr of this.getAllModules("/")) {
|
1772
|
+
for (const moduleIdStr of await this.getAllModules("/")) {
|
1785
1773
|
const moduleId = moduleIdStr;
|
1786
1774
|
const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
|
1787
1775
|
// TODO: refine to ModuleId and PatchId when parsing
|
@@ -2018,16 +2006,27 @@ function guessMimeTypeFromPath(filePath) {
|
|
2018
2006
|
function isCachedPatchFileOp(op) {
|
2019
2007
|
return !!(op.op === "file" && typeof op.filePath === "string" && op.value && typeof op.value === "object" && !Array.isArray(op.value) && "sha256" in op.value && typeof op.value.sha256 === "string");
|
2020
2008
|
}
|
2009
|
+
async function debugTiming(id, fn) {
|
2010
|
+
if (process.env["VAL_DEBUG_TIMING"] === "true") {
|
2011
|
+
const start = Date.now();
|
2012
|
+
const r = await fn();
|
2013
|
+
console.log(`Timing: ${id} took: ${Date.now() - start}ms (${new Date().toISOString()})`);
|
2014
|
+
return r;
|
2015
|
+
} else {
|
2016
|
+
return fn();
|
2017
|
+
}
|
2018
|
+
}
|
2021
2019
|
|
2022
2020
|
const textEncoder = new TextEncoder();
|
2023
2021
|
class LocalValServer extends ValServer {
|
2024
2022
|
static PATCHES_DIR = "patches";
|
2025
2023
|
static FILES_DIR = "files";
|
2026
2024
|
constructor(options, callbacks) {
|
2027
|
-
super(options.service.sourceFileHandler.projectRoot, options
|
2025
|
+
super(options.service.sourceFileHandler.projectRoot, options, callbacks);
|
2028
2026
|
this.options = options;
|
2029
2027
|
this.callbacks = callbacks;
|
2030
2028
|
this.patchesRootPath = options.cacheDir || path__default.join(options.service.sourceFileHandler.projectRoot, ".val");
|
2029
|
+
this.host = this.options.service.sourceFileHandler.host;
|
2031
2030
|
}
|
2032
2031
|
async session() {
|
2033
2032
|
return {
|
@@ -2285,13 +2284,22 @@ class LocalValServer extends ValServer {
|
|
2285
2284
|
}
|
2286
2285
|
};
|
2287
2286
|
}
|
2288
|
-
async
|
2287
|
+
async ensureInitialized() {
|
2289
2288
|
// No RemoteFS so nothing to ensure
|
2290
2289
|
return result.ok(undefined);
|
2291
2290
|
}
|
2292
2291
|
getModule(moduleId, options) {
|
2293
2292
|
return this.options.service.get(moduleId, "", options);
|
2294
2293
|
}
|
2294
|
+
async getAllModules(treePath) {
|
2295
|
+
const moduleIds = this.host.readDirectory(this.cwd, ["ts", "js"], ["node_modules", ".*"], ["**/*.val.ts", "**/*.val.js"]).filter(file => {
|
2296
|
+
if (treePath) {
|
2297
|
+
return file.replace(this.cwd, "").startsWith(treePath);
|
2298
|
+
}
|
2299
|
+
return true;
|
2300
|
+
}).map(file => file.replace(this.cwd, "").replace(".val.js", "").replace(".val.ts", "").split(path__default.sep).join("/"));
|
2301
|
+
return moduleIds;
|
2302
|
+
}
|
2295
2303
|
async execCommit(patches) {
|
2296
2304
|
for (const [patchId, moduleId, patch] of patches) {
|
2297
2305
|
// TODO: patch the entire module content directly by using a { path: "", op: "replace", value: patchedData }?
|
@@ -2378,219 +2386,32 @@ function encodeJwt(payload, sessionKey) {
|
|
2378
2386
|
return `${jwtHeaderBase64}.${payloadBase64}.${crypto.createHmac("sha256", sessionKey).update(`${jwtHeaderBase64}.${payloadBase64}`).digest("base64")}`;
|
2379
2387
|
}
|
2380
2388
|
|
2381
|
-
const SEPARATOR = "/";
|
2382
|
-
class RemoteFS {
|
2383
|
-
initialized = false;
|
2384
|
-
constructor() {
|
2385
|
-
this.data = {};
|
2386
|
-
this.modifiedFiles = [];
|
2387
|
-
this.deletedFiles = [];
|
2388
|
-
}
|
2389
|
-
useCaseSensitiveFileNames = true;
|
2390
|
-
isInitialized() {
|
2391
|
-
return this.initialized;
|
2392
|
-
}
|
2393
|
-
async initializeWith(data) {
|
2394
|
-
this.data = data;
|
2395
|
-
this.initialized = true;
|
2396
|
-
}
|
2397
|
-
async getPendingOperations() {
|
2398
|
-
const modified = {};
|
2399
|
-
for (const modifiedFile of this.modifiedFiles) {
|
2400
|
-
const {
|
2401
|
-
directory,
|
2402
|
-
filename
|
2403
|
-
} = RemoteFS.parsePath(modifiedFile);
|
2404
|
-
modified[modifiedFile] = this.data[directory].utf8Files[filename];
|
2405
|
-
}
|
2406
|
-
return {
|
2407
|
-
modified: modified,
|
2408
|
-
deleted: this.deletedFiles
|
2409
|
-
};
|
2410
|
-
}
|
2411
|
-
changedDirectories = {};
|
2412
|
-
readDirectory = (rootDir, extensions, excludes, includes, depth) => {
|
2413
|
-
// TODO: rewrite this! And make some tests! This is a mess!
|
2414
|
-
// Considered using glob which typescript seems to use, but that works on an entire typeof fs
|
2415
|
-
// glob uses minimatch internally, so using that instead
|
2416
|
-
const files = [];
|
2417
|
-
for (const dir in this.data) {
|
2418
|
-
const depthExceeded = depth ? dir.replace(rootDir, "").split(SEPARATOR).length > depth : false;
|
2419
|
-
if (dir.startsWith(rootDir) && !depthExceeded) {
|
2420
|
-
for (const file in this.data[dir].utf8Files) {
|
2421
|
-
for (const extension of extensions) {
|
2422
|
-
if (file.endsWith(extension)) {
|
2423
|
-
const path = `${dir}/${file}`;
|
2424
|
-
for (const include of includes ?? []) {
|
2425
|
-
// TODO: should default includes be ['**/*']?
|
2426
|
-
if (minimatch(path, include)) {
|
2427
|
-
let isExcluded = false;
|
2428
|
-
for (const exlude of excludes ?? []) {
|
2429
|
-
if (minimatch(path, exlude)) {
|
2430
|
-
isExcluded = true;
|
2431
|
-
break;
|
2432
|
-
}
|
2433
|
-
}
|
2434
|
-
if (!isExcluded) {
|
2435
|
-
files.push(path);
|
2436
|
-
}
|
2437
|
-
}
|
2438
|
-
}
|
2439
|
-
}
|
2440
|
-
}
|
2441
|
-
}
|
2442
|
-
}
|
2443
|
-
}
|
2444
|
-
return ts.sys.readDirectory(rootDir, extensions, excludes, includes, depth).concat(files);
|
2445
|
-
};
|
2446
|
-
writeFile = (filePath, data, encoding) => {
|
2447
|
-
// never write real fs
|
2448
|
-
const {
|
2449
|
-
directory,
|
2450
|
-
filename
|
2451
|
-
} = RemoteFS.parsePath(filePath);
|
2452
|
-
if (this.data[directory] === undefined) {
|
2453
|
-
throw new Error(`Directory not found: ${directory}`);
|
2454
|
-
}
|
2455
|
-
this.changedDirectories[directory] = this.changedDirectories[directory] ?? new Set();
|
2456
|
-
|
2457
|
-
// if it fails below this should not be added, so maybe a try/catch?
|
2458
|
-
this.changedDirectories[directory].add(filename);
|
2459
|
-
this.data[directory].utf8Files[filename] = data;
|
2460
|
-
this.modifiedFiles.push(filePath);
|
2461
|
-
};
|
2462
|
-
rmFile(filePath) {
|
2463
|
-
// never remove from real fs
|
2464
|
-
const {
|
2465
|
-
directory,
|
2466
|
-
filename
|
2467
|
-
} = RemoteFS.parsePath(filePath);
|
2468
|
-
if (this.data[directory] === undefined) {
|
2469
|
-
throw new Error(`Directory not found: ${directory}`);
|
2470
|
-
}
|
2471
|
-
this.changedDirectories[directory] = this.changedDirectories[directory] ?? new Set();
|
2472
|
-
|
2473
|
-
// if it fails below this should not be added, so maybe a try/catch?
|
2474
|
-
this.changedDirectories[directory].add(filename);
|
2475
|
-
delete this.data[directory].utf8Files[filename];
|
2476
|
-
delete this.data[directory].symlinks[filename];
|
2477
|
-
this.deletedFiles.push(filePath);
|
2478
|
-
}
|
2479
|
-
fileExists = filePath => {
|
2480
|
-
var _this$data$directory;
|
2481
|
-
if (ts.sys.fileExists(filePath)) {
|
2482
|
-
return true;
|
2483
|
-
}
|
2484
|
-
const {
|
2485
|
-
directory,
|
2486
|
-
filename
|
2487
|
-
} = RemoteFS.parsePath(this.realpath(filePath) // ts.sys seems to resolve symlinks while calling fileExists, i.e. a broken symlink (pointing to a non-existing file) is not considered to exist
|
2488
|
-
);
|
2489
|
-
return !!((_this$data$directory = this.data[directory]) !== null && _this$data$directory !== void 0 && _this$data$directory.utf8Files[filename]);
|
2490
|
-
};
|
2491
|
-
readFile = filePath => {
|
2492
|
-
const realFile = ts.sys.readFile(filePath);
|
2493
|
-
if (realFile !== undefined) {
|
2494
|
-
return realFile;
|
2495
|
-
}
|
2496
|
-
const {
|
2497
|
-
directory,
|
2498
|
-
filename
|
2499
|
-
} = RemoteFS.parsePath(filePath);
|
2500
|
-
const dirNode = this.data[directory];
|
2501
|
-
if (!dirNode) {
|
2502
|
-
return undefined;
|
2503
|
-
}
|
2504
|
-
const content = dirNode.utf8Files[filename];
|
2505
|
-
return content;
|
2506
|
-
};
|
2507
|
-
realpath(fullPath) {
|
2508
|
-
if (ts.sys.fileExists(fullPath) && ts.sys.realpath) {
|
2509
|
-
return ts.sys.realpath(fullPath);
|
2510
|
-
}
|
2511
|
-
// TODO: this only works in a very limited way.
|
2512
|
-
// It does not support symlinks to symlinks nor symlinked directories for instance.
|
2513
|
-
const {
|
2514
|
-
directory,
|
2515
|
-
filename
|
2516
|
-
} = RemoteFS.parsePath(fullPath);
|
2517
|
-
if (this.data[directory] === undefined) {
|
2518
|
-
return fullPath;
|
2519
|
-
}
|
2520
|
-
if (this.data[directory].utf8Files[filename] === undefined) {
|
2521
|
-
const link = this.data[directory].symlinks[filename];
|
2522
|
-
if (link === undefined) {
|
2523
|
-
return fullPath;
|
2524
|
-
} else {
|
2525
|
-
return link;
|
2526
|
-
}
|
2527
|
-
} else {
|
2528
|
-
return path__default.join(directory, filename);
|
2529
|
-
}
|
2530
|
-
}
|
2531
|
-
|
2532
|
-
/**
|
2533
|
-
*
|
2534
|
-
* @param path
|
2535
|
-
* @returns directory and filename. NOTE: directory might be empty string
|
2536
|
-
*/
|
2537
|
-
static parsePath(path) {
|
2538
|
-
const pathParts = path.split(SEPARATOR);
|
2539
|
-
const filename = pathParts.pop();
|
2540
|
-
if (!filename) {
|
2541
|
-
throw new Error(`Invalid path: '${path}'. Node filename: '${filename}'`);
|
2542
|
-
}
|
2543
|
-
const directory = pathParts.join(SEPARATOR);
|
2544
|
-
return {
|
2545
|
-
directory,
|
2546
|
-
filename
|
2547
|
-
};
|
2548
|
-
}
|
2549
|
-
}
|
2550
|
-
|
2551
|
-
/**
|
2552
|
-
* Represents directories
|
2553
|
-
* NOTE: the keys of directory nodes are the "full" path, i.e. "foo/bar"
|
2554
|
-
* NOTE: the keys of file nodes are the "filename" only, i.e. "baz.txt"
|
2555
|
-
*
|
2556
|
-
* @example
|
2557
|
-
* {
|
2558
|
-
* "foo/bar": { // <- directory. NOTE: this is the "full" path
|
2559
|
-
* gitHubSha: "123",
|
2560
|
-
* files: {
|
2561
|
-
* "baz.txt": "hello world" // <- file. NOTE: this is the "filename" only
|
2562
|
-
* },
|
2563
|
-
* },
|
2564
|
-
* };
|
2565
|
-
*/
|
2566
|
-
// TODO: a Map would be better here
|
2567
|
-
|
2568
2389
|
class ProxyValServer extends ValServer {
|
2569
|
-
moduleCache =
|
2390
|
+
moduleCache = null;
|
2570
2391
|
constructor(cwd, options, apiOptions, callbacks) {
|
2571
|
-
|
2572
|
-
super(cwd, remoteFS, options, callbacks);
|
2392
|
+
super(cwd, options, callbacks);
|
2573
2393
|
this.cwd = cwd;
|
2574
2394
|
this.options = options;
|
2575
2395
|
this.apiOptions = apiOptions;
|
2576
2396
|
this.callbacks = callbacks;
|
2577
|
-
this.
|
2397
|
+
this.moduleCache = null;
|
2578
2398
|
}
|
2579
2399
|
|
2580
2400
|
/** Remote FS dependent methods: */
|
2581
2401
|
|
2582
|
-
async getModule(moduleId,
|
2583
|
-
|
2584
|
-
|
2585
|
-
if (
|
2586
|
-
return
|
2402
|
+
async getModule(moduleId,
|
2403
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
2404
|
+
_options) {
|
2405
|
+
if (this.moduleCache) {
|
2406
|
+
return this.moduleCache[moduleId];
|
2587
2407
|
}
|
2588
|
-
|
2589
|
-
|
2408
|
+
throw new Error("Module cache not initialized");
|
2409
|
+
}
|
2410
|
+
async getAllModules(treePath) {
|
2411
|
+
if (!this.moduleCache) {
|
2412
|
+
throw new Error("Module cache not initialized");
|
2590
2413
|
}
|
2591
|
-
|
2592
|
-
this.moduleCache[cacheKey] = currentModule;
|
2593
|
-
return currentModule;
|
2414
|
+
return Object.keys(this.moduleCache).filter(moduleId => moduleId.startsWith(treePath));
|
2594
2415
|
}
|
2595
2416
|
execCommit(patches, cookies) {
|
2596
2417
|
return withAuth(this.options.valSecret, cookies, "execCommit", async ({
|
@@ -2605,46 +2426,19 @@ class ProxyValServer extends ValServer {
|
|
2605
2426
|
}
|
2606
2427
|
};
|
2607
2428
|
}
|
2608
|
-
const params =
|
2429
|
+
const params = createParams({
|
2430
|
+
root: this.apiOptions.root,
|
2431
|
+
commit,
|
2432
|
+
ext: ["ts", "js", "json"],
|
2433
|
+
package: ["@valbuild/core@" + this.options.versions.core, "@valbuild/next@" + this.options.versions.next],
|
2434
|
+
include: ["**/*.val.{js,ts},package.json,tsconfig.json,jsconfig.json"]
|
2435
|
+
});
|
2609
2436
|
const url = new URL(`/v1/commit/${this.options.remote}/heads/${this.options.git.branch}/~?${params}`, this.options.valContentUrl);
|
2610
|
-
|
2611
|
-
// Creates a fresh copy of the fs. We cannot touch the existing fs, since there might be parallel operations?
|
2612
|
-
// We could perhaps free up the other fs while doing this operation, but uncertain if we can actually do that and if that would actually help on memory.
|
2613
|
-
// It is a concern we have, since we might be using quite a lot of memory when having the whole FS in memory.
|
2614
|
-
// NOTE that base64 values from patches are not part of the patches, nor are they part of the fs so at least we do not have to worry about them.
|
2615
|
-
// This NOTE was written after we wrote the comments above. We are a bit uncertain whether memory usage should be a concern at this point.
|
2616
|
-
const remoteFS = new RemoteFS();
|
2617
|
-
const initRes = await this.initRemoteFS(commit, remoteFS, token);
|
2618
|
-
if (initRes.status !== 200) {
|
2619
|
-
return initRes;
|
2620
|
-
}
|
2621
|
-
const service = await createService(this.cwd, this.apiOptions, remoteFS);
|
2622
|
-
// TODO: optimize patches, e.g. only take the last replace for a given thing, etc...
|
2623
|
-
const patchIds = [];
|
2624
|
-
const binaryFileUpdates = {};
|
2625
|
-
for (const [patchId, moduleId, patch] of patches) {
|
2626
|
-
const patchableOps = [];
|
2627
|
-
for (const op of patch) {
|
2628
|
-
if (isCachedPatchFileOp(op)) {
|
2629
|
-
binaryFileUpdates[op.filePath] = op.value;
|
2630
|
-
} else {
|
2631
|
-
if (Internal.isFileOp(op)) {
|
2632
|
-
throw new Error(`Val: Unexpected file operation (file: ${op.filePath}). This is likely a Val bug.`);
|
2633
|
-
}
|
2634
|
-
patchableOps.push(op);
|
2635
|
-
}
|
2636
|
-
}
|
2637
|
-
await service.patch(moduleId, patchableOps);
|
2638
|
-
patchIds.push(patchId);
|
2639
|
-
}
|
2640
|
-
const sourceFileUpdates = await remoteFS.getPendingOperations();
|
2437
|
+
const patchIds = patches.map(([patchId]) => patchId);
|
2641
2438
|
const fetchRes = await fetch(url, {
|
2642
2439
|
method: "POST",
|
2643
2440
|
headers: getAuthHeaders(token, "application/json"),
|
2644
2441
|
body: JSON.stringify({
|
2645
|
-
sourceFileUpdates: sourceFileUpdates.modified,
|
2646
|
-
binaryFileUpdates,
|
2647
|
-
deletedFiles: sourceFileUpdates.deleted,
|
2648
2442
|
patchIds
|
2649
2443
|
})
|
2650
2444
|
});
|
@@ -2658,14 +2452,15 @@ class ProxyValServer extends ValServer {
|
|
2658
2452
|
}
|
2659
2453
|
});
|
2660
2454
|
}
|
2661
|
-
async
|
2662
|
-
const params =
|
2455
|
+
async init(commit, token) {
|
2456
|
+
const params = createParams({
|
2663
2457
|
root: this.apiOptions.root,
|
2664
|
-
commit
|
2665
|
-
|
2666
|
-
|
2458
|
+
commit,
|
2459
|
+
ext: ["ts", "js", "json"],
|
2460
|
+
package: ["@valbuild/core@" + this.options.versions.core, "@valbuild/next@" + this.options.versions.next],
|
2461
|
+
include: ["**/*.val.{js,ts},package.json,tsconfig.json,jsconfig.json"]
|
2667
2462
|
});
|
2668
|
-
const url = new URL(`/v1/
|
2463
|
+
const url = new URL(`/v1/eval/${this.options.remote}/heads/${this.options.git.branch}/~?${params}`, this.options.valContentUrl);
|
2669
2464
|
try {
|
2670
2465
|
const fetchRes = await fetch(url, {
|
2671
2466
|
headers: getAuthHeaders(token, "application/json")
|
@@ -2688,22 +2483,22 @@ class ProxyValServer extends ValServer {
|
|
2688
2483
|
details: "Invalid response: missing git.commit"
|
2689
2484
|
};
|
2690
2485
|
}
|
2691
|
-
if (typeof json.
|
2486
|
+
if (typeof json.modules !== "object" || json.modules === null) {
|
2692
2487
|
error = {
|
2693
|
-
details: "Invalid response: missing
|
2488
|
+
details: "Invalid response: missing modules"
|
2694
2489
|
};
|
2695
2490
|
}
|
2696
2491
|
if (error) {
|
2492
|
+
console.error("Could not initialize remote modules", error);
|
2697
2493
|
return {
|
2698
2494
|
status: 500,
|
2699
2495
|
json: {
|
2700
|
-
message: "Failed to fetch remote
|
2496
|
+
message: "Failed to fetch remote modules",
|
2701
2497
|
...error
|
2702
2498
|
}
|
2703
2499
|
};
|
2704
2500
|
}
|
2705
|
-
|
2706
|
-
), content])));
|
2501
|
+
this.moduleCache = json.modules;
|
2707
2502
|
return {
|
2708
2503
|
status: 200
|
2709
2504
|
};
|
@@ -2719,7 +2514,7 @@ class ProxyValServer extends ValServer {
|
|
2719
2514
|
};
|
2720
2515
|
}
|
2721
2516
|
}
|
2722
|
-
async
|
2517
|
+
async ensureInitialized(errorMessageType, cookies) {
|
2723
2518
|
const commit = this.options.git.commit;
|
2724
2519
|
if (!commit) {
|
2725
2520
|
return result.err({
|
@@ -2730,8 +2525,8 @@ class ProxyValServer extends ValServer {
|
|
2730
2525
|
});
|
2731
2526
|
}
|
2732
2527
|
const res = await withAuth(this.options.valSecret, cookies, errorMessageType, async data => {
|
2733
|
-
if (!this.
|
2734
|
-
return this.
|
2528
|
+
if (!this.moduleCache) {
|
2529
|
+
return this.init(commit, data.token);
|
2735
2530
|
} else {
|
2736
2531
|
return {
|
2737
2532
|
status: 200
|
@@ -3011,54 +2806,51 @@ class ProxyValServer extends ValServer {
|
|
3011
2806
|
}
|
3012
2807
|
});
|
3013
2808
|
}
|
3014
|
-
async getFiles(filePath, query,
|
3015
|
-
|
3016
|
-
|
3017
|
-
|
3018
|
-
|
2809
|
+
async getFiles(filePath, query, _cookies, reqHeaders) {
|
2810
|
+
const url = new URL(`/v1/files/${this.options.remote}${filePath}`, this.options.valContentUrl);
|
2811
|
+
if (typeof query.sha256 === "string") {
|
2812
|
+
url.searchParams.append("sha256", query.sha256);
|
2813
|
+
}
|
2814
|
+
const fetchRes = await fetch(url);
|
2815
|
+
if (fetchRes.status === 200) {
|
2816
|
+
// TODO: does this stream data?
|
2817
|
+
if (fetchRes.body) {
|
2818
|
+
return {
|
2819
|
+
status: fetchRes.status,
|
2820
|
+
headers: {
|
2821
|
+
"Content-Type": fetchRes.headers.get("Content-Type") || "",
|
2822
|
+
"Content-Length": fetchRes.headers.get("Content-Length") || "0",
|
2823
|
+
"Cache-Control": fetchRes.headers.get("Cache-Control") || ""
|
2824
|
+
},
|
2825
|
+
body: fetchRes.body
|
2826
|
+
};
|
3019
2827
|
} else {
|
3020
|
-
|
2828
|
+
return {
|
2829
|
+
status: 500,
|
2830
|
+
json: {
|
2831
|
+
message: "No body in response"
|
2832
|
+
}
|
2833
|
+
};
|
3021
2834
|
}
|
3022
|
-
|
3023
|
-
|
3024
|
-
});
|
3025
|
-
if (fetchRes.status === 200) {
|
3026
|
-
// TODO: does this stream data?
|
3027
|
-
if (fetchRes.body) {
|
3028
|
-
return {
|
3029
|
-
status: fetchRes.status,
|
3030
|
-
headers: {
|
3031
|
-
"Content-Type": fetchRes.headers.get("Content-Type") || "",
|
3032
|
-
"Content-Length": fetchRes.headers.get("Content-Length") || "0",
|
3033
|
-
"Cache-Control": "public, max-age=31536000, immutable"
|
3034
|
-
},
|
3035
|
-
body: fetchRes.body
|
3036
|
-
};
|
3037
|
-
} else {
|
3038
|
-
return {
|
3039
|
-
status: 500,
|
3040
|
-
json: {
|
3041
|
-
message: "No body in response"
|
3042
|
-
}
|
3043
|
-
};
|
3044
|
-
}
|
3045
|
-
} else {
|
3046
|
-
if (!(reqHeaders.host && reqHeaders["x-forwarded-proto"])) {
|
3047
|
-
return {
|
3048
|
-
status: 500,
|
3049
|
-
json: {
|
3050
|
-
message: "Missing host or x-forwarded-proto header"
|
3051
|
-
}
|
3052
|
-
};
|
3053
|
-
}
|
3054
|
-
const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
|
3055
|
-
const fileUrl = filePath.slice("/public".length);
|
2835
|
+
} else {
|
2836
|
+
if (!(reqHeaders.host && reqHeaders["x-forwarded-proto"])) {
|
3056
2837
|
return {
|
3057
|
-
status:
|
3058
|
-
|
2838
|
+
status: 500,
|
2839
|
+
json: {
|
2840
|
+
message: "Missing host or x-forwarded-proto header"
|
2841
|
+
}
|
3059
2842
|
};
|
3060
2843
|
}
|
3061
|
-
|
2844
|
+
const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
|
2845
|
+
const staticPublicUrl = new URL(filePath.slice("/public".length), host).toString();
|
2846
|
+
const fetchRes = await fetch(staticPublicUrl);
|
2847
|
+
return {
|
2848
|
+
status: fetchRes.status,
|
2849
|
+
// forward headers from static public url
|
2850
|
+
headers: Object.fromEntries(fetchRes.headers.entries()),
|
2851
|
+
body: fetchRes.body
|
2852
|
+
};
|
2853
|
+
}
|
3062
2854
|
}
|
3063
2855
|
}
|
3064
2856
|
function verifyCallbackReq(stateCookie, queryParams) {
|
@@ -3248,6 +3040,25 @@ function getAuthHeaders(token, type) {
|
|
3248
3040
|
Authorization: `Bearer ${token}`
|
3249
3041
|
};
|
3250
3042
|
}
|
3043
|
+
function createParams(params) {
|
3044
|
+
let paramIdx = 0;
|
3045
|
+
let paramsString = "";
|
3046
|
+
for (const key in params) {
|
3047
|
+
const param = params[key];
|
3048
|
+
if (Array.isArray(param)) {
|
3049
|
+
for (const value of param) {
|
3050
|
+
paramsString += `${key}=${encodeURIComponent(value)}&`;
|
3051
|
+
}
|
3052
|
+
} else if (param) {
|
3053
|
+
paramsString += `${key}=${encodeURIComponent(param)}`;
|
3054
|
+
}
|
3055
|
+
if (paramIdx < Object.keys(params).length - 1) {
|
3056
|
+
paramsString += "&";
|
3057
|
+
}
|
3058
|
+
paramIdx++;
|
3059
|
+
}
|
3060
|
+
return paramsString;
|
3061
|
+
}
|
3251
3062
|
|
3252
3063
|
async function createValServer(route, opts, callbacks) {
|
3253
3064
|
const serverOpts = await initHandlerOptions(route, opts);
|
@@ -3263,6 +3074,7 @@ async function initHandlerOptions(route, opts) {
|
|
3263
3074
|
const maybeValSecret = opts.valSecret || process.env.VAL_SECRET;
|
3264
3075
|
const isProxyMode = opts.mode === "proxy" || opts.mode === undefined && (maybeApiKey || maybeValSecret);
|
3265
3076
|
if (isProxyMode) {
|
3077
|
+
var _opts$versions, _opts$versions2;
|
3266
3078
|
const valBuildUrl = opts.valBuildUrl || process.env.VAL_BUILD_URL || "https://app.val.build";
|
3267
3079
|
if (!maybeApiKey || !maybeValSecret) {
|
3268
3080
|
throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode");
|
@@ -3280,6 +3092,14 @@ async function initHandlerOptions(route, opts) {
|
|
3280
3092
|
if (!maybeValRemote) {
|
3281
3093
|
throw new Error("Proxy mode does not work unless the 'remote' option in val.config is defined or the VAL_REMOTE env var is set.");
|
3282
3094
|
}
|
3095
|
+
const coreVersion = (_opts$versions = opts.versions) === null || _opts$versions === void 0 ? void 0 : _opts$versions.core;
|
3096
|
+
if (!coreVersion) {
|
3097
|
+
throw new Error("Could not determine version of @valbuild/core");
|
3098
|
+
}
|
3099
|
+
const nextVersion = (_opts$versions2 = opts.versions) === null || _opts$versions2 === void 0 ? void 0 : _opts$versions2.next;
|
3100
|
+
if (!nextVersion) {
|
3101
|
+
throw new Error("Could not determine version of @valbuild/next");
|
3102
|
+
}
|
3283
3103
|
return {
|
3284
3104
|
mode: "proxy",
|
3285
3105
|
route,
|
@@ -3287,6 +3107,10 @@ async function initHandlerOptions(route, opts) {
|
|
3287
3107
|
valSecret: maybeValSecret,
|
3288
3108
|
valBuildUrl,
|
3289
3109
|
valContentUrl,
|
3110
|
+
versions: {
|
3111
|
+
core: coreVersion,
|
3112
|
+
next: nextVersion
|
3113
|
+
},
|
3290
3114
|
git: {
|
3291
3115
|
commit: maybeGitCommit,
|
3292
3116
|
branch: maybeGitBranch
|