@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.
@@ -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, valConfigPath, runtime, options) => {
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(path__namespace["default"].dirname(valConfigPath), "<val>"));
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(opts, sourceFileHandler, runtime);
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.valConfigPath = valConfigPath || "./val.config";
1197
+ this.projectRoot = projectRoot;
1202
1198
  }
1203
1199
  async get(moduleId, modulePath, options) {
1204
- const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime, options ?? {
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.valConfigPath, patch, this.sourceFileHandler, this.runtime);
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, host, options, callbacks) {
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.ensureRemoteFSInitialized("getTree", cookies);
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.ensureRemoteFSInitialized("postValidate", cookies);
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.ensureRemoteFSInitialized("postCommit", cookies);
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
- for (const patchId of patchIdsByModuleId[moduleId] ?? []) {
1570
- const patch$1 = patchesById[patchId];
1571
- if (!patch$1) {
1572
- continue;
1573
- }
1574
- const patchRes = patch.applyPatch(source, ops, patch$1.filter(core.Internal.notFileOp));
1575
- if (fp.result.isOk(patchRes)) {
1576
- source = patchRes.value;
1577
- } else {
1578
- console.error("Val: got an unexpected error while applying patch. Is there a mismatch in Val versions? Perhaps Val is misconfigured?", {
1579
- patchId,
1580
- moduleId,
1581
- patch: JSON.stringify(patch$1, null, 2),
1582
- error: patchRes.error
1583
- });
1584
- return {
1585
- path: moduleId,
1586
- schema,
1587
- source,
1588
- errors: {
1589
- fatal: [{
1590
- message: "Unexpected error applying patch",
1591
- type: "invalid-patch"
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.service.sourceFileHandler.host, options, callbacks);
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 ensureRemoteFSInitialized() {
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
- const remoteFS = new RemoteFS();
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.remoteFS = remoteFS;
2427
+ this.moduleCache = null;
2609
2428
  }
2610
2429
 
2611
2430
  /** Remote FS dependent methods: */
2612
2431
 
2613
- async getModule(moduleId, options) {
2614
- const cacheKey = moduleId + ";" + JSON.stringify(options) + this.options.git.commit + this.cwd;
2615
- const cachedModule = this.moduleCache[cacheKey];
2616
- if (cachedModule) {
2617
- return Promise.resolve(cachedModule);
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
- if (!this.lazyService) {
2620
- this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
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
- const currentModule = await this.lazyService.get(moduleId, "", options);
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 = `commit=${encodeURIComponent(commit)}&root=${encodeURIComponent(this.apiOptions.root || "/")}&cwd=${encodeURIComponent(this.cwd)}`;
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 initRemoteFS(commit, remoteFS, token) {
2693
- const params = new URLSearchParams(this.apiOptions.root ? {
2485
+ async init(commit, token) {
2486
+ const params = createParams({
2694
2487
  root: this.apiOptions.root,
2695
- commit
2696
- } : {
2697
- commit
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/fs/${this.options.remote}/heads/${this.options.git.branch}/~?${params}`, this.options.valContentUrl);
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.directories !== "object" || json.directories === null) {
2516
+ if (typeof json.modules !== "object" || json.modules === null) {
2723
2517
  error = {
2724
- details: "Invalid response: missing directories"
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 files",
2526
+ message: "Failed to fetch remote modules",
2732
2527
  ...error
2733
2528
  }
2734
2529
  };
2735
2530
  }
2736
- remoteFS.initializeWith(Object.fromEntries(Object.entries(json.directories).map(([dir, content]) => [path__namespace["default"].join(this.cwd, ...dir.split("/") // content is always posix - not sure that matters...
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 ensureRemoteFSInitialized(errorMessageType, cookies) {
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.remoteFS.isInitialized()) {
2765
- return this.initRemoteFS(commit, this.remoteFS, data.token);
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, cookies, reqHeaders) {
3046
- return withAuth(this.options.valSecret, cookies, "getFiles", async data => {
3047
- const url = new URL(`/v1/files/${this.options.remote}${filePath}`, this.options.valContentUrl);
3048
- if (typeof query.sha256 === "string") {
3049
- url.searchParams.append("sha256", query.sha256);
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
- console.warn("Missing sha256 query param");
2858
+ return {
2859
+ status: 500,
2860
+ json: {
2861
+ message: "No body in response"
2862
+ }
2863
+ };
3052
2864
  }
3053
- const fetchRes = await fetch(url, {
3054
- headers: getAuthHeaders(data.token)
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: 302,
3089
- redirectTo: new URL(fileUrl, host).toString()
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