@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.
@@ -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, valConfigPath, runtime, options) => {
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(path__default.dirname(valConfigPath), "<val>"));
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(opts, sourceFileHandler, runtime);
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.valConfigPath = valConfigPath || "./val.config";
1167
+ this.projectRoot = projectRoot;
1171
1168
  }
1172
1169
  async get(moduleId, modulePath, options) {
1173
- const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime, options ?? {
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.valConfigPath, patch, this.sourceFileHandler, this.runtime);
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, host, options, callbacks) {
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.ensureRemoteFSInitialized("getTree", cookies);
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.ensureRemoteFSInitialized("postValidate", cookies);
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.ensureRemoteFSInitialized("postCommit", cookies);
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
- for (const patchId of patchIdsByModuleId[moduleId] ?? []) {
1539
- const patch = patchesById[patchId];
1540
- if (!patch) {
1541
- continue;
1542
- }
1543
- const patchRes = applyPatch(source, ops, patch.filter(Internal.notFileOp));
1544
- if (result.isOk(patchRes)) {
1545
- source = patchRes.value;
1546
- } else {
1547
- console.error("Val: got an unexpected error while applying patch. Is there a mismatch in Val versions? Perhaps Val is misconfigured?", {
1548
- patchId,
1549
- moduleId,
1550
- patch: JSON.stringify(patch, null, 2),
1551
- error: patchRes.error
1552
- });
1553
- return {
1554
- path: moduleId,
1555
- schema,
1556
- source,
1557
- errors: {
1558
- fatal: [{
1559
- message: "Unexpected error applying patch",
1560
- type: "invalid-patch"
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.service.sourceFileHandler.host, options, callbacks);
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 ensureRemoteFSInitialized() {
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
- const remoteFS = new RemoteFS();
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.remoteFS = remoteFS;
2397
+ this.moduleCache = null;
2578
2398
  }
2579
2399
 
2580
2400
  /** Remote FS dependent methods: */
2581
2401
 
2582
- async getModule(moduleId, options) {
2583
- const cacheKey = moduleId + ";" + JSON.stringify(options) + this.options.git.commit + this.cwd;
2584
- const cachedModule = this.moduleCache[cacheKey];
2585
- if (cachedModule) {
2586
- return Promise.resolve(cachedModule);
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
- if (!this.lazyService) {
2589
- this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
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
- const currentModule = await this.lazyService.get(moduleId, "", options);
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 = `commit=${encodeURIComponent(commit)}&root=${encodeURIComponent(this.apiOptions.root || "/")}&cwd=${encodeURIComponent(this.cwd)}`;
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 initRemoteFS(commit, remoteFS, token) {
2662
- const params = new URLSearchParams(this.apiOptions.root ? {
2455
+ async init(commit, token) {
2456
+ const params = createParams({
2663
2457
  root: this.apiOptions.root,
2664
- commit
2665
- } : {
2666
- commit
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/fs/${this.options.remote}/heads/${this.options.git.branch}/~?${params}`, this.options.valContentUrl);
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.directories !== "object" || json.directories === null) {
2486
+ if (typeof json.modules !== "object" || json.modules === null) {
2692
2487
  error = {
2693
- details: "Invalid response: missing directories"
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 files",
2496
+ message: "Failed to fetch remote modules",
2701
2497
  ...error
2702
2498
  }
2703
2499
  };
2704
2500
  }
2705
- remoteFS.initializeWith(Object.fromEntries(Object.entries(json.directories).map(([dir, content]) => [path__default.join(this.cwd, ...dir.split("/") // content is always posix - not sure that matters...
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 ensureRemoteFSInitialized(errorMessageType, cookies) {
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.remoteFS.isInitialized()) {
2734
- return this.initRemoteFS(commit, this.remoteFS, data.token);
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, cookies, reqHeaders) {
3015
- return withAuth(this.options.valSecret, cookies, "getFiles", async data => {
3016
- const url = new URL(`/v1/files/${this.options.remote}${filePath}`, this.options.valContentUrl);
3017
- if (typeof query.sha256 === "string") {
3018
- url.searchParams.append("sha256", query.sha256);
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
- console.warn("Missing sha256 query param");
2828
+ return {
2829
+ status: 500,
2830
+ json: {
2831
+ message: "No body in response"
2832
+ }
2833
+ };
3021
2834
  }
3022
- const fetchRes = await fetch(url, {
3023
- headers: getAuthHeaders(data.token)
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: 302,
3058
- redirectTo: new URL(fileUrl, host).toString()
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