@valbuild/server 0.15.0 → 0.16.1

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.
@@ -13,6 +13,7 @@ var express = require('express');
13
13
  var server = require('@valbuild/ui/server');
14
14
  var z = require('zod');
15
15
  var crypto = require('crypto');
16
+ var sizeOf = require('image-size');
16
17
 
17
18
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
18
19
 
@@ -22,6 +23,7 @@ var fs__default = /*#__PURE__*/_interopDefault(fs);
22
23
  var express__default = /*#__PURE__*/_interopDefault(express);
23
24
  var z__default = /*#__PURE__*/_interopDefault(z);
24
25
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
26
+ var sizeOf__default = /*#__PURE__*/_interopDefault(sizeOf);
25
27
 
26
28
  class ValSyntaxError {
27
29
  constructor(message, node) {
@@ -571,41 +573,64 @@ globalThis.valModule = {
571
573
  id: valModule?.default && Internal.getValPath(valModule?.default),
572
574
  schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
573
575
  source: valModule?.default && Internal.getRawSource(valModule?.default),
576
+ validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
577
+ valModule?.default && Internal.getValPath(valModule?.default) || "/",
578
+ valModule?.default && Internal.getRawSource(valModule?.default)
579
+ )
574
580
  };
575
581
  `;
576
582
  const result = context.evalCode(code,
577
583
  // Synthetic module name
578
584
  path__default["default"].join(path__default["default"].dirname(valConfigPath), "<val>"));
585
+ const fatalErrors = [];
579
586
  if (result.error) {
580
587
  const error = result.error.consume(context.dump);
581
- console.error("Got error", error); // TODO: use this to figure out how to strip out QuickJS specific errors and get the actual stack
582
-
583
- throw new Error(`Could not read val id: ${id}. Cause:\n${error.name}: ${error.message}${error.stack ? error.stack : ""}`);
588
+ return {
589
+ errors: {
590
+ invalidModuleId: id,
591
+ fatal: [{
592
+ message: `${error.name || "Unknown error"}: ${error.message || "<no message>"}`,
593
+ stack: error.stack
594
+ }]
595
+ }
596
+ };
584
597
  } else {
585
598
  result.value.dispose();
586
599
  const valModule = context.getProp(context.global, "valModule").consume(context.dump);
587
- const errors = [];
588
600
  if (!valModule) {
589
- errors.push(`Could not find any modules at: ${id}`);
601
+ fatalErrors.push(`Could not find any modules at: ${id}`);
590
602
  } else {
591
603
  if (valModule.id !== id) {
592
- errors.push(`Expected val id: '${id}' but got: '${valModule.id}'`);
604
+ fatalErrors.push(`Expected val id: '${id}' but got: '${valModule.id}'`);
593
605
  }
594
606
  if (!(valModule !== null && valModule !== void 0 && valModule.schema)) {
595
- errors.push(`Expected val id: '${id}' to have a schema`);
607
+ fatalErrors.push(`Expected val id: '${id}' to have a schema`);
596
608
  }
597
609
  if (!(valModule !== null && valModule !== void 0 && valModule.source)) {
598
- errors.push(`Expected val id: '${id}' to have a source`);
610
+ fatalErrors.push(`Expected val id: '${id}' to have a source`);
599
611
  }
600
612
  }
601
- if (errors.length > 0) {
602
- throw Error(`While processing module of id: ${id}, we got the following errors:\n${errors.join("\n")}`);
613
+ let errors = false;
614
+ if (fatalErrors.length > 0) {
615
+ errors = {
616
+ invalidModuleId: valModule.id !== id ? id : undefined,
617
+ fatal: fatalErrors.map(message => ({
618
+ message
619
+ }))
620
+ };
621
+ }
622
+ if (valModule !== null && valModule !== void 0 && valModule.validation) {
623
+ errors = {
624
+ ...(errors ? errors : {}),
625
+ validation: valModule.validation
626
+ };
603
627
  }
604
628
  return {
605
629
  path: valModule.id,
606
- // This might not be the asked id/path, however, that should be handled further up in the call chain
630
+ // NOTE: we use path here, since SerializedModuleContent (maybe bad name?) can be used for whole modules as well as subparts of modules
607
631
  source: valModule.source,
608
- schema: valModule.schema
632
+ schema: valModule.schema,
633
+ errors
609
634
  };
610
635
  }
611
636
  } finally {
@@ -752,7 +777,7 @@ class ValModuleLoader {
752
777
  // allowJs: true,
753
778
  // rootDir: this.compilerOptions.rootDir,
754
779
  module: ts__default["default"].ModuleKind.ESNext,
755
- target: ts__default["default"].ScriptTarget.ES2020 // QuickJS supports a lot of ES2020: https://test262.report/, however not all cases are in that report (e.g. export const {} = {})
780
+ target: ts__default["default"].ScriptTarget.ES2015 // QuickJS supports a lot of ES2020: https://test262.report/, however not all cases are in that report (e.g. export const {} = {})
756
781
  // moduleResolution: ts.ModuleResolutionKind.NodeNext,
757
782
  // target: ts.ScriptTarget.ES2020, // QuickJs runs in ES2020 so we must use that
758
783
  });
@@ -873,12 +898,11 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
873
898
  async function createService(projectRoot, opts, host = {
874
899
  ...ts__default["default"].sys,
875
900
  writeFile: fs__default["default"].writeFileSync
876
- }) {
901
+ }, loader) {
877
902
  const compilerOptions = getCompilerOptions(projectRoot, host);
878
903
  const sourceFileHandler = new ValSourceFileHandler(projectRoot, compilerOptions, host);
879
- const loader = new ValModuleLoader(projectRoot, compilerOptions, sourceFileHandler, host);
880
904
  const module = await quickjsEmscripten.newQuickJSWASMModule();
881
- const runtime = await newValQuickJSRuntime(module, loader);
905
+ const runtime = await newValQuickJSRuntime(module, loader || new ValModuleLoader(projectRoot, compilerOptions, sourceFileHandler, host));
882
906
  return new Service(opts, sourceFileHandler, runtime);
883
907
  }
884
908
  class Service {
@@ -891,12 +915,23 @@ class Service {
891
915
  }
892
916
  async get(moduleId, modulePath) {
893
917
  const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime);
894
- const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
895
- return {
896
- path: [moduleId, resolved.path].join("."),
897
- schema: resolved.schema instanceof core.Schema ? resolved.schema.serialize() : resolved.schema,
898
- source: resolved.source
899
- };
918
+ if (valModule.source && valModule.schema) {
919
+ const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
920
+ const sourcePath = [moduleId, resolved.path].join(".");
921
+ return {
922
+ path: sourcePath,
923
+ schema: resolved.schema instanceof core.Schema ? resolved.schema.serialize() : resolved.schema,
924
+ source: resolved.source,
925
+ errors: valModule.errors && valModule.errors.validation && valModule.errors.validation[sourcePath] ? {
926
+ validation: valModule.errors.validation[sourcePath] ? {
927
+ [sourcePath]: valModule.errors.validation[sourcePath]
928
+ } : undefined,
929
+ fatal: valModule.errors && valModule.errors.fatal ? valModule.errors.fatal : undefined
930
+ } : false
931
+ };
932
+ } else {
933
+ return valModule;
934
+ }
900
935
  }
901
936
  async patch(moduleId, patch) {
902
937
  return patchValFile(moduleId, this.valConfigPath, patch, this.sourceFileHandler, this.runtime);
@@ -1158,8 +1193,9 @@ class ProxyValServer {
1158
1193
  }
1159
1194
  }
1160
1195
  async session(req, res) {
1196
+ console.log("hit session");
1161
1197
  return this.withAuth(req, res, async data => {
1162
- const url = new URL("/api/val/auth/user/session", this.options.valBuildUrl);
1198
+ const url = new URL(`/api/val/${this.options.valName}/auth/session`, this.options.valBuildUrl);
1163
1199
  const fetchRes = await fetch(url, {
1164
1200
  headers: this.getAuthHeaders(data.token, "application/json")
1165
1201
  });
@@ -1262,7 +1298,7 @@ class ProxyValServer {
1262
1298
  };
1263
1299
  }
1264
1300
  async consumeCode(code) {
1265
- const url = new URL("/api/val/auth/user/token", this.options.valBuildUrl);
1301
+ const url = new URL(`/api/val/${this.options.valName}/auth/token`, this.options.valBuildUrl);
1266
1302
  url.searchParams.set("code", encodeURIComponent(code));
1267
1303
  return fetch(url, {
1268
1304
  method: "POST",
@@ -1288,7 +1324,7 @@ class ProxyValServer {
1288
1324
  });
1289
1325
  }
1290
1326
  getAuthorizeUrl(publicValApiRoute, token) {
1291
- const url = new URL("/authorize", this.options.valBuildUrl);
1327
+ const url = new URL(`/auth/${this.options.valName}/authorize`, this.options.valBuildUrl);
1292
1328
  url.searchParams.set("redirect_uri", encodeURIComponent(`${publicValApiRoute}/callback`));
1293
1329
  url.searchParams.set("state", token);
1294
1330
  return url.toString();
@@ -1444,6 +1480,10 @@ async function initHandlerOptions(route, opts) {
1444
1480
  if (!maybeGitBranch) {
1445
1481
  throw new Error("VAL_GIT_BRANCH env var must be set in proxy mode");
1446
1482
  }
1483
+ const maybeValName = opts.gitBranch || process.env.VAL_NAME;
1484
+ if (!maybeValName) {
1485
+ throw new Error("VAL_NAME env var must be set in proxy mode");
1486
+ }
1447
1487
  return {
1448
1488
  mode: "proxy",
1449
1489
  route,
@@ -1451,7 +1491,8 @@ async function initHandlerOptions(route, opts) {
1451
1491
  valSecret: maybeValSecret,
1452
1492
  valBuildUrl,
1453
1493
  gitCommit: maybeGitCommit,
1454
- gitBranch: maybeGitBranch
1494
+ gitBranch: maybeGitBranch,
1495
+ valName: maybeValName
1455
1496
  };
1456
1497
  } else {
1457
1498
  const service = await createService(process.cwd(), opts);
@@ -1514,10 +1555,122 @@ class ValFSHost {
1514
1555
  }
1515
1556
  }
1516
1557
 
1558
+ // TODO: find a better name? transformFixesToPatch?
1559
+ async function createFixPatch(config, apply, sourcePath, validationError) {
1560
+ async function getImageMetadata() {
1561
+ const maybeRef = validationError.value && typeof validationError.value === "object" && core.FILE_REF_PROP in validationError.value && typeof validationError.value[core.FILE_REF_PROP] === "string" ? validationError.value[core.FILE_REF_PROP] : undefined;
1562
+ if (!maybeRef) {
1563
+ // TODO:
1564
+ throw Error("Cannot fix image without a file reference");
1565
+ }
1566
+ const localFile = path__default["default"].join(config.projectRoot, maybeRef);
1567
+ const buffer = fs__default["default"].readFileSync(localFile);
1568
+ const sha256 = await getSHA256Hash(buffer);
1569
+ const imageSize = sizeOf__default["default"](buffer);
1570
+ return {
1571
+ ...imageSize,
1572
+ sha256
1573
+ };
1574
+ }
1575
+ const remainingErrors = [];
1576
+ const patch$1 = [];
1577
+ for (const fix of validationError.fixes || []) {
1578
+ if (fix === "image:replace-metadata" || fix === "image:add-metadata") {
1579
+ const imageMetadata = await getImageMetadata();
1580
+ if (imageMetadata.width === undefined || imageMetadata.height === undefined || imageMetadata.sha256 === undefined) {
1581
+ remainingErrors.push({
1582
+ ...validationError,
1583
+ message: "Failed to get image metadata",
1584
+ fixes: undefined
1585
+ });
1586
+ } else if (fix === "image:replace-metadata") {
1587
+ const currentValue = validationError.value;
1588
+ const metadataIsCorrect =
1589
+ // metadata is a prop that is an object
1590
+ typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object" &&
1591
+ // sha256 is correct
1592
+ "sha256" in currentValue.metadata && currentValue.metadata.sha256 === imageMetadata.sha256 &&
1593
+ // width is correct
1594
+ "width" in currentValue.metadata && currentValue.metadata.width === imageMetadata.width &&
1595
+ // height is correct
1596
+ "height" in currentValue.metadata && currentValue.metadata.height === imageMetadata.height;
1597
+
1598
+ // skips if the metadata is already correct
1599
+ if (!metadataIsCorrect) {
1600
+ if (apply) {
1601
+ patch$1.push({
1602
+ op: "replace",
1603
+ path: patch.sourceToPatchPath(sourcePath).concat("metadata"),
1604
+ value: {
1605
+ width: imageMetadata.width,
1606
+ height: imageMetadata.height,
1607
+ sha256: imageMetadata.sha256
1608
+ }
1609
+ });
1610
+ } else {
1611
+ if (typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object") {
1612
+ if (!("sha256" in currentValue.metadata) || currentValue.metadata.sha256 !== imageMetadata.sha256) {
1613
+ remainingErrors.push({
1614
+ message: "Image metadata sha256 is incorrect! Found: " + ("sha256" in currentValue.metadata ? currentValue.metadata.sha256 : "<empty>") + ". Expected: " + imageMetadata.sha256 + ".",
1615
+ fixes: undefined
1616
+ });
1617
+ }
1618
+ if (!("width" in currentValue.metadata) || currentValue.metadata.width !== imageMetadata.width) {
1619
+ remainingErrors.push({
1620
+ message: "Image metadata width is incorrect! Found: " + ("width" in currentValue.metadata ? currentValue.metadata.width : "<empty>") + ". Expected: " + imageMetadata.width,
1621
+ fixes: undefined
1622
+ });
1623
+ }
1624
+ if (!("height" in currentValue.metadata) || currentValue.metadata.height !== imageMetadata.height) {
1625
+ remainingErrors.push({
1626
+ message: "Image metadata height is incorrect! Found: " + ("height" in currentValue.metadata ? currentValue.metadata.height : "<empty>") + ". Expected: " + imageMetadata.height,
1627
+ fixes: undefined
1628
+ });
1629
+ }
1630
+ } else {
1631
+ remainingErrors.push({
1632
+ ...validationError,
1633
+ message: "Image metadata is not an object!",
1634
+ fixes: undefined
1635
+ });
1636
+ }
1637
+ }
1638
+ }
1639
+ } else if (fix === "image:add-metadata") {
1640
+ patch$1.push({
1641
+ op: "add",
1642
+ path: patch.sourceToPatchPath(sourcePath).concat("metadata"),
1643
+ value: {
1644
+ width: imageMetadata.width,
1645
+ height: imageMetadata.height,
1646
+ sha256: imageMetadata.sha256
1647
+ }
1648
+ });
1649
+ }
1650
+ }
1651
+ }
1652
+ if (!validationError.fixes || validationError.fixes.length === 0) {
1653
+ remainingErrors.push(validationError);
1654
+ }
1655
+ return {
1656
+ patch: patch$1,
1657
+ remainingErrors
1658
+ };
1659
+ }
1660
+ const getSHA256Hash = async bits => {
1661
+ const hashBuffer = await crypto__default["default"].subtle.digest("SHA-256", bits);
1662
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
1663
+ const hash = hashArray.map(item => item.toString(16).padStart(2, "0")).join("");
1664
+ return hash;
1665
+ };
1666
+
1667
+ exports.LocalValServer = LocalValServer;
1668
+ exports.PatchJSON = PatchJSON;
1517
1669
  exports.Service = Service;
1518
1670
  exports.ValFSHost = ValFSHost;
1519
1671
  exports.ValModuleLoader = ValModuleLoader;
1520
1672
  exports.ValSourceFileHandler = ValSourceFileHandler;
1673
+ exports.createFixPatch = createFixPatch;
1521
1674
  exports.createRequestHandler = createRequestHandler;
1522
1675
  exports.createRequestListener = createRequestListener;
1523
1676
  exports.createService = createService;
@@ -2,13 +2,14 @@ import { newQuickJSWASMModule } from 'quickjs-emscripten';
2
2
  import ts from 'typescript';
3
3
  import { result, pipe } from '@valbuild/core/fp';
4
4
  import { FILE_REF_PROP, derefPatch, Internal, Schema } from '@valbuild/core';
5
- import { deepEqual, isNotRoot, PatchError, parseAndValidateArrayIndex, applyPatch, parsePatch } from '@valbuild/core/patch';
5
+ import { deepEqual, isNotRoot, PatchError, parseAndValidateArrayIndex, applyPatch, parsePatch, sourceToPatchPath } from '@valbuild/core/patch';
6
6
  import path from 'path';
7
7
  import fs from 'fs';
8
8
  import express, { Router } from 'express';
9
9
  import { createRequestHandler as createRequestHandler$1 } from '@valbuild/ui/server';
10
10
  import z, { z as z$1 } from 'zod';
11
11
  import crypto from 'crypto';
12
+ import sizeOf from 'image-size';
12
13
 
13
14
  class ValSyntaxError {
14
15
  constructor(message, node) {
@@ -558,41 +559,64 @@ globalThis.valModule = {
558
559
  id: valModule?.default && Internal.getValPath(valModule?.default),
559
560
  schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
560
561
  source: valModule?.default && Internal.getRawSource(valModule?.default),
562
+ validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
563
+ valModule?.default && Internal.getValPath(valModule?.default) || "/",
564
+ valModule?.default && Internal.getRawSource(valModule?.default)
565
+ )
561
566
  };
562
567
  `;
563
568
  const result = context.evalCode(code,
564
569
  // Synthetic module name
565
570
  path.join(path.dirname(valConfigPath), "<val>"));
571
+ const fatalErrors = [];
566
572
  if (result.error) {
567
573
  const error = result.error.consume(context.dump);
568
- console.error("Got error", error); // TODO: use this to figure out how to strip out QuickJS specific errors and get the actual stack
569
-
570
- throw new Error(`Could not read val id: ${id}. Cause:\n${error.name}: ${error.message}${error.stack ? error.stack : ""}`);
574
+ return {
575
+ errors: {
576
+ invalidModuleId: id,
577
+ fatal: [{
578
+ message: `${error.name || "Unknown error"}: ${error.message || "<no message>"}`,
579
+ stack: error.stack
580
+ }]
581
+ }
582
+ };
571
583
  } else {
572
584
  result.value.dispose();
573
585
  const valModule = context.getProp(context.global, "valModule").consume(context.dump);
574
- const errors = [];
575
586
  if (!valModule) {
576
- errors.push(`Could not find any modules at: ${id}`);
587
+ fatalErrors.push(`Could not find any modules at: ${id}`);
577
588
  } else {
578
589
  if (valModule.id !== id) {
579
- errors.push(`Expected val id: '${id}' but got: '${valModule.id}'`);
590
+ fatalErrors.push(`Expected val id: '${id}' but got: '${valModule.id}'`);
580
591
  }
581
592
  if (!(valModule !== null && valModule !== void 0 && valModule.schema)) {
582
- errors.push(`Expected val id: '${id}' to have a schema`);
593
+ fatalErrors.push(`Expected val id: '${id}' to have a schema`);
583
594
  }
584
595
  if (!(valModule !== null && valModule !== void 0 && valModule.source)) {
585
- errors.push(`Expected val id: '${id}' to have a source`);
596
+ fatalErrors.push(`Expected val id: '${id}' to have a source`);
586
597
  }
587
598
  }
588
- if (errors.length > 0) {
589
- throw Error(`While processing module of id: ${id}, we got the following errors:\n${errors.join("\n")}`);
599
+ let errors = false;
600
+ if (fatalErrors.length > 0) {
601
+ errors = {
602
+ invalidModuleId: valModule.id !== id ? id : undefined,
603
+ fatal: fatalErrors.map(message => ({
604
+ message
605
+ }))
606
+ };
607
+ }
608
+ if (valModule !== null && valModule !== void 0 && valModule.validation) {
609
+ errors = {
610
+ ...(errors ? errors : {}),
611
+ validation: valModule.validation
612
+ };
590
613
  }
591
614
  return {
592
615
  path: valModule.id,
593
- // This might not be the asked id/path, however, that should be handled further up in the call chain
616
+ // NOTE: we use path here, since SerializedModuleContent (maybe bad name?) can be used for whole modules as well as subparts of modules
594
617
  source: valModule.source,
595
- schema: valModule.schema
618
+ schema: valModule.schema,
619
+ errors
596
620
  };
597
621
  }
598
622
  } finally {
@@ -739,7 +763,7 @@ class ValModuleLoader {
739
763
  // allowJs: true,
740
764
  // rootDir: this.compilerOptions.rootDir,
741
765
  module: ts.ModuleKind.ESNext,
742
- target: ts.ScriptTarget.ES2020 // QuickJS supports a lot of ES2020: https://test262.report/, however not all cases are in that report (e.g. export const {} = {})
766
+ target: ts.ScriptTarget.ES2015 // QuickJS supports a lot of ES2020: https://test262.report/, however not all cases are in that report (e.g. export const {} = {})
743
767
  // moduleResolution: ts.ModuleResolutionKind.NodeNext,
744
768
  // target: ts.ScriptTarget.ES2020, // QuickJs runs in ES2020 so we must use that
745
769
  });
@@ -860,12 +884,11 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
860
884
  async function createService(projectRoot, opts, host = {
861
885
  ...ts.sys,
862
886
  writeFile: fs.writeFileSync
863
- }) {
887
+ }, loader) {
864
888
  const compilerOptions = getCompilerOptions(projectRoot, host);
865
889
  const sourceFileHandler = new ValSourceFileHandler(projectRoot, compilerOptions, host);
866
- const loader = new ValModuleLoader(projectRoot, compilerOptions, sourceFileHandler, host);
867
890
  const module = await newQuickJSWASMModule();
868
- const runtime = await newValQuickJSRuntime(module, loader);
891
+ const runtime = await newValQuickJSRuntime(module, loader || new ValModuleLoader(projectRoot, compilerOptions, sourceFileHandler, host));
869
892
  return new Service(opts, sourceFileHandler, runtime);
870
893
  }
