@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
@@ -15,7 +15,6 @@ var z = require('zod');
|
|
15
15
|
var internal = require('@valbuild/shared/internal');
|
16
16
|
var sizeOf = require('image-size');
|
17
17
|
var crypto = require('crypto');
|
18
|
-
var minimatch = require('minimatch');
|
19
18
|
var server = require('@valbuild/ui/server');
|
20
19
|
|
21
20
|
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
@@ -44,7 +43,6 @@ var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
44
43
|
var z__default = /*#__PURE__*/_interopDefault(z);
|
45
44
|
var sizeOf__default = /*#__PURE__*/_interopDefault(sizeOf);
|
46
45
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
47
|
-
var minimatch__default = /*#__PURE__*/_interopDefault(minimatch);
|
48
46
|
|
49
47
|
class ValSyntaxError {
|
50
48
|
constructor(message, node) {
|
@@ -693,7 +691,7 @@ const patchSourceFile = (sourceFile, patch$1) => {
|
|
693
691
|
return patch.applyPatch(sourceFile, ops$1, patch$1);
|
694
692
|
};
|
695
693
|
|
696
|
-
const readValFile = async (id,
|
694
|
+
const readValFile = async (id, rootDirPath, runtime, options) => {
|
697
695
|
const context = runtime.newContext();
|
698
696
|
|
699
697
|
// avoid failures when console.log is called
|
@@ -745,7 +743,7 @@ globalThis.valModule = {
|
|
745
743
|
`;
|
746
744
|
const result = context.evalCode(code,
|
747
745
|
// Synthetic module name
|
748
|
-
path__namespace["default"].join(
|
746
|
+
path__namespace["default"].join(rootDirPath, "<val>"));
|
749
747
|
const fatalErrors = [];
|
750
748
|
if (result.error) {
|
751
749
|
const error = result.error.consume(context.dump);
|
@@ -1190,18 +1188,16 @@ async function createService(projectRoot, opts, host = {
|
|
1190
1188
|
const sourceFileHandler = new ValSourceFileHandler(projectRoot, compilerOptions, host);
|
1191
1189
|
const module = await quickjsEmscripten.newQuickJSWASMModule();
|
1192
1190
|
const runtime = await newValQuickJSRuntime(module, loader || new ValModuleLoader(projectRoot, compilerOptions, sourceFileHandler, host, opts.disableCache === undefined ? true : opts.disableCache));
|
1193
|
-
return new Service(
|
1191
|
+
return new Service(projectRoot, sourceFileHandler, runtime);
|
1194
1192
|
}
|
1195
1193
|
class Service {
|
1196
|
-
constructor({
|
1197
|
-
valConfigPath
|
1198
|
-
}, sourceFileHandler, runtime) {
|
1194
|
+
constructor(projectRoot, sourceFileHandler, runtime) {
|
1199
1195
|
this.sourceFileHandler = sourceFileHandler;
|
1200
1196
|
this.runtime = runtime;
|
1201
|
-
this.
|
1197
|
+
this.projectRoot = projectRoot;
|
1202
1198
|
}
|
1203
1199
|
async get(moduleId, modulePath, options) {
|
1204
|
-
const valModule = await readValFile(moduleId, this.
|
1200
|
+
const valModule = await readValFile(moduleId, this.projectRoot, this.runtime, options ?? {
|
1205
1201
|
validate: true,
|
1206
1202
|
source: true,
|
1207
1203
|
schema: true
|
@@ -1225,7 +1221,7 @@ class Service {
|
|
1225
1221
|
}
|
1226
1222
|
}
|
1227
1223
|
async patch(moduleId, patch) {
|
1228
|
-
await patchValFile(moduleId, this.
|
1224
|
+
await patchValFile(moduleId, this.projectRoot, patch, this.sourceFileHandler, this.runtime);
|
1229
1225
|
}
|
1230
1226
|
dispose() {
|
1231
1227
|
this.runtime.dispose();
|
@@ -1416,9 +1412,8 @@ function getValidationErrorMetadata(validationError) {
|
|
1416
1412
|
|
1417
1413
|
const ops = new patch.JSONOps();
|
1418
1414
|
class ValServer {
|
1419
|
-
constructor(cwd,
|
1415
|
+
constructor(cwd, options, callbacks) {
|
1420
1416
|
this.cwd = cwd;
|
1421
|
-
this.host = host;
|
1422
1417
|
this.options = options;
|
1423
1418
|
this.callbacks = callbacks;
|
1424
1419
|
}
|
@@ -1454,19 +1449,10 @@ class ValServer {
|
|
1454
1449
|
redirectTo: redirectToRes
|
1455
1450
|
};
|
1456
1451
|
}
|
1457
|
-
getAllModules(treePath) {
|
1458
|
-
const moduleIds = this.host.readDirectory(this.cwd, ["ts", "js"], ["node_modules", ".*"], ["**/*.val.ts", "**/*.val.js"]).filter(file => {
|
1459
|
-
if (treePath) {
|
1460
|
-
return file.replace(this.cwd, "").startsWith(treePath);
|
1461
|
-
}
|
1462
|
-
return true;
|
1463
|
-
}).map(file => file.replace(this.cwd, "").replace(".val.js", "").replace(".val.ts", "").split(path__namespace["default"].sep).join("/"));
|
1464
|
-
return moduleIds;
|
1465
|
-
}
|
1466
1452
|
async getTree(treePath,
|
1467
1453
|
// TODO: use the params: patch, schema, source now we return everything, every time
|
1468
1454
|
query, cookies, requestHeaders) {
|
1469
|
-
const ensureRes = await this.
|
1455
|
+
const ensureRes = await debugTiming("ensureInitialized", () => this.ensureInitialized("getTree", cookies));
|
1470
1456
|
if (fp.result.isErr(ensureRes)) {
|
1471
1457
|
return ensureRes.error;
|
1472
1458
|
}
|
@@ -1474,7 +1460,7 @@ class ValServer {
|
|
1474
1460
|
const execValidations = query.validate === "true";
|
1475
1461
|
const includeSource = query.source === "true";
|
1476
1462
|
const includeSchema = query.schema === "true";
|
1477
|
-
const moduleIds = this.getAllModules(treePath);
|
1463
|
+
const moduleIds = await debugTiming("getAllModules", () => this.getAllModules(treePath));
|
1478
1464
|
let {
|
1479
1465
|
patchIdsByModuleId,
|
1480
1466
|
patchesById,
|
@@ -1485,7 +1471,7 @@ class ValServer {
|
|
1485
1471
|
fileUpdates: {}
|
1486
1472
|
};
|
1487
1473
|
if (applyPatches) {
|
1488
|
-
const res = await this.readPatches(cookies);
|
1474
|
+
const res = await debugTiming("readPatches", () => this.readPatches(cookies));
|
1489
1475
|
if (fp.result.isErr(res)) {
|
1490
1476
|
return res.error;
|
1491
1477
|
}
|
@@ -1493,9 +1479,9 @@ class ValServer {
|
|
1493
1479
|
patchesById = res.value.patchesById;
|
1494
1480
|
fileUpdates = res.value.fileUpdates;
|
1495
1481
|
}
|
1496
|
-
const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
|
1482
|
+
const possiblyPatchedContent = await debugTiming("applyAllPatchesThenValidate", () => Promise.all(moduleIds.map(async moduleId => {
|
1497
1483
|
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
|
1498
|
-
}));
|
1484
|
+
})));
|
1499
1485
|
const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
|
1500
1486
|
const module = {
|
1501
1487
|
schema: serializedModuleContent.schema,
|
@@ -1514,14 +1500,14 @@ class ValServer {
|
|
1514
1500
|
};
|
1515
1501
|
}
|
1516
1502
|
async postValidate(rawBody, cookies, requestHeaders) {
|
1517
|
-
const ensureRes = await this.
|
1503
|
+
const ensureRes = await this.ensureInitialized("postValidate", cookies);
|
1518
1504
|
if (fp.result.isErr(ensureRes)) {
|
1519
1505
|
return ensureRes.error;
|
1520
1506
|
}
|
1521
1507
|
return this.validateThenMaybeCommit(rawBody, false, cookies, requestHeaders);
|
1522
1508
|
}
|
1523
1509
|
async postCommit(rawBody, cookies, requestHeaders) {
|
1524
|
-
const ensureRes = await this.
|
1510
|
+
const ensureRes = await this.ensureInitialized("postCommit", cookies);
|
1525
1511
|
if (fp.result.isErr(ensureRes)) {
|
1526
1512
|
return ensureRes.error;
|
1527
1513
|
}
|
@@ -1547,7 +1533,6 @@ class ValServer {
|
|
1547
1533
|
}
|
1548
1534
|
|
1549
1535
|
/* */
|
1550
|
-
|
1551
1536
|
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, validate, includeSource, includeSchema) {
|
1552
1537
|
const serializedModuleContent = await this.getModule(moduleId, {
|
1553
1538
|
validate: validate,
|
@@ -1566,38 +1551,40 @@ class ValServer {
|
|
1566
1551
|
return serializedModuleContent;
|
1567
1552
|
}
|
1568
1553
|
let source = maybeSource;
|
1569
|
-
|
1570
|
-
const
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1554
|
+
await debugTiming("applyPatches:" + moduleId, async () => {
|
1555
|
+
for (const patchId of patchIdsByModuleId[moduleId] ?? []) {
|
1556
|
+
const patch$1 = patchesById[patchId];
|
1557
|
+
if (!patch$1) {
|
1558
|
+
continue;
|
1559
|
+
}
|
1560
|
+
const patchRes = patch.applyPatch(source, ops, patch$1.filter(core.Internal.notFileOp));
|
1561
|
+
if (fp.result.isOk(patchRes)) {
|
1562
|
+
source = patchRes.value;
|
1563
|
+
} else {
|
1564
|
+
console.error("Val: got an unexpected error while applying patch. Is there a mismatch in Val versions? Perhaps Val is misconfigured?", {
|
1565
|
+
patchId,
|
1566
|
+
moduleId,
|
1567
|
+
patch: JSON.stringify(patch$1, null, 2),
|
1568
|
+
error: patchRes.error
|
1569
|
+
});
|
1570
|
+
return {
|
1571
|
+
path: moduleId,
|
1572
|
+
schema,
|
1573
|
+
source,
|
1574
|
+
errors: {
|
1575
|
+
fatal: [{
|
1576
|
+
message: "Unexpected error applying patch",
|
1577
|
+
type: "invalid-patch"
|
1578
|
+
}]
|
1579
|
+
}
|
1580
|
+
};
|
1581
|
+
}
|
1595
1582
|
}
|
1596
|
-
}
|
1583
|
+
});
|
1597
1584
|
if (validate) {
|
1598
|
-
const validationErrors = core.deserializeSchema(schema).validate(moduleId, source);
|
1585
|
+
const validationErrors = await debugTiming("validate:" + moduleId, async () => core.deserializeSchema(schema).validate(moduleId, source));
|
1599
1586
|
if (validationErrors) {
|
1600
|
-
const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
|
1587
|
+
const revalidated = await debugTiming("revalidate image/file:" + moduleId, async () => this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders));
|
1601
1588
|
return {
|
1602
1589
|
path: moduleId,
|
1603
1590
|
schema,
|
@@ -1812,7 +1799,7 @@ class ValServer {
|
|
1812
1799
|
fileUpdates
|
1813
1800
|
} = res.value;
|
1814
1801
|
const validationErrorsByModuleId = {};
|
1815
|
-
for (const moduleIdStr of this.getAllModules("/")) {
|
1802
|
+
for (const moduleIdStr of await this.getAllModules("/")) {
|
1816
1803
|
const moduleId = moduleIdStr;
|
1817
1804
|
const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
|
1818
1805
|
// TODO: refine to ModuleId and PatchId when parsing
|
@@ -2049,16 +2036,27 @@ function guessMimeTypeFromPath(filePath) {
|
|
2049
2036
|
function isCachedPatchFileOp(op) {
|
2050
2037
|
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");
|
2051
2038
|
}
|
2039
|
+
async function debugTiming(id, fn) {
|
2040
|
+
if (process.env["VAL_DEBUG_TIMING"] === "true") {
|
2041
|
+
const start = Date.now();
|
2042
|
+
const r = await fn();
|
2043
|
+
console.log(`Timing: ${id} took: ${Date.now() - start}ms (${new Date().toISOString()})`);
|
2044
|
+
return r;
|
2045
|
+
} else {
|
2046
|
+
return fn();
|
2047
|
+
}
|
2048
|
+
}
|
2052
2049
|
|
2053
2050
|
const textEncoder = new TextEncoder();
|
2054
2051
|
class LocalValServer extends ValServer {
|
2055
2052
|
static PATCHES_DIR = "patches";
|
2056
2053
|
static FILES_DIR = "files";
|
2057
2054
|
constructor(options, callbacks) {
|
2058
|
-
super(options.service.sourceFileHandler.projectRoot, options
|
2055
|
+
super(options.service.sourceFileHandler.projectRoot, options, callbacks);
|
2059
2056
|
this.options = options;
|
2060
2057
|
this.callbacks = callbacks;
|
2061
2058
|
this.patchesRootPath = options.cacheDir || path__namespace["default"].join(options.service.sourceFileHandler.projectRoot, ".val");
|
2059
|
+
this.host = this.options.service.sourceFileHandler.host;
|
2062
2060
|
}
|
2063
2061
|
async session() {
|
2064
2062
|
return {
|
@@ -2316,13 +2314,22 @@ class LocalValServer extends ValServer {
|
|
2316
2314
|
}
|
2317
2315
|
};
|
2318
2316
|
}
|
2319
|
-
async
|
2317
|
+
async ensureInitialized() {
|
2320
2318
|
// No RemoteFS so nothing to ensure
|
2321
2319
|
return fp.result.ok(undefined);
|
2322
2320
|
}
|
2323
2321
|
getModule(moduleId, options) {
|
2324
2322
|
return this.options.service.get(moduleId, "", options);
|
2325
2323
|
}
|
2324
|
+
async getAllModules(treePath) {
|
2325
|
+
const moduleIds = this.host.readDirectory(this.cwd, ["ts", "js"], ["node_modules", ".*"], ["**/*.val.ts", "**/*.val.js"]).filter(file => {
|
2326
|
+
if (treePath) {
|
2327
|
+
return file.replace(this.cwd, "").startsWith(treePath);
|
2328
|
+
}
|
2329
|
+
return true;
|
2330
|
+
}).map(file => file.replace(this.cwd, "").replace(".val.js", "").replace(".val.ts", "").split(path__namespace["default"].sep).join("/"));
|
2331
|
+
return moduleIds;
|
2332
|
+
}
|
2326
2333
|
async execCommit(patches) {
|
2327
2334
|
for (const [patchId, moduleId, patch] of patches) {
|
2328
2335
|
// TODO: patch the entire module content directly by using a { path: "", op: "replace", value: patchedData }?
|
@@ -2409,219 +2416,32 @@ function encodeJwt(payload, sessionKey) {
|
|
2409
2416
|
return `${jwtHeaderBase64}.${payloadBase64}.${crypto__default["default"].createHmac("sha256", sessionKey).update(`${jwtHeaderBase64}.${payloadBase64}`).digest("base64")}`;
|
2410
2417
|
}
|
2411
2418
|
|
2412
|
-
const SEPARATOR = "/";
|
2413
|
-
class RemoteFS {
|
2414
|
-
initialized = false;
|
2415
|
-
constructor() {
|
2416
|
-
this.data = {};
|
2417
|
-
this.modifiedFiles = [];
|
2418
|
-
this.deletedFiles = [];
|
2419
|
-
}
|
2420
|
-
useCaseSensitiveFileNames = true;
|
2421
|
-
isInitialized() {
|
2422
|
-
return this.initialized;
|
2423
|
-
}
|
2424
|
-
async initializeWith(data) {
|
2425
|
-
this.data = data;
|
2426
|
-
this.initialized = true;
|
2427
|
-
}
|
2428
|
-
async getPendingOperations() {
|
2429
|
-
const modified = {};
|
2430
|
-
for (const modifiedFile of this.modifiedFiles) {
|
2431
|
-
const {
|
2432
|
-
directory,
|
2433
|
-
filename
|
2434
|
-
} = RemoteFS.parsePath(modifiedFile);
|
2435
|
-
modified[modifiedFile] = this.data[directory].utf8Files[filename];
|
2436
|
-
}
|
2437
|
-
return {
|
2438
|
-
modified: modified,
|
2439
|
-
deleted: this.deletedFiles
|
2440
|
-
};
|
2441
|
-
}
|
2442
|
-
changedDirectories = {};
|
2443
|
-
readDirectory = (rootDir, extensions, excludes, includes, depth) => {
|
2444
|
-
// TODO: rewrite this! And make some tests! This is a mess!
|
2445
|
-
// Considered using glob which typescript seems to use, but that works on an entire typeof fs
|
2446
|
-
// glob uses minimatch internally, so using that instead
|
2447
|
-
const files = [];
|
2448
|
-
for (const dir in this.data) {
|
2449
|
-
const depthExceeded = depth ? dir.replace(rootDir, "").split(SEPARATOR).length > depth : false;
|
2450
|
-
if (dir.startsWith(rootDir) && !depthExceeded) {
|
2451
|
-
for (const file in this.data[dir].utf8Files) {
|
2452
|
-
for (const extension of extensions) {
|
2453
|
-
if (file.endsWith(extension)) {
|
2454
|
-
const path = `${dir}/${file}`;
|
2455
|
-
for (const include of includes ?? []) {
|
2456
|
-
// TODO: should default includes be ['**/*']?
|
2457
|
-
if (minimatch__default["default"](path, include)) {
|
2458
|
-
let isExcluded = false;
|
2459
|
-
for (const exlude of excludes ?? []) {
|
2460
|
-
if (minimatch__default["default"](path, exlude)) {
|
2461
|
-
isExcluded = true;
|
2462
|
-
break;
|
2463
|
-
}
|
2464
|
-
}
|
2465
|
-
if (!isExcluded) {
|
2466
|
-
files.push(path);
|
2467
|
-
}
|
2468
|
-
}
|
2469
|
-
}
|
2470
|
-
}
|
2471
|
-
}
|
2472
|
-
}
|
2473
|
-
}
|
2474
|
-
}
|
2475
|
-
return ts__default["default"].sys.readDirectory(rootDir, extensions, excludes, includes, depth).concat(files);
|
2476
|
-
};
|
2477
|
-
writeFile = (filePath, data, encoding) => {
|
2478
|
-
// never write real fs
|
2479
|
-
const {
|
2480
|
-
directory,
|
2481
|
-
filename
|
2482
|
-
} = RemoteFS.parsePath(filePath);
|
2483
|
-
if (this.data[directory] === undefined) {
|
2484
|
-
throw new Error(`Directory not found: ${directory}`);
|
2485
|
-
}
|
2486
|
-
this.changedDirectories[directory] = this.changedDirectories[directory] ?? new Set();
|
2487
|
-
|
2488
|
-
// if it fails below this should not be added, so maybe a try/catch?
|
2489
|
-
this.changedDirectories[directory].add(filename);
|
2490
|
-
this.data[directory].utf8Files[filename] = data;
|
2491
|
-
this.modifiedFiles.push(filePath);
|
2492
|
-
};
|
2493
|
-
rmFile(filePath) {
|
2494
|
-
// never remove from real fs
|
2495
|
-
const {
|
2496
|
-
directory,
|
2497
|
-
filename
|
2498
|
-
} = RemoteFS.parsePath(filePath);
|
2499
|
-
if (this.data[directory] === undefined) {
|
2500
|
-
throw new Error(`Directory not found: ${directory}`);
|
2501
|
-
}
|
2502
|
-
this.changedDirectories[directory] = this.changedDirectories[directory] ?? new Set();
|
2503
|
-
|
2504
|
-
// if it fails below this should not be added, so maybe a try/catch?
|
2505
|
-
this.changedDirectories[directory].add(filename);
|
2506
|
-
delete this.data[directory].utf8Files[filename];
|
2507
|
-
delete this.data[directory].symlinks[filename];
|
2508
|
-
this.deletedFiles.push(filePath);
|
2509
|
-
}
|
2510
|
-
fileExists = filePath => {
|
2511
|
-
var _this$data$directory;
|
2512
|
-
if (ts__default["default"].sys.fileExists(filePath)) {
|
2513
|
-
return true;
|
2514
|
-
}
|
2515
|
-
const {
|
2516
|
-
directory,
|
2517
|
-
filename
|
2518
|
-
} = 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
|
2519
|
-
);
|
2520
|
-
return !!((_this$data$directory = this.data[directory]) !== null && _this$data$directory !== void 0 && _this$data$directory.utf8Files[filename]);
|
2521
|
-
};
|
2522
|
-
readFile = filePath => {
|
2523
|
-
const realFile = ts__default["default"].sys.readFile(filePath);
|
2524
|
-
if (realFile !== undefined) {
|
2525
|
-
return realFile;
|
2526
|
-
}
|
2527
|
-
const {
|
2528
|
-
directory,
|
2529
|
-
filename
|
2530
|
-
} = RemoteFS.parsePath(filePath);
|
2531
|
-
const dirNode = this.data[directory];
|
2532
|
-
if (!dirNode) {
|
2533
|
-
return undefined;
|
2534
|
-
}
|
2535
|
-
const content = dirNode.utf8Files[filename];
|
2536
|
-
return content;
|
2537
|
-
};
|
2538
|
-
realpath(fullPath) {
|
2539
|
-
if (ts__default["default"].sys.fileExists(fullPath) && ts__default["default"].sys.realpath) {
|
2540
|
-
return ts__default["default"].sys.realpath(fullPath);
|
2541
|
-
}
|
2542
|
-
// TODO: this only works in a very limited way.
|
2543
|
-
// It does not support symlinks to symlinks nor symlinked directories for instance.
|
2544
|
-
const {
|
2545
|
-
directory,
|
2546
|
-
filename
|
2547
|
-
} = RemoteFS.parsePath(fullPath);
|
2548
|
-
if (this.data[directory] === undefined) {
|
2549
|
-
return fullPath;
|
2550
|
-
}
|
2551
|
-
if (this.data[directory].utf8Files[filename] === undefined) {
|
2552
|
-
const link = this.data[directory].symlinks[filename];
|
2553
|
-
if (link === undefined) {
|
2554
|
-
return fullPath;
|
2555
|
-
} else {
|
2556
|
-
return link;
|
2557
|
-
}
|
2558
|
-
} else {
|
2559
|
-
return path__namespace["default"].join(directory, filename);
|
2560
|
-
}
|
2561
|
-
}
|
2562
|
-
|
2563
|
-
/**
|
2564
|
-
*
|
2565
|
-
* @param path
|
2566
|
-
* @returns directory and filename. NOTE: directory might be empty string
|
2567
|
-
*/
|
2568
|
-
static parsePath(path) {
|
2569
|
-
const pathParts = path.split(SEPARATOR);
|
2570
|
-
const filename = pathParts.pop();
|
2571
|
-
if (!filename) {
|
2572
|
-
throw new Error(`Invalid path: '${path}'. Node filename: '${filename}'`);
|
2573
|
-
}
|
2574
|
-
const directory = pathParts.join(SEPARATOR);
|
2575
|
-
return {
|
2576
|
-
directory,
|
2577
|
-
filename
|
2578
|
-
};
|
2579
|
-
}
|
2580
|
-
}
|
2581
|
-
|
2582
|
-
/**
|
2583
|
-
* Represents directories
|
2584
|
-
* NOTE: the keys of directory nodes are the "full" path, i.e. "foo/bar"
|
2585
|
-
* NOTE: the keys of file nodes are the "filename" only, i.e. "baz.txt"
|
2586
|
-
*
|
2587
|
-
* @example
|
2588
|
-
* {
|
2589
|
-
* "foo/bar": { // <- directory. NOTE: this is the "full" path
|
2590
|
-
* gitHubSha: "123",
|
2591
|
-
* files: {
|
2592
|
-
* "baz.txt": "hello world" // <- file. NOTE: this is the "filename" only
|
2593
|
-
* },
|
2594
|
-
* },
|
2595
|
-
* };
|
2596
|
-
*/
|
2597
|
-
// TODO: a Map would be better here
|
2598
|
-
|
2599
2419
|
class ProxyValServer extends ValServer {
|
2600
|
-
moduleCache =
|
2420
|
+
moduleCache = null;
|
2601
2421
|
constructor(cwd, options, apiOptions, callbacks) {
|
2602
|
-
|
2603
|
-
super(cwd, remoteFS, options, callbacks);
|
2422
|
+
super(cwd, options, callbacks);
|
2604
2423
|
this.cwd = cwd;
|
2605
2424
|
this.options = options;
|
2606
2425
|
this.apiOptions = apiOptions;
|
2607
2426
|
this.callbacks = callbacks;
|
2608
|
-
this.
|
2427
|
+
this.moduleCache = null;
|
2609
2428
|
}
|
2610
2429
|
|
2611
2430
|
/** Remote FS dependent methods: */
|
2612
2431
|
|
2613
|
-
async getModule(moduleId,
|
2614
|
-
|
2615
|
-
|
2616
|
-
if (
|
2617
|
-
return
|
2432
|
+
async getModule(moduleId,
|
2433
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
2434
|
+
_options) {
|
2435
|
+
if (this.moduleCache) {
|
2436
|
+
return this.moduleCache[moduleId];
|
2618
2437
|
}
|
2619
|
-
|
2620
|
-
|
2438
|
+
throw new Error("Module cache not initialized");
|
2439
|
+
}
|
2440
|
+
async getAllModules(treePath) {
|
2441
|
+
if (!this.moduleCache) {
|
2442
|
+
throw new Error("Module cache not initialized");
|
2621
2443
|
}
|
2622
|
-
|
2623
|
-
this.moduleCache[cacheKey] = currentModule;
|
2624
|
-
return currentModule;
|
2444
|
+
return Object.keys(this.moduleCache).filter(moduleId => moduleId.startsWith(treePath));
|
2625
2445
|
}
|
2626
2446
|
execCommit(patches, cookies) {
|
2627
2447
|
return withAuth(this.options.valSecret, cookies, "execCommit", async ({
|
@@ -2636,46 +2456,19 @@ class ProxyValServer extends ValServer {
|
|
2636
2456
|
}
|
2637
2457
|
};
|
2638
2458
|
}
|
2639
|
-
const params =
|
2459
|
+
const params = createParams({
|
2460
|
+
root: this.apiOptions.root,
|
2461
|
+
commit,
|
2462
|
+
ext: ["ts", "js", "json"],
|
2463
|
+
package: ["@valbuild/core@" + this.options.versions.core, "@valbuild/next@" + this.options.versions.next],
|
2464
|
+
include: ["**/*.val.{js,ts},package.json,tsconfig.json,jsconfig.json"]
|
2465
|
+
});
|
2640
2466
|
const url = new URL(`/v1/commit/${this.options.remote}/heads/${this.options.git.branch}/~?${params}`, this.options.valContentUrl);
|
2641
|
-
|
2642
|
-
// Creates a fresh copy of the fs. We cannot touch the existing fs, since there might be parallel operations?
|
2643
|
-
// 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.
|
2644
|
-
// It is a concern we have, since we might be using quite a lot of memory when having the whole FS in memory.
|
2645
|
-
// 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.
|
2646
|
-
// 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.
|
2647
|
-
const remoteFS = new RemoteFS();
|
2648
|
-
const initRes = await this.initRemoteFS(commit, remoteFS, token);
|
2649
|
-
if (initRes.status !== 200) {
|
2650
|
-
return initRes;
|
2651
|
-
}
|
2652
|
-
const service = await createService(this.cwd, this.apiOptions, remoteFS);
|
2653
|
-
// TODO: optimize patches, e.g. only take the last replace for a given thing, etc...
|
2654
|
-
const patchIds = [];
|
2655
|
-
const binaryFileUpdates = {};
|
2656
|
-
for (const [patchId, moduleId, patch] of patches) {
|
2657
|
-
const patchableOps = [];
|
2658
|
-
for (const op of patch) {
|
2659
|
-
if (isCachedPatchFileOp(op)) {
|
2660
|
-
binaryFileUpdates[op.filePath] = op.value;
|
2661
|
-
} else {
|
2662
|
-
if (core.Internal.isFileOp(op)) {
|
2663
|
-
throw new Error(`Val: Unexpected file operation (file: ${op.filePath}). This is likely a Val bug.`);
|
2664
|
-
}
|
2665
|
-
patchableOps.push(op);
|
2666
|
-
}
|
2667
|
-
}
|
2668
|
-
await service.patch(moduleId, patchableOps);
|
2669
|
-
patchIds.push(patchId);
|
2670
|
-
}
|
2671
|
-
const sourceFileUpdates = await remoteFS.getPendingOperations();
|
2467
|
+
const patchIds = patches.map(([patchId]) => patchId);
|
2672
2468
|
const fetchRes = await fetch(url, {
|
2673
2469
|
method: "POST",
|
2674
2470
|
headers: getAuthHeaders(token, "application/json"),
|
2675
2471
|
body: JSON.stringify({
|
2676
|
-
sourceFileUpdates: sourceFileUpdates.modified,
|
2677
|
-
binaryFileUpdates,
|
2678
|
-
deletedFiles: sourceFileUpdates.deleted,
|
2679
2472
|
patchIds
|
2680
2473
|
})
|
2681
2474
|
});
|
@@ -2689,14 +2482,15 @@ class ProxyValServer extends ValServer {
|
|
2689
2482
|
}
|
2690
2483
|
});
|
2691
2484
|
}
|
2692
|
-
async
|
2693
|
-
const params =
|
2485
|
+
async init(commit, token) {
|
2486
|
+
const params = createParams({
|
2694
2487
|
root: this.apiOptions.root,
|
2695
|
-
commit
|
2696
|
-
|
2697
|
-
|
2488
|
+
commit,
|
2489
|
+
ext: ["ts", "js", "json"],
|
2490
|
+
package: ["@valbuild/core@" + this.options.versions.core, "@valbuild/next@" + this.options.versions.next],
|
2491
|
+
include: ["**/*.val.{js,ts},package.json,tsconfig.json,jsconfig.json"]
|
2698
2492
|
});
|
2699
|
-
const url = new URL(`/v1/
|
2493
|
+
const url = new URL(`/v1/eval/${this.options.remote}/heads/${this.options.git.branch}/~?${params}`, this.options.valContentUrl);
|
2700
2494
|
try {
|
2701
2495
|
const fetchRes = await fetch(url, {
|
2702
2496
|
headers: getAuthHeaders(token, "application/json")
|
@@ -2719,22 +2513,22 @@ class ProxyValServer extends ValServer {
|
|
2719
2513
|
details: "Invalid response: missing git.commit"
|
2720
2514
|
};
|
2721
2515
|
}
|
2722
|
-
if (typeof json.
|
2516
|
+
if (typeof json.modules !== "object" || json.modules === null) {
|
2723
2517
|
error = {
|
2724
|
-
details: "Invalid response: missing
|
2518
|
+
details: "Invalid response: missing modules"
|
2725
2519
|
};
|
2726
2520
|
}
|
2727
2521
|
if (error) {
|
2522
|
+
console.error("Could not initialize remote modules", error);
|
2728
2523
|
return {
|
2729
2524
|
status: 500,
|
2730
2525
|
json: {
|
2731
|
-
message: "Failed to fetch remote
|
2526
|
+
message: "Failed to fetch remote modules",
|
2732
2527
|
...error
|
2733
2528
|
}
|
2734
2529
|
};
|
2735
2530
|
}
|
2736
|
-
|
2737
|
-
), content])));
|
2531
|
+
this.moduleCache = json.modules;
|
2738
2532
|
return {
|
2739
2533
|
status: 200
|
2740
2534
|
};
|
@@ -2750,7 +2544,7 @@ class ProxyValServer extends ValServer {
|
|
2750
2544
|
};
|
2751
2545
|
}
|
2752
2546
|
}
|
2753
|
-
async
|
2547
|
+
async ensureInitialized(errorMessageType, cookies) {
|
2754
2548
|
const commit = this.options.git.commit;
|
2755
2549
|
if (!commit) {
|
2756
2550
|
return fp.result.err({
|
@@ -2761,8 +2555,8 @@ class ProxyValServer extends ValServer {
|
|
2761
2555
|
});
|
2762
2556
|
}
|
2763
2557
|
const res = await withAuth(this.options.valSecret, cookies, errorMessageType, async data => {
|
2764
|
-
if (!this.
|
2765
|
-
return this.
|
2558
|
+
if (!this.moduleCache) {
|
2559
|
+
return this.init(commit, data.token);
|
2766
2560
|
} else {
|
2767
2561
|
return {
|
2768
2562
|
status: 200
|
@@ -3042,54 +2836,51 @@ class ProxyValServer extends ValServer {
|
|
3042
2836
|
}
|
3043
2837
|
});
|
3044
2838
|
}
|
3045
|
-
async getFiles(filePath, query,
|
3046
|
-
|
3047
|
-
|
3048
|
-
|
3049
|
-
|
2839
|
+
async getFiles(filePath, query, _cookies, reqHeaders) {
|
2840
|
+
const url = new URL(`/v1/files/${this.options.remote}${filePath}`, this.options.valContentUrl);
|
2841
|
+
if (typeof query.sha256 === "string") {
|
2842
|
+
url.searchParams.append("sha256", query.sha256);
|
2843
|
+
}
|
2844
|
+
const fetchRes = await fetch(url);
|
2845
|
+
if (fetchRes.status === 200) {
|
2846
|
+
// TODO: does this stream data?
|
2847
|
+
if (fetchRes.body) {
|
2848
|
+
return {
|
2849
|
+
status: fetchRes.status,
|
2850
|
+
headers: {
|
2851
|
+
"Content-Type": fetchRes.headers.get("Content-Type") || "",
|
2852
|
+
"Content-Length": fetchRes.headers.get("Content-Length") || "0",
|
2853
|
+
"Cache-Control": fetchRes.headers.get("Cache-Control") || ""
|
2854
|
+
},
|
2855
|
+
body: fetchRes.body
|
2856
|
+
};
|
3050
2857
|
} else {
|
3051
|
-
|
2858
|
+
return {
|
2859
|
+
status: 500,
|
2860
|
+
json: {
|
2861
|
+
message: "No body in response"
|
2862
|
+
}
|
2863
|
+
};
|
3052
2864
|
}
|
3053
|
-
|
3054
|
-
|
3055
|
-
});
|
3056
|
-
if (fetchRes.status === 200) {
|
3057
|
-
// TODO: does this stream data?
|
3058
|
-
if (fetchRes.body) {
|
3059
|
-
return {
|
3060
|
-
status: fetchRes.status,
|
3061
|
-
headers: {
|
3062
|
-
"Content-Type": fetchRes.headers.get("Content-Type") || "",
|
3063
|
-
"Content-Length": fetchRes.headers.get("Content-Length") || "0",
|
3064
|
-
"Cache-Control": "public, max-age=31536000, immutable"
|
3065
|
-
},
|
3066
|
-
body: fetchRes.body
|
3067
|
-
};
|
3068
|
-
} else {
|
3069
|
-
return {
|
3070
|
-
status: 500,
|
3071
|
-
json: {
|
3072
|
-
message: "No body in response"
|
3073
|
-
}
|
3074
|
-
};
|
3075
|
-
}
|
3076
|
-
} else {
|
3077
|
-
if (!(reqHeaders.host && reqHeaders["x-forwarded-proto"])) {
|
3078
|
-
return {
|
3079
|
-
status: 500,
|
3080
|
-
json: {
|
3081
|
-
message: "Missing host or x-forwarded-proto header"
|
3082
|
-
}
|
3083
|
-
};
|
3084
|
-
}
|
3085
|
-
const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
|
3086
|
-
const fileUrl = filePath.slice("/public".length);
|
2865
|
+
} else {
|
2866
|
+
if (!(reqHeaders.host && reqHeaders["x-forwarded-proto"])) {
|
3087
2867
|
return {
|
3088
|
-
status:
|
3089
|
-
|
2868
|
+
status: 500,
|
2869
|
+
json: {
|
2870
|
+
message: "Missing host or x-forwarded-proto header"
|
2871
|
+
}
|
3090
2872
|
};
|
3091
2873
|
}
|
3092
|
-
|
2874
|
+
const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
|
2875
|
+
const staticPublicUrl = new URL(filePath.slice("/public".length), host).toString();
|
2876
|
+
const fetchRes = await fetch(staticPublicUrl);
|
2877
|
+
return {
|
2878
|
+
status: fetchRes.status,
|
2879
|
+
// forward headers from static public url
|
2880
|
+
headers: Object.fromEntries(fetchRes.headers.entries()),
|
2881
|
+
body: fetchRes.body
|
2882
|
+
};
|
2883
|
+
}
|
3093
2884
|
}
|
3094
2885
|
}
|
3095
2886
|
function verifyCallbackReq(stateCookie, queryParams) {
|
@@ -3279,6 +3070,25 @@ function getAuthHeaders(token, type) {
|
|
3279
3070
|
Authorization: `Bearer ${token}`
|
3280
3071
|
};
|
3281
3072
|
}
|
3073
|
+
function createParams(params) {
|
3074
|
+
let paramIdx = 0;
|
3075
|
+
let paramsString = "";
|
3076
|
+
for (const key in params) {
|
3077
|
+
const param = params[key];
|
3078
|
+
if (Array.isArray(param)) {
|
3079
|
+
for (const value of param) {
|
3080
|
+
paramsString += `${key}=${encodeURIComponent(value)}&`;
|
3081
|
+
}
|
3082
|
+
} else if (param) {
|
3083
|
+
paramsString += `${key}=${encodeURIComponent(param)}`;
|
3084
|
+
}
|
3085
|
+
if (paramIdx < Object.keys(params).length - 1) {
|
3086
|
+
paramsString += "&";
|
3087
|
+
}
|
3088
|
+
paramIdx++;
|
3089
|
+
}
|
3090
|
+
return paramsString;
|
3091
|
+
}
|
3282
3092
|
|
3283
3093
|
async function createValServer(route, opts, callbacks) {
|
3284
3094
|
const serverOpts = await initHandlerOptions(route, opts);
|
@@ -3294,6 +3104,7 @@ async function initHandlerOptions(route, opts) {
|
|
3294
3104
|
const maybeValSecret = opts.valSecret || process.env.VAL_SECRET;
|
3295
3105
|
const isProxyMode = opts.mode === "proxy" || opts.mode === undefined && (maybeApiKey || maybeValSecret);
|
3296
3106
|
if (isProxyMode) {
|
3107
|
+
var _opts$versions, _opts$versions2;
|
3297
3108
|
const valBuildUrl = opts.valBuildUrl || process.env.VAL_BUILD_URL || "https://app.val.build";
|
3298
3109
|
if (!maybeApiKey || !maybeValSecret) {
|
3299
3110
|
throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode");
|
@@ -3311,6 +3122,14 @@ async function initHandlerOptions(route, opts) {
|
|
3311
3122
|
if (!maybeValRemote) {
|
3312
3123
|
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.");
|
3313
3124
|
}
|
3125
|
+
const coreVersion = (_opts$versions = opts.versions) === null || _opts$versions === void 0 ? void 0 : _opts$versions.core;
|
3126
|
+
if (!coreVersion) {
|
3127
|
+
throw new Error("Could not determine version of @valbuild/core");
|
3128
|
+
}
|
3129
|
+
const nextVersion = (_opts$versions2 = opts.versions) === null || _opts$versions2 === void 0 ? void 0 : _opts$versions2.next;
|
3130
|
+
if (!nextVersion) {
|
3131
|
+
throw new Error("Could not determine version of @valbuild/next");
|
3132
|
+
}
|
3314
3133
|
return {
|
3315
3134
|
mode: "proxy",
|
3316
3135
|
route,
|
@@ -3318,6 +3137,10 @@ async function initHandlerOptions(route, opts) {
|
|
3318
3137
|
valSecret: maybeValSecret,
|
3319
3138
|
valBuildUrl,
|
3320
3139
|
valContentUrl,
|
3140
|
+
versions: {
|
3141
|
+
core: coreVersion,
|
3142
|
+
next: nextVersion
|
3143
|
+
},
|
3321
3144
|
git: {
|
3322
3145
|
commit: maybeGitCommit,
|
3323
3146
|
branch: maybeGitBranch
|