@valbuild/server 0.14.0 → 0.16.0
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/index.d.ts +2 -0
- package/dist/valbuild-server.cjs.dev.js +167 -22
- package/dist/valbuild-server.cjs.prod.js +167 -22
- package/dist/valbuild-server.esm.js +166 -24
- package/package.json +4 -3
- package/src/SerializedModuleContent.ts +34 -7
- package/src/Service.ts +41 -21
- package/src/ValModuleLoader.ts +1 -1
- package/src/createFixPatch.ts +174 -0
- package/src/index.ts +2 -0
- package/src/readValFile.ts +39 -21
|
@@ -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>;
|
|
@@ -10,3 +10,5 @@ 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";
|
|
@@ -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);
|
|
@@ -1514,10 +1549,120 @@ class ValFSHost {
|
|
|
1514
1549
|
}
|
|
1515
1550
|
}
|
|
1516
1551
|
|
|
1552
|
+
async function createFixPatch(config, apply, sourcePath, validationError) {
|
|
1553
|
+
async function getImageMetadata() {
|
|
1554
|
+
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;
|
|
1555
|
+
if (!maybeRef) {
|
|
1556
|
+
// TODO:
|
|
1557
|
+
throw Error("Cannot fix image without a file reference");
|
|
1558
|
+
}
|
|
1559
|
+
const localFile = path__default["default"].join(config.projectRoot, maybeRef);
|
|
1560
|
+
const buffer = fs__default["default"].readFileSync(localFile);
|
|
1561
|
+
const sha256 = await getSHA256Hash(buffer);
|
|
1562
|
+
const imageSize = sizeOf__default["default"](buffer);
|
|
1563
|
+
return {
|
|
1564
|
+
...imageSize,
|
|
1565
|
+
sha256
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
const remainingErrors = [];
|
|
1569
|
+
const patch$1 = [];
|
|
1570
|
+
for (const fix of validationError.fixes || []) {
|
|
1571
|
+
if (fix === "image:replace-metadata" || fix === "image:add-metadata") {
|
|
1572
|
+
const imageMetadata = await getImageMetadata();
|
|
1573
|
+
if (imageMetadata.width === undefined || imageMetadata.height === undefined || imageMetadata.sha256 === undefined) {
|
|
1574
|
+
remainingErrors.push({
|
|
1575
|
+
...validationError,
|
|
1576
|
+
message: "Failed to get image metadata",
|
|
1577
|
+
fixes: undefined
|
|
1578
|
+
});
|
|
1579
|
+
} else if (fix === "image:replace-metadata") {
|
|
1580
|
+
const currentValue = validationError.value;
|
|
1581
|
+
const metadataIsCorrect =
|
|
1582
|
+
// metadata is a prop that is an object
|
|
1583
|
+
typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object" &&
|
|
1584
|
+
// sha256 is correct
|
|
1585
|
+
"sha256" in currentValue.metadata && currentValue.metadata.sha256 === imageMetadata.sha256 &&
|
|
1586
|
+
// width is correct
|
|
1587
|
+
"width" in currentValue.metadata && currentValue.metadata.width === imageMetadata.width &&
|
|
1588
|
+
// height is correct
|
|
1589
|
+
"height" in currentValue.metadata && currentValue.metadata.height === imageMetadata.height;
|
|
1590
|
+
|
|
1591
|
+
// skips if the metadata is already correct
|
|
1592
|
+
if (!metadataIsCorrect) {
|
|
1593
|
+
if (apply) {
|
|
1594
|
+
patch$1.push({
|
|
1595
|
+
op: "replace",
|
|
1596
|
+
path: patch.sourceToPatchPath(sourcePath).concat("metadata"),
|
|
1597
|
+
value: {
|
|
1598
|
+
width: imageMetadata.width,
|
|
1599
|
+
height: imageMetadata.height,
|
|
1600
|
+
sha256: imageMetadata.sha256
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
} else {
|
|
1604
|
+
if (typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object") {
|
|
1605
|
+
if (!("sha256" in currentValue.metadata) || currentValue.metadata.sha256 !== imageMetadata.sha256) {
|
|
1606
|
+
remainingErrors.push({
|
|
1607
|
+
message: "Image metadata sha256 is incorrect! Found: " + ("sha256" in currentValue.metadata ? currentValue.metadata.sha256 : "<empty>") + ". Expected: " + imageMetadata.sha256 + ".",
|
|
1608
|
+
fixes: undefined
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
if (!("width" in currentValue.metadata) || currentValue.metadata.width !== imageMetadata.width) {
|
|
1612
|
+
remainingErrors.push({
|
|
1613
|
+
message: "Image metadata width is incorrect! Found: " + ("width" in currentValue.metadata ? currentValue.metadata.width : "<empty>") + ". Expected: " + imageMetadata.width,
|
|
1614
|
+
fixes: undefined
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
if (!("height" in currentValue.metadata) || currentValue.metadata.height !== imageMetadata.height) {
|
|
1618
|
+
remainingErrors.push({
|
|
1619
|
+
message: "Image metadata height is incorrect! Found: " + ("height" in currentValue.metadata ? currentValue.metadata.height : "<empty>") + ". Expected: " + imageMetadata.height,
|
|
1620
|
+
fixes: undefined
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
} else {
|
|
1624
|
+
remainingErrors.push({
|
|
1625
|
+
...validationError,
|
|
1626
|
+
message: "Image metadata is not an object!",
|
|
1627
|
+
fixes: undefined
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
} else if (fix === "image:add-metadata") {
|
|
1633
|
+
patch$1.push({
|
|
1634
|
+
op: "add",
|
|
1635
|
+
path: patch.sourceToPatchPath(sourcePath).concat("metadata"),
|
|
1636
|
+
value: {
|
|
1637
|
+
width: imageMetadata.width,
|
|
1638
|
+
height: imageMetadata.height,
|
|
1639
|
+
sha256: imageMetadata.sha256
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
if (!validationError.fixes || validationError.fixes.length === 0) {
|
|
1646
|
+
remainingErrors.push(validationError);
|
|
1647
|
+
}
|
|
1648
|
+
return {
|
|
1649
|
+
patch: patch$1,
|
|
1650
|
+
remainingErrors
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
const getSHA256Hash = async bits => {
|
|
1654
|
+
const hashBuffer = await crypto__default["default"].subtle.digest("SHA-256", bits);
|
|
1655
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1656
|
+
const hash = hashArray.map(item => item.toString(16).padStart(2, "0")).join("");
|
|
1657
|
+
return hash;
|
|
1658
|
+
};
|
|
1659
|
+
|
|
1660
|
+
exports.LocalValServer = LocalValServer;
|
|
1517
1661
|
exports.Service = Service;
|
|
1518
1662
|
exports.ValFSHost = ValFSHost;
|
|
1519
1663
|
exports.ValModuleLoader = ValModuleLoader;
|
|
1520
1664
|
exports.ValSourceFileHandler = ValSourceFileHandler;
|
|
1665
|
+
exports.createFixPatch = createFixPatch;
|
|
1521
1666
|
exports.createRequestHandler = createRequestHandler;
|
|
1522
1667
|
exports.createRequestListener = createRequestListener;
|
|
1523
1668
|
exports.createService = createService;
|
|
@@ -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);
|
|
@@ -1514,10 +1549,120 @@ class ValFSHost {
|
|
|
1514
1549
|
}
|
|
1515
1550
|
}
|
|
1516
1551
|
|
|
1552
|
+
async function createFixPatch(config, apply, sourcePath, validationError) {
|
|
1553
|
+
async function getImageMetadata() {
|
|
1554
|
+
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;
|
|
1555
|
+
if (!maybeRef) {
|
|
1556
|
+
// TODO:
|
|
1557
|
+
throw Error("Cannot fix image without a file reference");
|
|
1558
|
+
}
|
|
1559
|
+
const localFile = path__default["default"].join(config.projectRoot, maybeRef);
|
|
1560
|
+
const buffer = fs__default["default"].readFileSync(localFile);
|
|
1561
|
+
const sha256 = await getSHA256Hash(buffer);
|
|
1562
|
+
const imageSize = sizeOf__default["default"](buffer);
|
|
1563
|
+
return {
|
|
1564
|
+
...imageSize,
|
|
1565
|
+
sha256
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
const remainingErrors = [];
|
|
1569
|
+
const patch$1 = [];
|
|
1570
|
+
for (const fix of validationError.fixes || []) {
|
|
1571
|
+
if (fix === "image:replace-metadata" || fix === "image:add-metadata") {
|
|
1572
|
+
const imageMetadata = await getImageMetadata();
|
|
1573
|
+
if (imageMetadata.width === undefined || imageMetadata.height === undefined || imageMetadata.sha256 === undefined) {
|
|
1574
|
+
remainingErrors.push({
|
|
1575
|
+
...validationError,
|
|
1576
|
+
message: "Failed to get image metadata",
|
|
1577
|
+
fixes: undefined
|
|
1578
|
+
});
|
|
1579
|
+
} else if (fix === "image:replace-metadata") {
|
|
1580
|
+
const currentValue = validationError.value;
|
|
1581
|
+
const metadataIsCorrect =
|
|
1582
|
+
// metadata is a prop that is an object
|
|
1583
|
+
typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object" &&
|
|
1584
|
+
// sha256 is correct
|
|
1585
|
+
"sha256" in currentValue.metadata && currentValue.metadata.sha256 === imageMetadata.sha256 &&
|
|
1586
|
+
// width is correct
|
|
1587
|
+
"width" in currentValue.metadata && currentValue.metadata.width === imageMetadata.width &&
|
|
1588
|
+
// height is correct
|
|
1589
|
+
"height" in currentValue.metadata && currentValue.metadata.height === imageMetadata.height;
|
|
1590
|
+
|
|
1591
|
+
// skips if the metadata is already correct
|
|
1592
|
+
if (!metadataIsCorrect) {
|
|
1593
|
+
if (apply) {
|
|
1594
|
+
patch$1.push({
|
|
1595
|
+
op: "replace",
|
|
1596
|
+
path: patch.sourceToPatchPath(sourcePath).concat("metadata"),
|
|
1597
|
+
value: {
|
|
1598
|
+
width: imageMetadata.width,
|
|
1599
|
+
height: imageMetadata.height,
|
|
1600
|
+
sha256: imageMetadata.sha256
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
} else {
|
|
1604
|
+
if (typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object") {
|
|
1605
|
+
if (!("sha256" in currentValue.metadata) || currentValue.metadata.sha256 !== imageMetadata.sha256) {
|
|
1606
|
+
remainingErrors.push({
|
|
1607
|
+
message: "Image metadata sha256 is incorrect! Found: " + ("sha256" in currentValue.metadata ? currentValue.metadata.sha256 : "<empty>") + ". Expected: " + imageMetadata.sha256 + ".",
|
|
1608
|
+
fixes: undefined
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
if (!("width" in currentValue.metadata) || currentValue.metadata.width !== imageMetadata.width) {
|
|
1612
|
+
remainingErrors.push({
|
|
1613
|
+
message: "Image metadata width is incorrect! Found: " + ("width" in currentValue.metadata ? currentValue.metadata.width : "<empty>") + ". Expected: " + imageMetadata.width,
|
|
1614
|
+
fixes: undefined
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
if (!("height" in currentValue.metadata) || currentValue.metadata.height !== imageMetadata.height) {
|
|
1618
|
+
remainingErrors.push({
|
|
1619
|
+
message: "Image metadata height is incorrect! Found: " + ("height" in currentValue.metadata ? currentValue.metadata.height : "<empty>") + ". Expected: " + imageMetadata.height,
|
|
1620
|
+
fixes: undefined
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
} else {
|
|
1624
|
+
remainingErrors.push({
|
|
1625
|
+
...validationError,
|
|
1626
|
+
message: "Image metadata is not an object!",
|
|
1627
|
+
fixes: undefined
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
} else if (fix === "image:add-metadata") {
|
|
1633
|
+
patch$1.push({
|
|
1634
|
+
op: "add",
|
|
1635
|
+
path: patch.sourceToPatchPath(sourcePath).concat("metadata"),
|
|
1636
|
+
value: {
|
|
1637
|
+
width: imageMetadata.width,
|
|
1638
|
+
height: imageMetadata.height,
|
|
1639
|
+
sha256: imageMetadata.sha256
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
if (!validationError.fixes || validationError.fixes.length === 0) {
|
|
1646
|
+
remainingErrors.push(validationError);
|
|
1647
|
+
}
|
|
1648
|
+
return {
|
|
1649
|
+
patch: patch$1,
|
|
1650
|
+
remainingErrors
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
const getSHA256Hash = async bits => {
|
|
1654
|
+
const hashBuffer = await crypto__default["default"].subtle.digest("SHA-256", bits);
|
|
1655
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1656
|
+
const hash = hashArray.map(item => item.toString(16).padStart(2, "0")).join("");
|
|
1657
|
+
return hash;
|
|
1658
|
+
};
|
|
1659
|
+
|
|
1660
|
+
exports.LocalValServer = LocalValServer;
|
|
1517
1661
|
exports.Service = Service;
|
|
1518
1662
|
exports.ValFSHost = ValFSHost;
|
|
1519
1663
|
exports.ValModuleLoader = ValModuleLoader;
|
|
1520
1664
|
exports.ValSourceFileHandler = ValSourceFileHandler;
|
|
1665
|
+
exports.createFixPatch = createFixPatch;
|
|
1521
1666
|
exports.createRequestHandler = createRequestHandler;
|
|
1522
1667
|
exports.createRequestListener = createRequestListener;
|
|
1523
1668
|
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);
|
|
@@ -1501,4 +1535,112 @@ class ValFSHost {
|
|
|
1501
1535
|
}
|
|
1502
1536
|
}
|
|
1503
1537
|
|
|
1504
|
-
|
|
1538
|
+
async function createFixPatch(config, apply, sourcePath, validationError) {
|
|
1539
|
+
async function getImageMetadata() {
|
|
1540
|
+
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;
|
|
1541
|
+
if (!maybeRef) {
|
|
1542
|
+
// TODO:
|
|
1543
|
+
throw Error("Cannot fix image without a file reference");
|
|
1544
|
+
}
|
|
1545
|
+
const localFile = path.join(config.projectRoot, maybeRef);
|
|
1546
|
+
const buffer = fs.readFileSync(localFile);
|
|
1547
|
+
const sha256 = await getSHA256Hash(buffer);
|
|
1548
|
+
const imageSize = sizeOf(buffer);
|
|
1549
|
+
return {
|
|
1550
|
+
...imageSize,
|
|
1551
|
+
sha256
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
const remainingErrors = [];
|
|
1555
|
+
const patch = [];
|
|
1556
|
+
for (const fix of validationError.fixes || []) {
|
|
1557
|
+
if (fix === "image:replace-metadata" || fix === "image:add-metadata") {
|
|
1558
|
+
const imageMetadata = await getImageMetadata();
|
|
1559
|
+
if (imageMetadata.width === undefined || imageMetadata.height === undefined || imageMetadata.sha256 === undefined) {
|
|
1560
|
+
remainingErrors.push({
|
|
1561
|
+
...validationError,
|
|
1562
|
+
message: "Failed to get image metadata",
|
|
1563
|
+
fixes: undefined
|
|
1564
|
+
});
|
|
1565
|
+
} else if (fix === "image:replace-metadata") {
|
|
1566
|
+
const currentValue = validationError.value;
|
|
1567
|
+
const metadataIsCorrect =
|
|
1568
|
+
// metadata is a prop that is an object
|
|
1569
|
+
typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object" &&
|
|
1570
|
+
// sha256 is correct
|
|
1571
|
+
"sha256" in currentValue.metadata && currentValue.metadata.sha256 === imageMetadata.sha256 &&
|
|
1572
|
+
// width is correct
|
|
1573
|
+
"width" in currentValue.metadata && currentValue.metadata.width === imageMetadata.width &&
|
|
1574
|
+
// height is correct
|
|
1575
|
+
"height" in currentValue.metadata && currentValue.metadata.height === imageMetadata.height;
|
|
1576
|
+
|
|
1577
|
+
// skips if the metadata is already correct
|
|
1578
|
+
if (!metadataIsCorrect) {
|
|
1579
|
+
if (apply) {
|
|
1580
|
+
patch.push({
|
|
1581
|
+
op: "replace",
|
|
1582
|
+
path: sourceToPatchPath(sourcePath).concat("metadata"),
|
|
1583
|
+
value: {
|
|
1584
|
+
width: imageMetadata.width,
|
|
1585
|
+
height: imageMetadata.height,
|
|
1586
|
+
sha256: imageMetadata.sha256
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
} else {
|
|
1590
|
+
if (typeof currentValue === "object" && currentValue && "metadata" in currentValue && currentValue.metadata && typeof currentValue.metadata === "object") {
|
|
1591
|
+
if (!("sha256" in currentValue.metadata) || currentValue.metadata.sha256 !== imageMetadata.sha256) {
|
|
1592
|
+
remainingErrors.push({
|
|
1593
|
+
message: "Image metadata sha256 is incorrect! Found: " + ("sha256" in currentValue.metadata ? currentValue.metadata.sha256 : "<empty>") + ". Expected: " + imageMetadata.sha256 + ".",
|
|
1594
|
+
fixes: undefined
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
if (!("width" in currentValue.metadata) || currentValue.metadata.width !== imageMetadata.width) {
|
|
1598
|
+
remainingErrors.push({
|
|
1599
|
+
message: "Image metadata width is incorrect! Found: " + ("width" in currentValue.metadata ? currentValue.metadata.width : "<empty>") + ". Expected: " + imageMetadata.width,
|
|
1600
|
+
fixes: undefined
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
if (!("height" in currentValue.metadata) || currentValue.metadata.height !== imageMetadata.height) {
|
|
1604
|
+
remainingErrors.push({
|
|
1605
|
+
message: "Image metadata height is incorrect! Found: " + ("height" in currentValue.metadata ? currentValue.metadata.height : "<empty>") + ". Expected: " + imageMetadata.height,
|
|
1606
|
+
fixes: undefined
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
} else {
|
|
1610
|
+
remainingErrors.push({
|
|
1611
|
+
...validationError,
|
|
1612
|
+
message: "Image metadata is not an object!",
|
|
1613
|
+
fixes: undefined
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
} else if (fix === "image:add-metadata") {
|
|
1619
|
+
patch.push({
|
|
1620
|
+
op: "add",
|
|
1621
|
+
path: sourceToPatchPath(sourcePath).concat("metadata"),
|
|
1622
|
+
value: {
|
|
1623
|
+
width: imageMetadata.width,
|
|
1624
|
+
height: imageMetadata.height,
|
|
1625
|
+
sha256: imageMetadata.sha256
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
if (!validationError.fixes || validationError.fixes.length === 0) {
|
|
1632
|
+
remainingErrors.push(validationError);
|
|
1633
|
+
}
|
|
1634
|
+
return {
|
|
1635
|
+
patch,
|
|
1636
|
+
remainingErrors
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
const getSHA256Hash = async bits => {
|
|
1640
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", bits);
|
|
1641
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1642
|
+
const hash = hashArray.map(item => item.toString(16).padStart(2, "0")).join("");
|
|
1643
|
+
return hash;
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
export { LocalValServer, 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.0",
|
|
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.
|
|
29
|
-
"@valbuild/ui": "~0.
|
|
28
|
+
"@valbuild/core": "~0.16.0",
|
|
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",
|
|
@@ -1,8 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
type Source,
|
|
3
|
+
type SerializedSchema,
|
|
4
|
+
ValidationErrors,
|
|
5
|
+
} from "@valbuild/core";
|
|
6
|
+
import { ModuleId, type SourcePath } from "@valbuild/core/src/val";
|
|
3
7
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
export const FATAL_ERROR_TYPES = [
|
|
9
|
+
"no-schema",
|
|
10
|
+
"no-source",
|
|
11
|
+
"invalid-id",
|
|
12
|
+
"no-module",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
export type SerializedModuleContent =
|
|
16
|
+
| {
|
|
17
|
+
source: Source;
|
|
18
|
+
schema: SerializedSchema;
|
|
19
|
+
path: SourcePath;
|
|
20
|
+
errors: false;
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
source?: Source;
|
|
24
|
+
schema?: SerializedSchema;
|
|
25
|
+
path?: SourcePath;
|
|
26
|
+
errors: {
|
|
27
|
+
invalidModuleId?: ModuleId;
|
|
28
|
+
validation?: ValidationErrors;
|
|
29
|
+
fatal?: {
|
|
30
|
+
message: string;
|
|
31
|
+
stack?: string[];
|
|
32
|
+
type?: (typeof FATAL_ERROR_TYPES)[number];
|
|
33
|
+
}[];
|
|
34
|
+
};
|
|
35
|
+
};
|
package/src/Service.ts
CHANGED
|
@@ -34,7 +34,8 @@ export async function createService(
|
|
|
34
34
|
host: IValFSHost = {
|
|
35
35
|
...ts.sys,
|
|
36
36
|
writeFile: fs.writeFileSync,
|
|
37
|
-
}
|
|
37
|
+
},
|
|
38
|
+
loader?: ValModuleLoader
|
|
38
39
|
): Promise<Service> {
|
|
39
40
|
const compilerOptions = getCompilerOptions(projectRoot, host);
|
|
40
41
|
const sourceFileHandler = new ValSourceFileHandler(
|
|
@@ -42,14 +43,12 @@ export async function createService(
|
|
|
42
43
|
compilerOptions,
|
|
43
44
|
host
|
|
44
45
|
);
|
|
45
|
-
const loader = new ValModuleLoader(
|
|
46
|
-
projectRoot,
|
|
47
|
-
compilerOptions,
|
|
48
|
-
sourceFileHandler,
|
|
49
|
-
host
|
|
50
|
-
);
|
|
51
46
|
const module = await newQuickJSWASMModule();
|
|
52
|
-
const runtime = await newValQuickJSRuntime(
|
|
47
|
+
const runtime = await newValQuickJSRuntime(
|
|
48
|
+
module,
|
|
49
|
+
loader ||
|
|
50
|
+
new ValModuleLoader(projectRoot, compilerOptions, sourceFileHandler, host)
|
|
51
|
+
);
|
|
53
52
|
return new Service(opts, sourceFileHandler, runtime);
|
|
54
53
|
}
|
|
55
54
|
|
|
@@ -74,19 +73,40 @@ export class Service {
|
|
|
74
73
|
this.runtime
|
|
75
74
|
);
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
if (valModule.source && valModule.schema) {
|
|
77
|
+
const resolved = Internal.resolvePath(
|
|
78
|
+
modulePath,
|
|
79
|
+
valModule.source,
|
|
80
|
+
valModule.schema
|
|
81
|
+
);
|
|
82
|
+
const sourcePath = [moduleId, resolved.path].join(".") as SourcePath;
|
|
83
|
+
return {
|
|
84
|
+
path: sourcePath,
|
|
85
|
+
schema:
|
|
86
|
+
resolved.schema instanceof Schema<SelectorSource>
|
|
87
|
+
? resolved.schema.serialize()
|
|
88
|
+
: resolved.schema,
|
|
89
|
+
source: resolved.source,
|
|
90
|
+
errors:
|
|
91
|
+
valModule.errors &&
|
|
92
|
+
valModule.errors.validation &&
|
|
93
|
+
valModule.errors.validation[sourcePath]
|
|
94
|
+
? {
|
|
95
|
+
validation: valModule.errors.validation[sourcePath]
|
|
96
|
+
? {
|
|
97
|
+
[sourcePath]: valModule.errors.validation[sourcePath],
|
|
98
|
+
}
|
|
99
|
+
: undefined,
|
|
100
|
+
fatal:
|
|
101
|
+
valModule.errors && valModule.errors.fatal
|
|
102
|
+
? valModule.errors.fatal
|
|
103
|
+
: undefined,
|
|
104
|
+
}
|
|
105
|
+
: false,
|
|
106
|
+
};
|
|
107
|
+
} else {
|
|
108
|
+
return valModule;
|
|
109
|
+
}
|
|
90
110
|
}
|
|
91
111
|
|
|
92
112
|
async patch(
|
package/src/ValModuleLoader.ts
CHANGED
|
@@ -60,7 +60,7 @@ export class ValModuleLoader {
|
|
|
60
60
|
// allowJs: true,
|
|
61
61
|
// rootDir: this.compilerOptions.rootDir,
|
|
62
62
|
module: ts.ModuleKind.ESNext,
|
|
63
|
-
target: ts.ScriptTarget.
|
|
63
|
+
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 {} = {})
|
|
64
64
|
// moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
65
65
|
// target: ts.ScriptTarget.ES2020, // QuickJs runs in ES2020 so we must use that
|
|
66
66
|
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { FILE_REF_PROP, SourcePath, ValidationError } from "@valbuild/core";
|
|
2
|
+
import { Patch, sourceToPatchPath } from "@valbuild/core/patch";
|
|
3
|
+
import sizeOf from "image-size";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
export async function createFixPatch(
|
|
9
|
+
config: { projectRoot: string },
|
|
10
|
+
apply: boolean,
|
|
11
|
+
sourcePath: SourcePath,
|
|
12
|
+
validationError: ValidationError
|
|
13
|
+
): Promise<{ patch: Patch; remainingErrors: ValidationError[] } | undefined> {
|
|
14
|
+
async function getImageMetadata() {
|
|
15
|
+
const maybeRef =
|
|
16
|
+
validationError.value &&
|
|
17
|
+
typeof validationError.value === "object" &&
|
|
18
|
+
FILE_REF_PROP in validationError.value &&
|
|
19
|
+
typeof validationError.value[FILE_REF_PROP] === "string"
|
|
20
|
+
? validationError.value[FILE_REF_PROP]
|
|
21
|
+
: undefined;
|
|
22
|
+
|
|
23
|
+
if (!maybeRef) {
|
|
24
|
+
// TODO:
|
|
25
|
+
throw Error("Cannot fix image without a file reference");
|
|
26
|
+
}
|
|
27
|
+
const localFile = path.join(config.projectRoot, maybeRef);
|
|
28
|
+
const buffer = fs.readFileSync(localFile);
|
|
29
|
+
const sha256 = await getSHA256Hash(buffer);
|
|
30
|
+
const imageSize = sizeOf(buffer);
|
|
31
|
+
return {
|
|
32
|
+
...imageSize,
|
|
33
|
+
sha256,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const remainingErrors: ValidationError[] = [];
|
|
37
|
+
const patch: Patch = [];
|
|
38
|
+
for (const fix of validationError.fixes || []) {
|
|
39
|
+
if (fix === "image:replace-metadata" || fix === "image:add-metadata") {
|
|
40
|
+
const imageMetadata = await getImageMetadata();
|
|
41
|
+
if (
|
|
42
|
+
imageMetadata.width === undefined ||
|
|
43
|
+
imageMetadata.height === undefined ||
|
|
44
|
+
imageMetadata.sha256 === undefined
|
|
45
|
+
) {
|
|
46
|
+
remainingErrors.push({
|
|
47
|
+
...validationError,
|
|
48
|
+
message: "Failed to get image metadata",
|
|
49
|
+
fixes: undefined,
|
|
50
|
+
});
|
|
51
|
+
} else if (fix === "image:replace-metadata") {
|
|
52
|
+
const currentValue = validationError.value;
|
|
53
|
+
const metadataIsCorrect =
|
|
54
|
+
// metadata is a prop that is an object
|
|
55
|
+
typeof currentValue === "object" &&
|
|
56
|
+
currentValue &&
|
|
57
|
+
"metadata" in currentValue &&
|
|
58
|
+
currentValue.metadata &&
|
|
59
|
+
typeof currentValue.metadata === "object" &&
|
|
60
|
+
// sha256 is correct
|
|
61
|
+
"sha256" in currentValue.metadata &&
|
|
62
|
+
currentValue.metadata.sha256 === imageMetadata.sha256 &&
|
|
63
|
+
// width is correct
|
|
64
|
+
"width" in currentValue.metadata &&
|
|
65
|
+
currentValue.metadata.width === imageMetadata.width &&
|
|
66
|
+
// height is correct
|
|
67
|
+
"height" in currentValue.metadata &&
|
|
68
|
+
currentValue.metadata.height === imageMetadata.height;
|
|
69
|
+
|
|
70
|
+
// skips if the metadata is already correct
|
|
71
|
+
if (!metadataIsCorrect) {
|
|
72
|
+
if (apply) {
|
|
73
|
+
patch.push({
|
|
74
|
+
op: "replace",
|
|
75
|
+
path: sourceToPatchPath(sourcePath).concat("metadata"),
|
|
76
|
+
value: {
|
|
77
|
+
width: imageMetadata.width,
|
|
78
|
+
height: imageMetadata.height,
|
|
79
|
+
sha256: imageMetadata.sha256,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
if (
|
|
84
|
+
typeof currentValue === "object" &&
|
|
85
|
+
currentValue &&
|
|
86
|
+
"metadata" in currentValue &&
|
|
87
|
+
currentValue.metadata &&
|
|
88
|
+
typeof currentValue.metadata === "object"
|
|
89
|
+
) {
|
|
90
|
+
if (
|
|
91
|
+
!("sha256" in currentValue.metadata) ||
|
|
92
|
+
currentValue.metadata.sha256 !== imageMetadata.sha256
|
|
93
|
+
) {
|
|
94
|
+
remainingErrors.push({
|
|
95
|
+
message:
|
|
96
|
+
"Image metadata sha256 is incorrect! Found: " +
|
|
97
|
+
("sha256" in currentValue.metadata
|
|
98
|
+
? currentValue.metadata.sha256
|
|
99
|
+
: "<empty>") +
|
|
100
|
+
". Expected: " +
|
|
101
|
+
imageMetadata.sha256 +
|
|
102
|
+
".",
|
|
103
|
+
fixes: undefined,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (
|
|
107
|
+
!("width" in currentValue.metadata) ||
|
|
108
|
+
currentValue.metadata.width !== imageMetadata.width
|
|
109
|
+
) {
|
|
110
|
+
remainingErrors.push({
|
|
111
|
+
message:
|
|
112
|
+
"Image metadata width is incorrect! Found: " +
|
|
113
|
+
("width" in currentValue.metadata
|
|
114
|
+
? currentValue.metadata.width
|
|
115
|
+
: "<empty>") +
|
|
116
|
+
". Expected: " +
|
|
117
|
+
imageMetadata.width,
|
|
118
|
+
fixes: undefined,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (
|
|
122
|
+
!("height" in currentValue.metadata) ||
|
|
123
|
+
currentValue.metadata.height !== imageMetadata.height
|
|
124
|
+
) {
|
|
125
|
+
remainingErrors.push({
|
|
126
|
+
message:
|
|
127
|
+
"Image metadata height is incorrect! Found: " +
|
|
128
|
+
("height" in currentValue.metadata
|
|
129
|
+
? currentValue.metadata.height
|
|
130
|
+
: "<empty>") +
|
|
131
|
+
". Expected: " +
|
|
132
|
+
imageMetadata.height,
|
|
133
|
+
fixes: undefined,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
remainingErrors.push({
|
|
138
|
+
...validationError,
|
|
139
|
+
message: "Image metadata is not an object!",
|
|
140
|
+
fixes: undefined,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} else if (fix === "image:add-metadata") {
|
|
146
|
+
patch.push({
|
|
147
|
+
op: "add",
|
|
148
|
+
path: sourceToPatchPath(sourcePath).concat("metadata"),
|
|
149
|
+
value: {
|
|
150
|
+
width: imageMetadata.width,
|
|
151
|
+
height: imageMetadata.height,
|
|
152
|
+
sha256: imageMetadata.sha256,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!validationError.fixes || validationError.fixes.length === 0) {
|
|
159
|
+
remainingErrors.push(validationError);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
patch,
|
|
163
|
+
remainingErrors,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const getSHA256Hash = async (bits: Uint8Array) => {
|
|
168
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", bits);
|
|
169
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
170
|
+
const hash = hashArray
|
|
171
|
+
.map((item) => item.toString(16).padStart(2, "0"))
|
|
172
|
+
.join("");
|
|
173
|
+
return hash;
|
|
174
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -10,3 +10,5 @@ export type { IValFSHost } from "./ValFSHost";
|
|
|
10
10
|
export type { ValFS } from "./ValFS";
|
|
11
11
|
export { patchSourceFile } from "./patchValFile";
|
|
12
12
|
export { formatSyntaxErrorTree } from "./patch/ts/syntax";
|
|
13
|
+
export { LocalValServer } from "./LocalValServer";
|
|
14
|
+
export { createFixPatch } from "./createFixPatch";
|
package/src/readValFile.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ModuleId } from "@valbuild/core";
|
|
1
2
|
import path from "path";
|
|
2
3
|
import { QuickJSRuntime } from "quickjs-emscripten";
|
|
3
4
|
import { SerializedModuleContent } from "./SerializedModuleContent";
|
|
@@ -16,6 +17,10 @@ globalThis.valModule = {
|
|
|
16
17
|
id: valModule?.default && Internal.getValPath(valModule?.default),
|
|
17
18
|
schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
|
|
18
19
|
source: valModule?.default && Internal.getRawSource(valModule?.default),
|
|
20
|
+
validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
|
|
21
|
+
valModule?.default && Internal.getValPath(valModule?.default) || "/",
|
|
22
|
+
valModule?.default && Internal.getRawSource(valModule?.default)
|
|
23
|
+
)
|
|
19
24
|
};
|
|
20
25
|
`;
|
|
21
26
|
const result = context.evalCode(
|
|
@@ -23,48 +28,61 @@ globalThis.valModule = {
|
|
|
23
28
|
// Synthetic module name
|
|
24
29
|
path.join(path.dirname(valConfigPath), "<val>")
|
|
25
30
|
);
|
|
31
|
+
const fatalErrors: string[] = [];
|
|
26
32
|
if (result.error) {
|
|
27
33
|
const error = result.error.consume(context.dump);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
return {
|
|
35
|
+
errors: {
|
|
36
|
+
invalidModuleId: id as ModuleId,
|
|
37
|
+
fatal: [
|
|
38
|
+
{
|
|
39
|
+
message: `${error.name || "Unknown error"}: ${
|
|
40
|
+
error.message || "<no message>"
|
|
41
|
+
}`,
|
|
42
|
+
stack: error.stack,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
};
|
|
35
47
|
} else {
|
|
36
48
|
result.value.dispose();
|
|
37
49
|
const valModule = context
|
|
38
50
|
.getProp(context.global, "valModule")
|
|
39
51
|
.consume(context.dump);
|
|
40
52
|
|
|
41
|
-
const errors: string[] = [];
|
|
42
|
-
|
|
43
53
|
if (!valModule) {
|
|
44
|
-
|
|
54
|
+
fatalErrors.push(`Could not find any modules at: ${id}`);
|
|
45
55
|
} else {
|
|
46
56
|
if (valModule.id !== id) {
|
|
47
|
-
|
|
57
|
+
fatalErrors.push(
|
|
58
|
+
`Expected val id: '${id}' but got: '${valModule.id}'`
|
|
59
|
+
);
|
|
48
60
|
}
|
|
49
61
|
if (!valModule?.schema) {
|
|
50
|
-
|
|
62
|
+
fatalErrors.push(`Expected val id: '${id}' to have a schema`);
|
|
51
63
|
}
|
|
52
64
|
if (!valModule?.source) {
|
|
53
|
-
|
|
65
|
+
fatalErrors.push(`Expected val id: '${id}' to have a source`);
|
|
54
66
|
}
|
|
55
67
|
}
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
let errors: SerializedModuleContent["errors"] = false;
|
|
69
|
+
if (fatalErrors.length > 0) {
|
|
70
|
+
errors = {
|
|
71
|
+
invalidModuleId: valModule.id !== id ? (id as ModuleId) : undefined,
|
|
72
|
+
fatal: fatalErrors.map((message) => ({ message })),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (valModule?.validation) {
|
|
76
|
+
errors = {
|
|
77
|
+
...(errors ? errors : {}),
|
|
78
|
+
validation: valModule.validation,
|
|
79
|
+
};
|
|
63
80
|
}
|
|
64
81
|
return {
|
|
65
|
-
path: valModule.id, //
|
|
82
|
+
path: valModule.id, // NOTE: we use path here, since SerializedModuleContent (maybe bad name?) can be used for whole modules as well as subparts of modules
|
|
66
83
|
source: valModule.source,
|
|
67
84
|
schema: valModule.schema,
|
|
85
|
+
errors,
|
|
68
86
|
};
|
|
69
87
|
}
|
|
70
88
|
} finally {
|