871
894
  class Service {
@@ -878,12 +901,23 @@ class Service {
878
901
  }
879
902
  async get(moduleId, modulePath) {
880
903
  const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime);
881
- const resolved = Internal.resolvePath(modulePath, valModule.source, valModule.schema);
882
- return {
883
- path: [moduleId, resolved.path].join("."),
884
- schema: resolved.schema instanceof Schema ? resolved.schema.serialize() : resolved.schema,
885
- source: resolved.source
886
- };
904
+ if (valModule.source && valModule.schema) {
905
+ const resolved = Internal.resolvePath(modulePath, valModule.source, valModule.schema);
906
+ const sourcePath = [moduleId, resolved.path].join(".");
907
+ return {
908
+ path: sourcePath,
909
+ schema: resolved.schema instanceof Schema ? resolved.schema.serialize() : resolved.schema,
910
+ source: resolved.source,
911
+ errors: valModule.errors && valModule.errors.validation && valModule.errors.validation[sourcePath] ? {
912
+ validation: valModule.errors.validation[sourcePath] ? {
913
+ [sourcePath]: valModule.errors.validation[sourcePath]
914
+ } : undefined,
915
+ fatal: valModule.errors && valModule.errors.fatal ? valModule.errors.fatal : undefined
916
+ } : false
917
+ };
918
+ } else {
919
+ return valModule;
920
+ }
887
921
  }
