@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.
@@ -0,0 +1,22 @@
1
+ import express from "express";
2
+ import { Service } from "./Service.js";
3
+ import { ValServer } from "./ValServer.js";
4
+ export type LocalValServerOptions = {
5
+ service: Service;
6
+ };
7
+ export declare class LocalValServer implements ValServer {
8
+ readonly options: LocalValServerOptions;
9
+ constructor(options: LocalValServerOptions);
10
+ session(_req: express.Request, res: express.Response): Promise<void>;
11
+ getIds(req: express.Request<{
12
+ 0: string;
13
+ }>, res: express.Response): Promise<void>;
14
+ patchIds(req: express.Request<{
15
+ 0: string;
16
+ }>, res: express.Response): Promise<void>;
17
+ private badRequest;
18
+ commit(req: express.Request, res: express.Response): Promise<void>;
19
+ authorize(req: express.Request, res: express.Response): Promise<void>;
20
+ callback(req: express.Request, res: express.Response): Promise<void>;
21
+ logout(req: express.Request, res: express.Response): Promise<void>;
22
+ }
@@ -1,7 +1,22 @@
1
- import { type Source, type SerializedSchema } from "@valbuild/core";
2
- import { type SourcePath } from "@valbuild/core/src/val";
1
+ import { type Source, type SerializedSchema, ValidationErrors } from "@valbuild/core";
2
+ import { ModuleId, type SourcePath } from "@valbuild/core/src/val";
3
+ export declare const FATAL_ERROR_TYPES: readonly ["no-schema", "no-source", "invalid-id", "no-module"];
3
4
  export type SerializedModuleContent = {
4
5
  source: Source;
5
6
  schema: SerializedSchema;
6
7
  path: SourcePath;
8
+ errors: false;
9
+ } | {
10
+ source?: Source;
11
+ schema?: SerializedSchema;
12
+ path?: SourcePath;
13
+ errors: {
14
+ invalidModuleId?: ModuleId;
15
+ validation?: ValidationErrors;
16
+ fatal?: {
17
+ message: string;
18
+ stack?: string[];
19
+ type?: (typeof FATAL_ERROR_TYPES)[number];
20
+ }[];
21
+ };
7
22
  };
@@ -1,5 +1,6 @@
1
1
  import { QuickJSRuntime } from "quickjs-emscripten";
2
2
  import { Patch } from "@valbuild/core/patch";
3
+ import { ValModuleLoader } from "./ValModuleLoader.js";
3
4
  import { ValSourceFileHandler } from "./ValSourceFileHandler.js";
4
5
  import { IValFSHost } from "./ValFSHost.js";
5
6
  import { SerializedModuleContent } from "./SerializedModuleContent.js";
@@ -12,7 +13,7 @@ export type ServiceOptions = {
12
13
  */
13
14
  valConfigPath: string;
14
15
  };
15
- export declare function createService(projectRoot: string, opts: ServiceOptions, host?: IValFSHost): Promise<Service>;
16
+ export declare function createService(projectRoot: string, opts: ServiceOptions, host?: IValFSHost, loader?: ValModuleLoader): Promise<Service>;
16
17
  export declare class Service {
17
18
  private readonly sourceFileHandler;
18
19
  private readonly runtime;
@@ -0,0 +1,8 @@
1
+ import { SourcePath, ValidationError } from "@valbuild/core";
2
+ import { Patch } from "@valbuild/core/patch";
3
+ export declare function createFixPatch(config: {
4
+ projectRoot: string;
5
+ }, apply: boolean, sourcePath: SourcePath, validationError: ValidationError): Promise<{
6
+ patch: Patch;
7
+ remainingErrors: ValidationError[];
8
+ } | undefined>;
@@ -66,6 +66,14 @@ type ValServerOverrides = Partial<{
66
66
  * @example "https://app.val.build"
67
67
  */
68
68
  valBuildUrl: string;
69
+ /**
70
+ * The full name of this Val project.
71
+ *
72
+ * Typically this is set using the VAL_NAME env var.
73
+ *
74
+ * @example "myorg/my-project"
75
+ */
76
+ valName: string;
69
77
  }>;
70
78
  export declare function createRequestListener(route: string, opts: Opts): RequestListener;
71
79
  export {};
@@ -10,3 +10,6 @@ export type { IValFSHost } from "./ValFSHost.js";
10
10
  export type { ValFS } from "./ValFS.js";
11
11
  export { patchSourceFile } from "./patchValFile.js";
12
12
  export { formatSyntaxErrorTree } from "./patch/ts/syntax.js";
13
+ export { LocalValServer } from "./LocalValServer.js";
14
+ export { createFixPatch } from "./createFixPatch.js";
15
+ export { PatchJSON } from "./patch/validation.js";
@@ -0,0 +1,4 @@
1
+ import type { PatchJSON as PatchJSONT } from "@valbuild/core/patch";
2
+ import z from "zod";
3
+ export declare const PatchJSON: z.ZodType<PatchJSONT>;
4
+ export type PatchJSON = PatchJSONT;
@@ -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;