@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.
- package/dist/declarations/src/LocalValServer.d.ts +22 -0
- package/dist/declarations/src/SerializedModuleContent.d.ts +17 -2
- package/dist/declarations/src/Service.d.ts +2 -1
- package/dist/declarations/src/createFixPatch.d.ts +8 -0
- package/dist/declarations/src/hosting.d.ts +8 -0
- package/dist/declarations/src/index.d.ts +3 -0
- package/dist/declarations/src/patch/validation.d.ts +4 -0
- package/dist/valbuild-server.cjs.dev.js +179 -26
- package/dist/valbuild-server.cjs.prod.js +179 -26
- package/dist/valbuild-server.esm.js +177 -28
- package/package.json +3 -2
- package/src/ProxyValServer.ts +11 -3
- package/src/SerializedModuleContent.ts +34 -7
- package/src/Service.ts +41 -21
- package/src/ValModuleLoader.ts +1 -1
- package/src/createFixPatch.ts +175 -0
- package/src/hosting.ts +13 -0
- package/src/index.ts +3 -0
- package/src/readValFile.ts +39 -21
|
@@ -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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
601
|
+
fatalErrors.push(`Could not find any modules at: ${id}`);
|
|
590
602
|
} else {
|
|
591
603
|
if (valModule.id !== id) {
|
|
592
|
-
|
|
604
|
+
fatalErrors.push(`Expected val id: '${id}' but got: '${valModule.id}'`);
|
|
593
605
|
}
|
|
594
606
|
if (!(valModule !== null && valModule !== void 0 && valModule.schema)) {
|
|
595
|
-
|
|
607
|
+
fatalErrors.push(`Expected val id: '${id}' to have a schema`);
|
|
596
608
|
}
|
|
597
609
|
if (!(valModule !== null && valModule !== void 0 && valModule.source)) {
|
|
598
|
-
|
|
610
|
+
fatalErrors.push(`Expected val id: '${id}' to have a source`);
|
|
599
611
|
}
|
|
600
612
|
}
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
587
|
+
fatalErrors.push(`Could not find any modules at: ${id}`);
|
|
577
588
|
} else {
|
|
578
589
|
if (valModule.id !== id) {
|
|
579
|
-
|
|
590
|
+
fatalErrors.push(`Expected val id: '${id}' but got: '${valModule.id}'`);
|
|
580
591
|
}
|
|
581
592
|
if (!(valModule !== null && valModule !== void 0 && valModule.schema)) {
|
|
582
|
-
|
|
593
|
+
fatalErrors.push(`Expected val id: '${id}' to have a schema`);
|
|
583
594
|
}
|
|
584
595
|
if (!(valModule !== null && valModule !== void 0 && valModule.source)) {
|
|
585
|
-
|
|
596
|
+
fatalErrors.push(`Expected val id: '${id}' to have a source`);
|
|
586
597
|
}
|
|
587
598
|
}
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
+
"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.
|
|
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",
|