@valbuild/server 0.60.18 → 0.60.19
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 +5 -1
- package/dist/declarations/src/Service.d.ts +5 -1
- package/dist/declarations/src/ValServer.d.ts +7 -1
- package/dist/valbuild-server.cjs.dev.js +101 -46
- package/dist/valbuild-server.cjs.prod.js +101 -46
- package/dist/valbuild-server.esm.js +101 -46
- package/package.json +4 -4
@@ -42,7 +42,11 @@ export declare class LocalValServer extends ValServer {
|
|
42
42
|
private getPatchFilePath;
|
43
43
|
private badRequest;
|
44
44
|
protected ensureRemoteFSInitialized(): Promise<result.Result<undefined, ValServerError>>;
|
45
|
-
protected getModule(moduleId: ModuleId
|
45
|
+
protected getModule(moduleId: ModuleId, options: {
|
46
|
+
validate: boolean;
|
47
|
+
source: boolean;
|
48
|
+
schema: boolean;
|
49
|
+
}): Promise<SerializedModuleContent>;
|
46
50
|
protected execCommit(patches: [PatchId, ModuleId, Patch][]): Promise<{
|
47
51
|
status: 200;
|
48
52
|
json: Record<ModuleId, {
|
@@ -25,7 +25,11 @@ export declare class Service {
|
|
25
25
|
private readonly runtime;
|
26
26
|
readonly valConfigPath: string;
|
27
27
|
constructor({ valConfigPath }: ServiceOptions, sourceFileHandler: ValSourceFileHandler, runtime: QuickJSRuntime);
|
28
|
-
get(moduleId: ModuleId, modulePath
|
28
|
+
get(moduleId: ModuleId, modulePath: ModulePath, options?: {
|
29
|
+
validate: boolean;
|
30
|
+
source: boolean;
|
31
|
+
schema: boolean;
|
32
|
+
}): Promise<SerializedModuleContent>;
|
29
33
|
patch(moduleId: ModuleId, patch: Patch): Promise<void>;
|
30
34
|
dispose(): void;
|
31
35
|
}
|
@@ -32,6 +32,7 @@ export declare abstract class ValServer implements IValServer {
|
|
32
32
|
patch?: string;
|
33
33
|
schema?: string;
|
34
34
|
source?: string;
|
35
|
+
validate?: string;
|
35
36
|
}, cookies: ValCookies<VAL_SESSION_COOKIE>, requestHeaders: RequestHeaders): Promise<ValServerJsonResult<ApiTreeResponse>>;
|
36
37
|
postValidate(rawBody: unknown, cookies: ValCookies<VAL_SESSION_COOKIE>, requestHeaders: RequestHeaders): Promise<ValServerJsonResult<ApiPostValidationResponse | ApiPostValidationErrorResponse>>;
|
37
38
|
postCommit(rawBody: unknown, cookies: ValCookies<VAL_SESSION_COOKIE>, requestHeaders: RequestHeaders): Promise<ValServerJsonResult<ApiCommitResponse, ApiPostValidationErrorResponse>>;
|
@@ -58,7 +59,11 @@ export declare abstract class ValServer implements IValServer {
|
|
58
59
|
* 2) The error is returned via API if the remote FS could not be initialized
|
59
60
|
* */
|
60
61
|
protected abstract ensureRemoteFSInitialized(errorMessageType: string, cookies: ValCookies<VAL_SESSION_COOKIE>): Promise<result.Result<undefined, ValServerError>>;
|
61
|
-
protected abstract getModule(moduleId: ModuleId
|
62
|
+
protected abstract getModule(moduleId: ModuleId, options: {
|
63
|
+
validate: boolean;
|
64
|
+
source: boolean;
|
65
|
+
schema: boolean;
|
66
|
+
}): Promise<SerializedModuleContent>;
|
62
67
|
protected abstract execCommit(patches: [PatchId, ModuleId, Patch][], cookies: ValCookies<VAL_SESSION_COOKIE>): Promise<{
|
63
68
|
status: 200;
|
64
69
|
json: Record<ModuleId, {
|
@@ -129,6 +134,7 @@ export interface IValServer {
|
|
129
134
|
patch?: string;
|
130
135
|
schema?: string;
|
131
136
|
source?: string;
|
137
|
+
validate?: string;
|
132
138
|
}, cookies: ValCookies<VAL_SESSION_COOKIE>, requestHeaders: RequestHeaders): Promise<ValServerJsonResult<ApiTreeResponse>>;
|
133
139
|
getPatches(query: {
|
134
140
|
id?: string[];
|
@@ -10,6 +10,7 @@ var patch = require('@valbuild/core/patch');
|
|
10
10
|
var path = require('path');
|
11
11
|
var fs = require('fs');
|
12
12
|
var sucrase = require('sucrase');
|
13
|
+
var ui = require('@valbuild/ui');
|
13
14
|
var z = require('zod');
|
14
15
|
var internal = require('@valbuild/shared/internal');
|
15
16
|
var sizeOf = require('image-size');
|
@@ -692,7 +693,7 @@ const patchSourceFile = (sourceFile, patch$1) => {
|
|
692
693
|
return patch.applyPatch(sourceFile, ops$1, patch$1);
|
693
694
|
};
|
694
695
|
|
695
|
-
const readValFile = async (id, valConfigPath, runtime) => {
|
696
|
+
const readValFile = async (id, valConfigPath, runtime, options) => {
|
696
697
|
const context = runtime.newContext();
|
697
698
|
|
698
699
|
// avoid failures when console.log is called
|
@@ -710,8 +711,22 @@ const readValFile = async (id, valConfigPath, runtime) => {
|
|
710
711
|
const processHandle = context.newObject();
|
711
712
|
context.setProp(processHandle, "env", envHandle);
|
712
713
|
context.setProp(context.global, "process", processHandle);
|
714
|
+
const optionsHandle = context.newObject();
|
715
|
+
if (options) {
|
716
|
+
if (options.validate !== undefined) {
|
717
|
+
context.setProp(optionsHandle, "validate", context.newNumber(+options.validate));
|
718
|
+
}
|
719
|
+
if (options.source !== undefined) {
|
720
|
+
context.setProp(optionsHandle, "source", context.newNumber(+options.source));
|
721
|
+
}
|
722
|
+
if (options.schema !== undefined) {
|
723
|
+
context.setProp(optionsHandle, "schema", context.newNumber(+options.schema));
|
724
|
+
}
|
725
|
+
}
|
726
|
+
context.setProp(context.global, "__VAL_OPTIONS__", optionsHandle);
|
713
727
|
envHandle.dispose();
|
714
728
|
processHandle.dispose();
|
729
|
+
optionsHandle.dispose();
|
715
730
|
try {
|
716
731
|
const modulePath = `.${id}.val`;
|
717
732
|
const code = `import * as valModule from ${JSON.stringify(modulePath)};
|
@@ -719,12 +734,12 @@ import { Internal } from "@valbuild/core";
|
|
719
734
|
|
720
735
|
globalThis.valModule = {
|
721
736
|
id: valModule?.default && Internal.getValPath(valModule?.default),
|
722
|
-
schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
|
723
|
-
source: valModule?.default && Internal.getSource(valModule?.default),
|
724
|
-
validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
|
737
|
+
schema: !!globalThis['__VAL_OPTIONS__'].schema ? valModule?.default && Internal.getSchema(valModule?.default)?.serialize() : undefined,
|
738
|
+
source: !!globalThis['__VAL_OPTIONS__'].source ? valModule?.default && Internal.getSource(valModule?.default) : undefined,
|
739
|
+
validation: !!globalThis['__VAL_OPTIONS__'].validate ? valModule?.default && Internal.getSchema(valModule?.default)?.validate(
|
725
740
|
valModule?.default && Internal.getValPath(valModule?.default) || "/",
|
726
741
|
valModule?.default && Internal.getSource(valModule?.default)
|
727
|
-
),
|
742
|
+
) : undefined,
|
728
743
|
defaultExport: !!valModule?.default,
|
729
744
|
};
|
730
745
|
`;
|
@@ -755,9 +770,9 @@ globalThis.valModule = {
|
|
755
770
|
fatalErrors.push(`Wrong c.define id! Expected: '${id}', found: '${valModule.id}'`);
|
756
771
|
} else if (encodeURIComponent(valModule.id).replace(/%2F/g, "/") !== valModule.id) {
|
757
772
|
fatalErrors.push(`Invalid c.define id! Must be a web-safe path without escape characters, found: '${valModule.id}', which was encoded as: '${encodeURIComponent(valModule.id).replace("%2F", "/")}'`);
|
758
|
-
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined) {
|
773
|
+
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined && options.schema) {
|
759
774
|
fatalErrors.push(`Expected val id: '${id}' to have a schema`);
|
760
|
-
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined) {
|
775
|
+
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined && options.source) {
|
761
776
|
fatalErrors.push(`Expected val id: '${id}' to have a source`);
|
762
777
|
}
|
763
778
|
}
|
@@ -998,7 +1013,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
|
|
998
1013
|
}
|
999
1014
|
if (modulePath === "@valbuild/react/internal") {
|
1000
1015
|
return {
|
1001
|
-
value:
|
1016
|
+
value: `
|
1017
|
+
const useVal = () => { throw Error('Cannot use \\'useVal\\' in this type of file') };
|
1018
|
+
export function ValProvider() { throw Error('Cannot use \\'ValProvider\\' in this type of file') };
|
1019
|
+
export function ValRichText() { throw Error('Cannot use \\'ValRichText\\' in this type of file')};`
|
1002
1020
|
};
|
1003
1021
|
}
|
1004
1022
|
if (modulePath === "@valbuild/ui") {
|
@@ -1007,10 +1025,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
|
|
1007
1025
|
export const ValOverlay = () => {
|
1008
1026
|
throw Error("Cannot use 'ValOverlay' in this type of file")
|
1009
1027
|
};
|
1010
|
-
export const VAL_CSS_PATH = "
|
1011
|
-
export const VAL_APP_PATH = "
|
1012
|
-
export const VAL_APP_ID = "
|
1013
|
-
export const VAL_OVERLAY_ID = "
|
1028
|
+
export const VAL_CSS_PATH = "${ui.VAL_CSS_PATH}";
|
1029
|
+
export const VAL_APP_PATH = "${ui.VAL_CSS_PATH}";
|
1030
|
+
export const VAL_APP_ID = "${ui.VAL_APP_ID}";
|
1031
|
+
export const VAL_OVERLAY_ID = "${ui.VAL_OVERLAY_ID}";
|
1014
1032
|
export const IS_DEV = false;
|
1015
1033
|
`
|
1016
1034
|
};
|
@@ -1037,7 +1055,21 @@ export const IS_DEV = false;
|
|
1037
1055
|
}
|
1038
1056
|
if (modulePath.startsWith("react")) {
|
1039
1057
|
return {
|
1040
|
-
value:
|
1058
|
+
value: `
|
1059
|
+
export const createContext = () => new Proxy({}, { get() { return () => { throw new Error('Cannot use \\'createContext\\' in this file') } } } );
|
1060
|
+
export const useTransition = () => { throw Error('Cannot use \\'useTransition\\' in this type of file') };
|
1061
|
+
|
1062
|
+
export default new Proxy({}, {
|
1063
|
+
get(target, props) {
|
1064
|
+
// React.createContext might be called on top-level
|
1065
|
+
if (props === 'createContext') {
|
1066
|
+
return createContext;
|
1067
|
+
}
|
1068
|
+
return () => {
|
1069
|
+
throw new Error('Cannot import \\'react\\' in this file');
|
1070
|
+
}
|
1071
|
+
}
|
1072
|
+
})`
|
1041
1073
|
};
|
1042
1074
|
}
|
1043
1075
|
if (modulePath.includes("/ValNextProvider")) {
|
@@ -1168,8 +1200,12 @@ class Service {
|
|
1168
1200
|
this.runtime = runtime;
|
1169
1201
|
this.valConfigPath = valConfigPath || "./val.config";
|
1170
1202
|
}
|
1171
|
-
async get(moduleId, modulePath
|
1172
|
-
const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime
|
1203
|
+
async get(moduleId, modulePath, options) {
|
1204
|
+
const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime, options ?? {
|
1205
|
+
validate: true,
|
1206
|
+
source: true,
|
1207
|
+
schema: true
|
1208
|
+
});
|
1173
1209
|
if (valModule.source && valModule.schema) {
|
1174
1210
|
const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
|
1175
1211
|
const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
|
@@ -1434,8 +1470,11 @@ class ValServer {
|
|
1434
1470
|
if (fp.result.isErr(ensureRes)) {
|
1435
1471
|
return ensureRes.error;
|
1436
1472
|
}
|
1437
|
-
const moduleIds = this.getAllModules(treePath);
|
1438
1473
|
const applyPatches = query.patch === "true";
|
1474
|
+
const execValidations = query.validate === "true";
|
1475
|
+
const includeSource = query.source === "true";
|
1476
|
+
const includeSchema = query.schema === "true";
|
1477
|
+
const moduleIds = this.getAllModules(treePath);
|
1439
1478
|
let {
|
1440
1479
|
patchIdsByModuleId,
|
1441
1480
|
patchesById,
|
@@ -1455,7 +1494,7 @@ class ValServer {
|
|
1455
1494
|
fileUpdates = res.value.fileUpdates;
|
1456
1495
|
}
|
1457
1496
|
const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
|
1458
|
-
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1497
|
+
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
|
1459
1498
|
}));
|
1460
1499
|
const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
|
1461
1500
|
const module = {
|
@@ -1509,13 +1548,17 @@ class ValServer {
|
|
1509
1548
|
|
1510
1549
|
/* */
|
1511
1550
|
|
1512
|
-
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1513
|
-
const serializedModuleContent = await this.getModule(moduleId
|
1514
|
-
|
1515
|
-
|
1551
|
+
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, validate, includeSource, includeSchema) {
|
1552
|
+
const serializedModuleContent = await this.getModule(moduleId, {
|
1553
|
+
validate: validate,
|
1554
|
+
source: includeSource,
|
1555
|
+
schema: includeSchema
|
1556
|
+
});
|
1516
1557
|
if (!applyPatches) {
|
1517
1558
|
return serializedModuleContent;
|
1518
1559
|
}
|
1560
|
+
const schema = serializedModuleContent.schema;
|
1561
|
+
const maybeSource = serializedModuleContent.source;
|
1519
1562
|
if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
|
1520
1563
|
return serializedModuleContent;
|
1521
1564
|
}
|
@@ -1551,17 +1594,19 @@ class ValServer {
|
|
1551
1594
|
};
|
1552
1595
|
}
|
1553
1596
|
}
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1597
|
+
if (validate) {
|
1598
|
+
const validationErrors = core.deserializeSchema(schema).validate(moduleId, source);
|
1599
|
+
if (validationErrors) {
|
1600
|
+
const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
|
1601
|
+
return {
|
1602
|
+
path: moduleId,
|
1603
|
+
schema,
|
1604
|
+
source,
|
1605
|
+
errors: revalidated && {
|
1606
|
+
validation: revalidated
|
1607
|
+
}
|
1608
|
+
};
|
1609
|
+
}
|
1565
1610
|
}
|
1566
1611
|
return {
|
1567
1612
|
path: moduleId,
|
@@ -1576,7 +1621,7 @@ class ValServer {
|
|
1576
1621
|
// The reason is that validate will be called inside QuickJS (in the future, hopefully),
|
1577
1622
|
// which does not have access to the filesystem, at least not at the time of writing this comment.
|
1578
1623
|
// If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
|
1579
|
-
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies,
|
1624
|
+
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
|
1580
1625
|
const revalidatedValidationErrors = {};
|
1581
1626
|
for (const pathStr in validationErrors) {
|
1582
1627
|
const errorSourcePath = pathStr;
|
@@ -1598,7 +1643,7 @@ class ValServer {
|
|
1598
1643
|
if (updatedFileMetadata) {
|
1599
1644
|
const fileRes = await this.getFiles(fileRef, {
|
1600
1645
|
sha256: updatedFileMetadata.sha256
|
1601
|
-
}, cookies,
|
1646
|
+
}, cookies, reqHeaders);
|
1602
1647
|
if (fileRes.status === 200 && fileRes.body) {
|
1603
1648
|
const res = new Response(fileRes.body);
|
1604
1649
|
fileBuffer = Buffer.from(await res.arrayBuffer());
|
@@ -1608,14 +1653,23 @@ class ValServer {
|
|
1608
1653
|
});
|
1609
1654
|
}
|
1610
1655
|
}
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1656
|
+
// try fetch file directly via http
|
1657
|
+
if (fileRef.startsWith("/public")) {
|
1658
|
+
const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
|
1659
|
+
const fileUrl = fileRef.slice("/public".length);
|
1660
|
+
const fetchRes = await fetch(new URL(fileUrl, host));
|
1661
|
+
if (fetchRes.status === 200) {
|
1662
|
+
fileBuffer = Buffer.from(await fetchRes.arrayBuffer());
|
1663
|
+
} else {
|
1664
|
+
console.error("Val: unexpected error while fetching image / file:", fileRef, {
|
1665
|
+
error: {
|
1666
|
+
status: fetchRes.status,
|
1667
|
+
url: fetchRes.url
|
1668
|
+
}
|
1617
1669
|
});
|
1618
1670
|
}
|
1671
|
+
} else {
|
1672
|
+
console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
|
1619
1673
|
}
|
1620
1674
|
if (!fileBuffer) {
|
1621
1675
|
revalidatedValidationErrors[errorSourcePath].push({
|
@@ -1762,7 +1816,7 @@ class ValServer {
|
|
1762
1816
|
const moduleId = moduleIdStr;
|
1763
1817
|
const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
|
1764
1818
|
// TODO: refine to ModuleId and PatchId when parsing
|
1765
|
-
patchIdsByModuleId, patchesById, fileUpdates, true,
|
1819
|
+
patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
|
1766
1820
|
if (serializedModuleContent.errors) {
|
1767
1821
|
validationErrorsByModuleId[moduleId] = serializedModuleContent;
|
1768
1822
|
}
|
@@ -2266,8 +2320,8 @@ class LocalValServer extends ValServer {
|
|
2266
2320
|
// No RemoteFS so nothing to ensure
|
2267
2321
|
return fp.result.ok(undefined);
|
2268
2322
|
}
|
2269
|
-
getModule(moduleId) {
|
2270
|
-
return this.options.service.get(moduleId);
|
2323
|
+
getModule(moduleId, options) {
|
2324
|
+
return this.options.service.get(moduleId, "", options);
|
2271
2325
|
}
|
2272
2326
|
async execCommit(patches) {
|
2273
2327
|
for (const [patchId, moduleId, patch] of patches) {
|
@@ -2555,11 +2609,11 @@ class ProxyValServer extends ValServer {
|
|
2555
2609
|
|
2556
2610
|
/** Remote FS dependent methods: */
|
2557
2611
|
|
2558
|
-
async getModule(moduleId) {
|
2612
|
+
async getModule(moduleId, options) {
|
2559
2613
|
if (!this.lazyService) {
|
2560
2614
|
this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
|
2561
2615
|
}
|
2562
|
-
return this.lazyService.get(moduleId);
|
2616
|
+
return this.lazyService.get(moduleId, "", options);
|
2563
2617
|
}
|
2564
2618
|
execCommit(patches, cookies) {
|
2565
2619
|
return withAuth(this.options.valSecret, cookies, "execCommit", async ({
|
@@ -3421,7 +3475,8 @@ function createValApiRouter(route, valServerPromise, convert) {
|
|
3421
3475
|
return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
|
3422
3476
|
patch: url.searchParams.get("patch") || undefined,
|
3423
3477
|
schema: url.searchParams.get("schema") || undefined,
|
3424
|
-
source: url.searchParams.get("source") || undefined
|
3478
|
+
source: url.searchParams.get("source") || undefined,
|
3479
|
+
validate: url.searchParams.get("validate") || undefined
|
3425
3480
|
}, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
|
3426
3481
|
} else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
|
3427
3482
|
return withTreePath(path, PATCHES_PATH_PREFIX)(async () => convert(await valServer.getPatches({
|
@@ -10,6 +10,7 @@ var patch = require('@valbuild/core/patch');
|
|
10
10
|
var path = require('path');
|
11
11
|
var fs = require('fs');
|
12
12
|
var sucrase = require('sucrase');
|
13
|
+
var ui = require('@valbuild/ui');
|
13
14
|
var z = require('zod');
|
14
15
|
var internal = require('@valbuild/shared/internal');
|
15
16
|
var sizeOf = require('image-size');
|
@@ -692,7 +693,7 @@ const patchSourceFile = (sourceFile, patch$1) => {
|
|
692
693
|
return patch.applyPatch(sourceFile, ops$1, patch$1);
|
693
694
|
};
|
694
695
|
|
695
|
-
const readValFile = async (id, valConfigPath, runtime) => {
|
696
|
+
const readValFile = async (id, valConfigPath, runtime, options) => {
|
696
697
|
const context = runtime.newContext();
|
697
698
|
|
698
699
|
// avoid failures when console.log is called
|
@@ -710,8 +711,22 @@ const readValFile = async (id, valConfigPath, runtime) => {
|
|
710
711
|
const processHandle = context.newObject();
|
711
712
|
context.setProp(processHandle, "env", envHandle);
|
712
713
|
context.setProp(context.global, "process", processHandle);
|
714
|
+
const optionsHandle = context.newObject();
|
715
|
+
if (options) {
|
716
|
+
if (options.validate !== undefined) {
|
717
|
+
context.setProp(optionsHandle, "validate", context.newNumber(+options.validate));
|
718
|
+
}
|
719
|
+
if (options.source !== undefined) {
|
720
|
+
context.setProp(optionsHandle, "source", context.newNumber(+options.source));
|
721
|
+
}
|
722
|
+
if (options.schema !== undefined) {
|
723
|
+
context.setProp(optionsHandle, "schema", context.newNumber(+options.schema));
|
724
|
+
}
|
725
|
+
}
|
726
|
+
context.setProp(context.global, "__VAL_OPTIONS__", optionsHandle);
|
713
727
|
envHandle.dispose();
|
714
728
|
processHandle.dispose();
|
729
|
+
optionsHandle.dispose();
|
715
730
|
try {
|
716
731
|
const modulePath = `.${id}.val`;
|
717
732
|
const code = `import * as valModule from ${JSON.stringify(modulePath)};
|
@@ -719,12 +734,12 @@ import { Internal } from "@valbuild/core";
|
|
719
734
|
|
720
735
|
globalThis.valModule = {
|
721
736
|
id: valModule?.default && Internal.getValPath(valModule?.default),
|
722
|
-
schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
|
723
|
-
source: valModule?.default && Internal.getSource(valModule?.default),
|
724
|
-
validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
|
737
|
+
schema: !!globalThis['__VAL_OPTIONS__'].schema ? valModule?.default && Internal.getSchema(valModule?.default)?.serialize() : undefined,
|
738
|
+
source: !!globalThis['__VAL_OPTIONS__'].source ? valModule?.default && Internal.getSource(valModule?.default) : undefined,
|
739
|
+
validation: !!globalThis['__VAL_OPTIONS__'].validate ? valModule?.default && Internal.getSchema(valModule?.default)?.validate(
|
725
740
|
valModule?.default && Internal.getValPath(valModule?.default) || "/",
|
726
741
|
valModule?.default && Internal.getSource(valModule?.default)
|
727
|
-
),
|
742
|
+
) : undefined,
|
728
743
|
defaultExport: !!valModule?.default,
|
729
744
|
};
|
730
745
|
`;
|
@@ -755,9 +770,9 @@ globalThis.valModule = {
|
|
755
770
|
fatalErrors.push(`Wrong c.define id! Expected: '${id}', found: '${valModule.id}'`);
|
756
771
|
} else if (encodeURIComponent(valModule.id).replace(/%2F/g, "/") !== valModule.id) {
|
757
772
|
fatalErrors.push(`Invalid c.define id! Must be a web-safe path without escape characters, found: '${valModule.id}', which was encoded as: '${encodeURIComponent(valModule.id).replace("%2F", "/")}'`);
|
758
|
-
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined) {
|
773
|
+
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined && options.schema) {
|
759
774
|
fatalErrors.push(`Expected val id: '${id}' to have a schema`);
|
760
|
-
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined) {
|
775
|
+
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined && options.source) {
|
761
776
|
fatalErrors.push(`Expected val id: '${id}' to have a source`);
|
762
777
|
}
|
763
778
|
}
|
@@ -998,7 +1013,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
|
|
998
1013
|
}
|
999
1014
|
if (modulePath === "@valbuild/react/internal") {
|
1000
1015
|
return {
|
1001
|
-
value:
|
1016
|
+
value: `
|
1017
|
+
const useVal = () => { throw Error('Cannot use \\'useVal\\' in this type of file') };
|
1018
|
+
export function ValProvider() { throw Error('Cannot use \\'ValProvider\\' in this type of file') };
|
1019
|
+
export function ValRichText() { throw Error('Cannot use \\'ValRichText\\' in this type of file')};`
|
1002
1020
|
};
|
1003
1021
|
}
|
1004
1022
|
if (modulePath === "@valbuild/ui") {
|
@@ -1007,10 +1025,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
|
|
1007
1025
|
export const ValOverlay = () => {
|
1008
1026
|
throw Error("Cannot use 'ValOverlay' in this type of file")
|
1009
1027
|
};
|
1010
|
-
export const VAL_CSS_PATH = "
|
1011
|
-
export const VAL_APP_PATH = "
|
1012
|
-
export const VAL_APP_ID = "
|
1013
|
-
export const VAL_OVERLAY_ID = "
|
1028
|
+
export const VAL_CSS_PATH = "${ui.VAL_CSS_PATH}";
|
1029
|
+
export const VAL_APP_PATH = "${ui.VAL_CSS_PATH}";
|
1030
|
+
export const VAL_APP_ID = "${ui.VAL_APP_ID}";
|
1031
|
+
export const VAL_OVERLAY_ID = "${ui.VAL_OVERLAY_ID}";
|
1014
1032
|
export const IS_DEV = false;
|
1015
1033
|
`
|
1016
1034
|
};
|
@@ -1037,7 +1055,21 @@ export const IS_DEV = false;
|
|
1037
1055
|
}
|
1038
1056
|
if (modulePath.startsWith("react")) {
|
1039
1057
|
return {
|
1040
|
-
value:
|
1058
|
+
value: `
|
1059
|
+
export const createContext = () => new Proxy({}, { get() { return () => { throw new Error('Cannot use \\'createContext\\' in this file') } } } );
|
1060
|
+
export const useTransition = () => { throw Error('Cannot use \\'useTransition\\' in this type of file') };
|
1061
|
+
|
1062
|
+
export default new Proxy({}, {
|
1063
|
+
get(target, props) {
|
1064
|
+
// React.createContext might be called on top-level
|
1065
|
+
if (props === 'createContext') {
|
1066
|
+
return createContext;
|
1067
|
+
}
|
1068
|
+
return () => {
|
1069
|
+
throw new Error('Cannot import \\'react\\' in this file');
|
1070
|
+
}
|
1071
|
+
}
|
1072
|
+
})`
|
1041
1073
|
};
|
1042
1074
|
}
|
1043
1075
|
if (modulePath.includes("/ValNextProvider")) {
|
@@ -1168,8 +1200,12 @@ class Service {
|
|
1168
1200
|
this.runtime = runtime;
|
1169
1201
|
this.valConfigPath = valConfigPath || "./val.config";
|
1170
1202
|
}
|
1171
|
-
async get(moduleId, modulePath
|
1172
|
-
const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime
|
1203
|
+
async get(moduleId, modulePath, options) {
|
1204
|
+
const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime, options ?? {
|
1205
|
+
validate: true,
|
1206
|
+
source: true,
|
1207
|
+
schema: true
|
1208
|
+
});
|
1173
1209
|
if (valModule.source && valModule.schema) {
|
1174
1210
|
const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
|
1175
1211
|
const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
|
@@ -1434,8 +1470,11 @@ class ValServer {
|
|
1434
1470
|
if (fp.result.isErr(ensureRes)) {
|
1435
1471
|
return ensureRes.error;
|
1436
1472
|
}
|
1437
|
-
const moduleIds = this.getAllModules(treePath);
|
1438
1473
|
const applyPatches = query.patch === "true";
|
1474
|
+
const execValidations = query.validate === "true";
|
1475
|
+
const includeSource = query.source === "true";
|
1476
|
+
const includeSchema = query.schema === "true";
|
1477
|
+
const moduleIds = this.getAllModules(treePath);
|
1439
1478
|
let {
|
1440
1479
|
patchIdsByModuleId,
|
1441
1480
|
patchesById,
|
@@ -1455,7 +1494,7 @@ class ValServer {
|
|
1455
1494
|
fileUpdates = res.value.fileUpdates;
|
1456
1495
|
}
|
1457
1496
|
const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
|
1458
|
-
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1497
|
+
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
|
1459
1498
|
}));
|
1460
1499
|
const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
|
1461
1500
|
const module = {
|
@@ -1509,13 +1548,17 @@ class ValServer {
|
|
1509
1548
|
|
1510
1549
|
/* */
|
1511
1550
|
|
1512
|
-
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1513
|
-
const serializedModuleContent = await this.getModule(moduleId
|
1514
|
-
|
1515
|
-
|
1551
|
+
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, validate, includeSource, includeSchema) {
|
1552
|
+
const serializedModuleContent = await this.getModule(moduleId, {
|
1553
|
+
validate: validate,
|
1554
|
+
source: includeSource,
|
1555
|
+
schema: includeSchema
|
1556
|
+
});
|
1516
1557
|
if (!applyPatches) {
|
1517
1558
|
return serializedModuleContent;
|
1518
1559
|
}
|
1560
|
+
const schema = serializedModuleContent.schema;
|
1561
|
+
const maybeSource = serializedModuleContent.source;
|
1519
1562
|
if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
|
1520
1563
|
return serializedModuleContent;
|
1521
1564
|
}
|
@@ -1551,17 +1594,19 @@ class ValServer {
|
|
1551
1594
|
};
|
1552
1595
|
}
|
1553
1596
|
}
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1597
|
+
if (validate) {
|
1598
|
+
const validationErrors = core.deserializeSchema(schema).validate(moduleId, source);
|
1599
|
+
if (validationErrors) {
|
1600
|
+
const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
|
1601
|
+
return {
|
1602
|
+
path: moduleId,
|
1603
|
+
schema,
|
1604
|
+
source,
|
1605
|
+
errors: revalidated && {
|
1606
|
+
validation: revalidated
|
1607
|
+
}
|
1608
|
+
};
|
1609
|
+
}
|
1565
1610
|
}
|
1566
1611
|
return {
|
1567
1612
|
path: moduleId,
|
@@ -1576,7 +1621,7 @@ class ValServer {
|
|
1576
1621
|
// The reason is that validate will be called inside QuickJS (in the future, hopefully),
|
1577
1622
|
// which does not have access to the filesystem, at least not at the time of writing this comment.
|
1578
1623
|
// If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
|
1579
|
-
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies,
|
1624
|
+
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
|
1580
1625
|
const revalidatedValidationErrors = {};
|
1581
1626
|
for (const pathStr in validationErrors) {
|
1582
1627
|
const errorSourcePath = pathStr;
|
@@ -1598,7 +1643,7 @@ class ValServer {
|
|
1598
1643
|
if (updatedFileMetadata) {
|
1599
1644
|
const fileRes = await this.getFiles(fileRef, {
|
1600
1645
|
sha256: updatedFileMetadata.sha256
|
1601
|
-
}, cookies,
|
1646
|
+
}, cookies, reqHeaders);
|
1602
1647
|
if (fileRes.status === 200 && fileRes.body) {
|
1603
1648
|
const res = new Response(fileRes.body);
|
1604
1649
|
fileBuffer = Buffer.from(await res.arrayBuffer());
|
@@ -1608,14 +1653,23 @@ class ValServer {
|
|
1608
1653
|
});
|
1609
1654
|
}
|
1610
1655
|
}
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1656
|
+
// try fetch file directly via http
|
1657
|
+
if (fileRef.startsWith("/public")) {
|
1658
|
+
const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
|
1659
|
+
const fileUrl = fileRef.slice("/public".length);
|
1660
|
+
const fetchRes = await fetch(new URL(fileUrl, host));
|
1661
|
+
if (fetchRes.status === 200) {
|
1662
|
+
fileBuffer = Buffer.from(await fetchRes.arrayBuffer());
|
1663
|
+
} else {
|
1664
|
+
console.error("Val: unexpected error while fetching image / file:", fileRef, {
|
1665
|
+
error: {
|
1666
|
+
status: fetchRes.status,
|
1667
|
+
url: fetchRes.url
|
1668
|
+
}
|
1617
1669
|
});
|
1618
1670
|
}
|
1671
|
+
} else {
|
1672
|
+
console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
|
1619
1673
|
}
|
1620
1674
|
if (!fileBuffer) {
|
1621
1675
|
revalidatedValidationErrors[errorSourcePath].push({
|
@@ -1762,7 +1816,7 @@ class ValServer {
|
|
1762
1816
|
const moduleId = moduleIdStr;
|
1763
1817
|
const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
|
1764
1818
|
// TODO: refine to ModuleId and PatchId when parsing
|
1765
|
-
patchIdsByModuleId, patchesById, fileUpdates, true,
|
1819
|
+
patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
|
1766
1820
|
if (serializedModuleContent.errors) {
|
1767
1821
|
validationErrorsByModuleId[moduleId] = serializedModuleContent;
|
1768
1822
|
}
|
@@ -2266,8 +2320,8 @@ class LocalValServer extends ValServer {
|
|
2266
2320
|
// No RemoteFS so nothing to ensure
|
2267
2321
|
return fp.result.ok(undefined);
|
2268
2322
|
}
|
2269
|
-
getModule(moduleId) {
|
2270
|
-
return this.options.service.get(moduleId);
|
2323
|
+
getModule(moduleId, options) {
|
2324
|
+
return this.options.service.get(moduleId, "", options);
|
2271
2325
|
}
|
2272
2326
|
async execCommit(patches) {
|
2273
2327
|
for (const [patchId, moduleId, patch] of patches) {
|
@@ -2555,11 +2609,11 @@ class ProxyValServer extends ValServer {
|
|
2555
2609
|
|
2556
2610
|
/** Remote FS dependent methods: */
|
2557
2611
|
|
2558
|
-
async getModule(moduleId) {
|
2612
|
+
async getModule(moduleId, options) {
|
2559
2613
|
if (!this.lazyService) {
|
2560
2614
|
this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
|
2561
2615
|
}
|
2562
|
-
return this.lazyService.get(moduleId);
|
2616
|
+
return this.lazyService.get(moduleId, "", options);
|
2563
2617
|
}
|
2564
2618
|
execCommit(patches, cookies) {
|
2565
2619
|
return withAuth(this.options.valSecret, cookies, "execCommit", async ({
|
@@ -3421,7 +3475,8 @@ function createValApiRouter(route, valServerPromise, convert) {
|
|
3421
3475
|
return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
|
3422
3476
|
patch: url.searchParams.get("patch") || undefined,
|
3423
3477
|
schema: url.searchParams.get("schema") || undefined,
|
3424
|
-
source: url.searchParams.get("source") || undefined
|
3478
|
+
source: url.searchParams.get("source") || undefined,
|
3479
|
+
validate: url.searchParams.get("validate") || undefined
|
3425
3480
|
}, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
|
3426
3481
|
} else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
|
3427
3482
|
return withTreePath(path, PATCHES_PATH_PREFIX)(async () => convert(await valServer.getPatches({
|
@@ -7,6 +7,7 @@ import * as path from 'path';
|
|
7
7
|
import path__default from 'path';
|
8
8
|
import fs, { promises } from 'fs';
|
9
9
|
import { transform } from 'sucrase';
|
10
|
+
import { VAL_CSS_PATH, VAL_APP_ID, VAL_OVERLAY_ID } from '@valbuild/ui';
|
10
11
|
import z, { z as z$1 } from 'zod';
|
11
12
|
import { MIME_TYPES_TO_EXT, filenameToMimeType, VAL_ENABLE_COOKIE_NAME, VAL_STATE_COOKIE as VAL_STATE_COOKIE$1, VAL_SESSION_COOKIE as VAL_SESSION_COOKIE$1 } from '@valbuild/shared/internal';
|
12
13
|
import sizeOf from 'image-size';
|
@@ -661,7 +662,7 @@ const patchSourceFile = (sourceFile, patch) => {
|
|
661
662
|
return applyPatch(sourceFile, ops$1, patch);
|
662
663
|
};
|
663
664
|
|
664
|
-
const readValFile = async (id, valConfigPath, runtime) => {
|
665
|
+
const readValFile = async (id, valConfigPath, runtime, options) => {
|
665
666
|
const context = runtime.newContext();
|
666
667
|
|
667
668
|
// avoid failures when console.log is called
|
@@ -679,8 +680,22 @@ const readValFile = async (id, valConfigPath, runtime) => {
|
|
679
680
|
const processHandle = context.newObject();
|
680
681
|
context.setProp(processHandle, "env", envHandle);
|
681
682
|
context.setProp(context.global, "process", processHandle);
|
683
|
+
const optionsHandle = context.newObject();
|
684
|
+
if (options) {
|
685
|
+
if (options.validate !== undefined) {
|
686
|
+
context.setProp(optionsHandle, "validate", context.newNumber(+options.validate));
|
687
|
+
}
|
688
|
+
if (options.source !== undefined) {
|
689
|
+
context.setProp(optionsHandle, "source", context.newNumber(+options.source));
|
690
|
+
}
|
691
|
+
if (options.schema !== undefined) {
|
692
|
+
context.setProp(optionsHandle, "schema", context.newNumber(+options.schema));
|
693
|
+
}
|
694
|
+
}
|
695
|
+
context.setProp(context.global, "__VAL_OPTIONS__", optionsHandle);
|
682
696
|
envHandle.dispose();
|
683
697
|
processHandle.dispose();
|
698
|
+
optionsHandle.dispose();
|
684
699
|
try {
|
685
700
|
const modulePath = `.${id}.val`;
|
686
701
|
const code = `import * as valModule from ${JSON.stringify(modulePath)};
|
@@ -688,12 +703,12 @@ import { Internal } from "@valbuild/core";
|
|
688
703
|
|
689
704
|
globalThis.valModule = {
|
690
705
|
id: valModule?.default && Internal.getValPath(valModule?.default),
|
691
|
-
schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
|
692
|
-
source: valModule?.default && Internal.getSource(valModule?.default),
|
693
|
-
validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
|
706
|
+
schema: !!globalThis['__VAL_OPTIONS__'].schema ? valModule?.default && Internal.getSchema(valModule?.default)?.serialize() : undefined,
|
707
|
+
source: !!globalThis['__VAL_OPTIONS__'].source ? valModule?.default && Internal.getSource(valModule?.default) : undefined,
|
708
|
+
validation: !!globalThis['__VAL_OPTIONS__'].validate ? valModule?.default && Internal.getSchema(valModule?.default)?.validate(
|
694
709
|
valModule?.default && Internal.getValPath(valModule?.default) || "/",
|
695
710
|
valModule?.default && Internal.getSource(valModule?.default)
|
696
|
-
),
|
711
|
+
) : undefined,
|
697
712
|
defaultExport: !!valModule?.default,
|
698
713
|
};
|
699
714
|
`;
|
@@ -724,9 +739,9 @@ globalThis.valModule = {
|
|
724
739
|
fatalErrors.push(`Wrong c.define id! Expected: '${id}', found: '${valModule.id}'`);
|
725
740
|
} else if (encodeURIComponent(valModule.id).replace(/%2F/g, "/") !== valModule.id) {
|
726
741
|
fatalErrors.push(`Invalid c.define id! Must be a web-safe path without escape characters, found: '${valModule.id}', which was encoded as: '${encodeURIComponent(valModule.id).replace("%2F", "/")}'`);
|
727
|
-
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined) {
|
742
|
+
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined && options.schema) {
|
728
743
|
fatalErrors.push(`Expected val id: '${id}' to have a schema`);
|
729
|
-
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined) {
|
744
|
+
} else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined && options.source) {
|
730
745
|
fatalErrors.push(`Expected val id: '${id}' to have a source`);
|
731
746
|
}
|
732
747
|
}
|
@@ -967,7 +982,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
|
|
967
982
|
}
|
968
983
|
if (modulePath === "@valbuild/react/internal") {
|
969
984
|
return {
|
970
|
-
value:
|
985
|
+
value: `
|
986
|
+
const useVal = () => { throw Error('Cannot use \\'useVal\\' in this type of file') };
|
987
|
+
export function ValProvider() { throw Error('Cannot use \\'ValProvider\\' in this type of file') };
|
988
|
+
export function ValRichText() { throw Error('Cannot use \\'ValRichText\\' in this type of file')};`
|
971
989
|
};
|
972
990
|
}
|
973
991
|
if (modulePath === "@valbuild/ui") {
|
@@ -976,10 +994,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
|
|
976
994
|
export const ValOverlay = () => {
|
977
995
|
throw Error("Cannot use 'ValOverlay' in this type of file")
|
978
996
|
};
|
979
|
-
export const VAL_CSS_PATH = "
|
980
|
-
export const VAL_APP_PATH = "
|
981
|
-
export const VAL_APP_ID = "
|
982
|
-
export const VAL_OVERLAY_ID = "
|
997
|
+
export const VAL_CSS_PATH = "${VAL_CSS_PATH}";
|
998
|
+
export const VAL_APP_PATH = "${VAL_CSS_PATH}";
|
999
|
+
export const VAL_APP_ID = "${VAL_APP_ID}";
|
1000
|
+
export const VAL_OVERLAY_ID = "${VAL_OVERLAY_ID}";
|
983
1001
|
export const IS_DEV = false;
|
984
1002
|
`
|
985
1003
|
};
|
@@ -1006,7 +1024,21 @@ export const IS_DEV = false;
|
|
1006
1024
|
}
|
1007
1025
|
if (modulePath.startsWith("react")) {
|
1008
1026
|
return {
|
1009
|
-
value:
|
1027
|
+
value: `
|
1028
|
+
export const createContext = () => new Proxy({}, { get() { return () => { throw new Error('Cannot use \\'createContext\\' in this file') } } } );
|
1029
|
+
export const useTransition = () => { throw Error('Cannot use \\'useTransition\\' in this type of file') };
|
1030
|
+
|
1031
|
+
export default new Proxy({}, {
|
1032
|
+
get(target, props) {
|
1033
|
+
// React.createContext might be called on top-level
|
1034
|
+
if (props === 'createContext') {
|
1035
|
+
return createContext;
|
1036
|
+
}
|
1037
|
+
return () => {
|
1038
|
+
throw new Error('Cannot import \\'react\\' in this file');
|
1039
|
+
}
|
1040
|
+
}
|
1041
|
+
})`
|
1010
1042
|
};
|
1011
1043
|
}
|
1012
1044
|
if (modulePath.includes("/ValNextProvider")) {
|
@@ -1137,8 +1169,12 @@ class Service {
|
|
1137
1169
|
this.runtime = runtime;
|
1138
1170
|
this.valConfigPath = valConfigPath || "./val.config";
|
1139
1171
|
}
|
1140
|
-
async get(moduleId, modulePath
|
1141
|
-
const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime
|
1172
|
+
async get(moduleId, modulePath, options) {
|
1173
|
+
const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime, options ?? {
|
1174
|
+
validate: true,
|
1175
|
+
source: true,
|
1176
|
+
schema: true
|
1177
|
+
});
|
1142
1178
|
if (valModule.source && valModule.schema) {
|
1143
1179
|
const resolved = Internal.resolvePath(modulePath, valModule.source, valModule.schema);
|
1144
1180
|
const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
|
@@ -1403,8 +1439,11 @@ class ValServer {
|
|
1403
1439
|
if (result.isErr(ensureRes)) {
|
1404
1440
|
return ensureRes.error;
|
1405
1441
|
}
|
1406
|
-
const moduleIds = this.getAllModules(treePath);
|
1407
1442
|
const applyPatches = query.patch === "true";
|
1443
|
+
const execValidations = query.validate === "true";
|
1444
|
+
const includeSource = query.source === "true";
|
1445
|
+
const includeSchema = query.schema === "true";
|
1446
|
+
const moduleIds = this.getAllModules(treePath);
|
1408
1447
|
let {
|
1409
1448
|
patchIdsByModuleId,
|
1410
1449
|
patchesById,
|
@@ -1424,7 +1463,7 @@ class ValServer {
|
|
1424
1463
|
fileUpdates = res.value.fileUpdates;
|
1425
1464
|
}
|
1426
1465
|
const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
|
1427
|
-
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1466
|
+
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
|
1428
1467
|
}));
|
1429
1468
|
const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
|
1430
1469
|
const module = {
|
@@ -1478,13 +1517,17 @@ class ValServer {
|
|
1478
1517
|
|
1479
1518
|
/* */
|
1480
1519
|
|
1481
|
-
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1482
|
-
const serializedModuleContent = await this.getModule(moduleId
|
1483
|
-
|
1484
|
-
|
1520
|
+
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, validate, includeSource, includeSchema) {
|
1521
|
+
const serializedModuleContent = await this.getModule(moduleId, {
|
1522
|
+
validate: validate,
|
1523
|
+
source: includeSource,
|
1524
|
+
schema: includeSchema
|
1525
|
+
});
|
1485
1526
|
if (!applyPatches) {
|
1486
1527
|
return serializedModuleContent;
|
1487
1528
|
}
|
1529
|
+
const schema = serializedModuleContent.schema;
|
1530
|
+
const maybeSource = serializedModuleContent.source;
|
1488
1531
|
if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
|
1489
1532
|
return serializedModuleContent;
|
1490
1533
|
}
|
@@ -1520,17 +1563,19 @@ class ValServer {
|
|
1520
1563
|
};
|
1521
1564
|
}
|
1522
1565
|
}
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1566
|
+
if (validate) {
|
1567
|
+
const validationErrors = deserializeSchema(schema).validate(moduleId, source);
|
1568
|
+
if (validationErrors) {
|
1569
|
+
const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
|
1570
|
+
return {
|
1571
|
+
path: moduleId,
|
1572
|
+
schema,
|
1573
|
+
source,
|
1574
|
+
errors: revalidated && {
|
1575
|
+
validation: revalidated
|
1576
|
+
}
|
1577
|
+
};
|
1578
|
+
}
|
1534
1579
|
}
|
1535
1580
|
return {
|
1536
1581
|
path: moduleId,
|
@@ -1545,7 +1590,7 @@ class ValServer {
|
|
1545
1590
|
// The reason is that validate will be called inside QuickJS (in the future, hopefully),
|
1546
1591
|
// which does not have access to the filesystem, at least not at the time of writing this comment.
|
1547
1592
|
// If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
|
1548
|
-
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies,
|
1593
|
+
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
|
1549
1594
|
const revalidatedValidationErrors = {};
|
1550
1595
|
for (const pathStr in validationErrors) {
|
1551
1596
|
const errorSourcePath = pathStr;
|
@@ -1567,7 +1612,7 @@ class ValServer {
|
|
1567
1612
|
if (updatedFileMetadata) {
|
1568
1613
|
const fileRes = await this.getFiles(fileRef, {
|
1569
1614
|
sha256: updatedFileMetadata.sha256
|
1570
|
-
}, cookies,
|
1615
|
+
}, cookies, reqHeaders);
|
1571
1616
|
if (fileRes.status === 200 && fileRes.body) {
|
1572
1617
|
const res = new Response(fileRes.body);
|
1573
1618
|
fileBuffer = Buffer.from(await res.arrayBuffer());
|
@@ -1577,14 +1622,23 @@ class ValServer {
|
|
1577
1622
|
});
|
1578
1623
|
}
|
1579
1624
|
}
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1625
|
+
// try fetch file directly via http
|
1626
|
+
if (fileRef.startsWith("/public")) {
|
1627
|
+
const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
|
1628
|
+
const fileUrl = fileRef.slice("/public".length);
|
1629
|
+
const fetchRes = await fetch(new URL(fileUrl, host));
|
1630
|
+
if (fetchRes.status === 200) {
|
1631
|
+
fileBuffer = Buffer.from(await fetchRes.arrayBuffer());
|
1632
|
+
} else {
|
1633
|
+
console.error("Val: unexpected error while fetching image / file:", fileRef, {
|
1634
|
+
error: {
|
1635
|
+
status: fetchRes.status,
|
1636
|
+
url: fetchRes.url
|
1637
|
+
}
|
1586
1638
|
});
|
1587
1639
|
}
|
1640
|
+
} else {
|
1641
|
+
console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
|
1588
1642
|
}
|
1589
1643
|
if (!fileBuffer) {
|
1590
1644
|
revalidatedValidationErrors[errorSourcePath].push({
|
@@ -1731,7 +1785,7 @@ class ValServer {
|
|
1731
1785
|
const moduleId = moduleIdStr;
|
1732
1786
|
const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
|
1733
1787
|
// TODO: refine to ModuleId and PatchId when parsing
|
1734
|
-
patchIdsByModuleId, patchesById, fileUpdates, true,
|
1788
|
+
patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
|
1735
1789
|
if (serializedModuleContent.errors) {
|
1736
1790
|
validationErrorsByModuleId[moduleId] = serializedModuleContent;
|
1737
1791
|
}
|
@@ -2235,8 +2289,8 @@ class LocalValServer extends ValServer {
|
|
2235
2289
|
// No RemoteFS so nothing to ensure
|
2236
2290
|
return result.ok(undefined);
|
2237
2291
|
}
|
2238
|
-
getModule(moduleId) {
|
2239
|
-
return this.options.service.get(moduleId);
|
2292
|
+
getModule(moduleId, options) {
|
2293
|
+
return this.options.service.get(moduleId, "", options);
|
2240
2294
|
}
|
2241
2295
|
async execCommit(patches) {
|
2242
2296
|
for (const [patchId, moduleId, patch] of patches) {
|
@@ -2524,11 +2578,11 @@ class ProxyValServer extends ValServer {
|
|
2524
2578
|
|
2525
2579
|
/** Remote FS dependent methods: */
|
2526
2580
|
|
2527
|
-
async getModule(moduleId) {
|
2581
|
+
async getModule(moduleId, options) {
|
2528
2582
|
if (!this.lazyService) {
|
2529
2583
|
this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
|
2530
2584
|
}
|
2531
|
-
return this.lazyService.get(moduleId);
|
2585
|
+
return this.lazyService.get(moduleId, "", options);
|
2532
2586
|
}
|
2533
2587
|
execCommit(patches, cookies) {
|
2534
2588
|
return withAuth(this.options.valSecret, cookies, "execCommit", async ({
|
@@ -3390,7 +3444,8 @@ function createValApiRouter(route, valServerPromise, convert) {
|
|
3390
3444
|
return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
|
3391
3445
|
patch: url.searchParams.get("patch") || undefined,
|
3392
3446
|
schema: url.searchParams.get("schema") || undefined,
|
3393
|
-
source: url.searchParams.get("source") || undefined
|
3447
|
+
source: url.searchParams.get("source") || undefined,
|
3448
|
+
validate: url.searchParams.get("validate") || undefined
|
3394
3449
|
}, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
|
3395
3450
|
} else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
|
3396
3451
|
return withTreePath(path, PATCHES_PATH_PREFIX)(async () => convert(await valServer.getPatches({
|
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.60.
|
15
|
+
"version": "0.60.19",
|
16
16
|
"scripts": {
|
17
17
|
"typecheck": "tsc --noEmit",
|
18
18
|
"test": "jest",
|
@@ -24,9 +24,9 @@
|
|
24
24
|
"concurrently": "^7.6.0"
|
25
25
|
},
|
26
26
|
"dependencies": {
|
27
|
-
"@valbuild/core": "~0.60.
|
28
|
-
"@valbuild/shared": "~0.60.
|
29
|
-
"@valbuild/ui": "~0.60.
|
27
|
+
"@valbuild/core": "~0.60.19",
|
28
|
+
"@valbuild/shared": "~0.60.19",
|
29
|
+
"@valbuild/ui": "~0.60.19",
|
30
30
|
"express": "^4.18.2",
|
31
31
|
"image-size": "^1.0.2",
|
32
32
|
"minimatch": "^3.0.4",
|