888
922
  async patch(moduleId, patch) {
889
923
  return patchValFile(moduleId, this.valConfigPath, patch, this.sourceFileHandler, this.runtime);
@@ -1145,8 +1179,9 @@ class ProxyValServer {
1145
1179
  }
1146
1180
  }
1147
1181
  async session(req, res) {
1182
+ console.log("hit session");
1148
1183
  return this.withAuth(req, res, async data => {
1149
- const url = new URL("/api/val/auth/user/session", this.options.valBuildUrl);
1184
+ const url = new URL(`/api/val/${this.options.valName}/auth/session`, this.options.valBuildUrl);
1150
1185
  const fetchRes = await fetch(url, {
1151
1186
  headers: this.getAuthHeaders(data.token, "application/json")
1152
1187
  });
@@ -1249,7 +1284,7 @@ class ProxyValServer {
1249
1284
  };
1250
1285
  }
1251
1286
  async consumeCode(code) {
1252
- const url = new URL("/api/val/auth/user/token", this.options.valBuildUrl);
1287
+ const url = new URL(`/api/val/${this.options.valName}/auth/token`, this.options.valBuildUrl);
1253
1288
  url.searchParams.set("code", encodeURIComponent(code));
1254
1289
  return fetch(url, {
1255
1290
  method: "POST",
@@ -1275,7 +1310,7 @@ class ProxyValServer {
1275
1310
  });
1276
1311
  }
1277
1312
  getAuthorizeUrl(publicValApiRoute, token) {
1278
- const url = new URL("/authorize", this.options.valBuildUrl);
1313
+ const url = new URL(`/auth/${this.options.valName}/authorize`, this.options.valBuildUrl);
1279
1314
  url.searchParams.set("redirect_uri", encodeURIComponent(`${publicValApiRoute}/callback`));
1280
1315
  url.searchParams.set("state", token);
1281
1316
  return url.toString();
@@ -1431,6 +1466,10 @@ async function initHandlerOptions(route, opts) {
1431
1466
  if (!maybeGitBranch) {
1432
1467
  throw new Error("VAL_GIT_BRANCH env var must be set in proxy mode");
1433
1468
  }
1469
+ const maybeValName = opts.gitBranch || process.env.VAL_NAME;
1470
+ if (!maybeValName) {
1471
+ throw new Error("VAL_NAME env var must be set in proxy mode");
1472
+ }
1434
1473
  return {
1435
1474
  mode: "proxy",
1436
1475
  route,
@@ -1438,7 +1477,8 @@ async function initHandlerOptions(route, opts) {
1438
1477
  valSecret: maybeValSecret,
1439
1478
  valBuildUrl,
1440
1479
  gitCommit: maybeGitCommit,
1441
- gitBranch: maybeGitBranch
1480
+ gitBranch: maybeGitBranch,
1481
+ valName: maybeValName
1442
1482
  };
1443
1483
  } else {
1444
1484
  const service = await createService(process.cwd(), opts);
@@ -1501,4 +1541,113 @@ class ValFSHost {
1501
1541
  }
1502
1542
  }
1503
1543
 
1504
- export { Service, ValFSHost, ValModuleLoader, ValSourceFileHandler, createRequestHandler, createRequestListener, createService, formatSyntaxErrorTree, getCompilerOptions, patchSourceFile };
1544
+ // TODO: find a better name? transformFixesToPatch?
1545
+ async function createFixPatch(config, apply, sourcePath, validationError) {
1546
+ async function getImageMetadata() {
1547
+ const maybeRef = validationError.value && typeof validationError.value === "object" && FILE_REF_PROP in validationError.value && typeof validationError.value[FILE_REF_PROP] === "string" ? validationError.value[FILE_REF_PROP] : undefined;
1548
+ if (!maybeRef) {
1549
+ // TODO:
1550
+ throw Error("Cannot fix image without a file reference");
1551
+ }
1552
+ const localFile = path.join(config.projectRoot, maybeRef);
1553
+ const buffer = fs.readFileSync(localFile);
1554
+ const sha256 = await getSHA256Hash(buffer);
1555
+ const imageSize = sizeOf(buffer);
1556
+ return {
1557
+ ...imageSize,
1558
+ sha256
1559
+ };
1560
+ }
1561
+ const remainingErrors = [];
1562
+ const patch = [];
1563
+ for (const fix of validationError.fixes || []) {
1564
+ if (fix === "image:replace-metadata" || fix === "image:add-metadata") {
1565
+ const imageMetadata = await getImageMetadata();
1566
+ if (imageMetadata.width === undefined || imageMetadata.height === undefined || imageMetadata.sha256 === undefined) {
1567
+ remainingErrors.push({
1568
+ ...validationError,
1569
+ message: "Failed to get image metadata",
1570
+ fixes: undefined
1571
+ });
1572
+ } else if (fix === "image:replace-metadata") {
1573
+ const currentValue = validationError.value;
1574
+ const metadataIsCorrect =
1575
+ // metadata is a prop that is an object
1576
+ typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object" &&
1577
+ // sha256 is correct
1578
+ "sha256" in currentValue.metadata && currentValue.metadata.sha256 === imageMetadata.sha256 &&
1579
+ // width is correct
1580
+ "width" in currentValue.metadata && currentValue.metadata.width === imageMetadata.width &&
1581
+ // height is correct
1582
+ "height" in currentValue.metadata && currentValue.metadata.height === imageMetadata.height;
1583
+
1584
+ // skips if the metadata is already correct
1585
+ if (!metadataIsCorrect) {
1586
+ if (apply) {
1587
+ patch.push({
1588
+ op: "replace",
1589
+ path: sourceToPatchPath(sourcePath).concat("metadata"),
1590
+ value: {
1591
+ width: imageMetadata.width,
1592
+ height: imageMetadata.height,
1593
+ sha256: imageMetadata.sha256
1594
+ }
1595
+ });
1596
+ } else {
1597
+ if (typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object") {
1598
+ if (!("sha256" in currentValue.metadata) || currentValue.metadata.sha256 !== imageMetadata.sha256) {
1599
+ remainingErrors.push({
1600
+ message: "Image metadata sha256 is incorrect! Found: " + ("sha256" in currentValue.metadata ? currentValue.metadata.sha256 : "<empty>") + ". Expected: " + imageMetadata.sha256 + ".",
1601
+ fixes: undefined
1602
+ });
1603
+ }
1604
+ if (!("width" in currentValue.metadata) || currentValue.metadata.width !== imageMetadata.width) {
1605
+ remainingErrors.push({
1606
+ message: "Image metadata width is incorrect! Found: " + ("width" in currentValue.metadata ? currentValue.metadata.width : "<empty>") + ". Expected: " + imageMetadata.width,
1607
+ fixes: undefined
1608
+ });
1609
+ }
1610
+ if (!("height" in currentValue.metadata) || currentValue.metadata.height !== imageMetadata.height) {
1611
+ remainingErrors.push({
1612
+ message: "Image metadata height is incorrect! Found: " + ("height" in currentValue.metadata ? currentValue.metadata.height : "<empty>") + ". Expected: " + imageMetadata.height,
1613
+ fixes: undefined
1614
+ });
1615
+ }
1616
+ } else {
1617
+ remainingErrors.push({
1618
+ ...validationError,
1619
+ message: "Image metadata is not an object!",
1620
+ fixes: undefined
1621
+ });
1622
+ }
1623
+ }
1624
+ }
1625
+ } else if (fix === "image:add-metadata") {
1626
+ patch.push({
1627
+ op: "add",
1628
+ path: sourceToPatchPath(sourcePath).concat("metadata"),
1629
+ value: {
1630
+ width: imageMetadata.width,
1631
+ height: imageMetadata.height,
1632
+ sha256: imageMetadata.sha256
1633
+ }
1634
+ });
1635
+ }
1636
+ }
1637
+ }
1638
+ if (!validationError.fixes || validationError.fixes.length === 0) {
1639
+ remainingErrors.push(validationError);
1640
+ }
1641
+ return {
1642
+ patch,
1643
+ remainingErrors
1644
+ };
1645
+ }
1646
+ const getSHA256Hash = async bits => {
1647
+ const hashBuffer = await crypto.subtle.digest("SHA-256", bits);
1648
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
1649
+ const hash = hashArray.map(item => item.toString(16).padStart(2, "0")).join("");
1650
+ return hash;
1651
+ };
1652
+
1653
+ export { LocalValServer, PatchJSON, Service, ValFSHost, ValModuleLoader, ValSourceFileHandler, createFixPatch, createRequestHandler, createRequestListener, createService, formatSyntaxErrorTree, getCompilerOptions, patchSourceFile };
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "./package.json": "./package.json"
13
13
  },
14
14
  "types": "dist/valbuild-server.cjs.d.ts",
15
- "version": "0.15.0",
15
+ "version": "0.16.1",
16
16
  "scripts": {
17
17
  "typecheck": "tsc --noEmit",
18
18
  "test": "jest",
@@ -25,9 +25,10 @@
25
25
  "concurrently": "^7.6.0"
26
26
  },
27
27
  "dependencies": {
28
- "@valbuild/core": "~0.15.0",
28
+ "@valbuild/core": "~0.16.0",
29
29
  "@valbuild/ui": "~0.15.0",
30
30
  "express": "^4.18.2",
31
+ "image-size": "^1.0.2",
31
32
  "quickjs-emscripten": "^0.21.1",
32
33
  "ts-morph": "^17.0.1",
33
34
  "typescript": "^4.9.4",