@valbuild/server 0.60.17 → 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 +111 -46
- package/dist/valbuild-server.cjs.prod.js +111 -46
- package/dist/valbuild-server.esm.js +111 -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")) {
|
@@ -1045,6 +1077,11 @@ export const IS_DEV = false;
|
|
1045
1077
|
value: "export const ValNextProvider = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValNextProvider' in this file`) } } } )"
|
1046
1078
|
};
|
1047
1079
|
}
|
1080
|
+
if (modulePath.includes("/ValContext")) {
|
1081
|
+
return {
|
1082
|
+
value: "export const useValEvents = () => { throw Error(`Cannot use 'useValEvents' in this type of file`) }; export const ValContext = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValContext' in this file`) } } } ) export const ValEvents = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValEvents' in this file`) } } } )"
|
1083
|
+
};
|
1084
|
+
}
|
1048
1085
|
if (modulePath.includes("/ValImage")) {
|
1049
1086
|
return {
|
1050
1087
|
value: "export const ValImage = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValImage' in this file`) } } } )"
|
@@ -1110,6 +1147,11 @@ export const IS_DEV = false;
|
|
1110
1147
|
value: requestedName
|
1111
1148
|
};
|
1112
1149
|
}
|
1150
|
+
if (requestedName.includes("/ValContext")) {
|
1151
|
+
return {
|
1152
|
+
value: requestedName
|
1153
|
+
};
|
1154
|
+
}
|
1113
1155
|
if (requestedName.includes("/ValImage")) {
|
1114
1156
|
return {
|
1115
1157
|
value: requestedName
|
@@ -1158,8 +1200,12 @@ class Service {
|
|
1158
1200
|
this.runtime = runtime;
|
1159
1201
|
this.valConfigPath = valConfigPath || "./val.config";
|
1160
1202
|
}
|
1161
|
-
async get(moduleId, modulePath
|
1162
|
-
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
|
+
});
|
1163
1209
|
if (valModule.source && valModule.schema) {
|
1164
1210
|
const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
|
1165
1211
|
const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
|
@@ -1424,8 +1470,11 @@ class ValServer {
|
|
1424
1470
|
if (fp.result.isErr(ensureRes)) {
|
1425
1471
|
return ensureRes.error;
|
1426
1472
|
}
|
1427
|
-
const moduleIds = this.getAllModules(treePath);
|
1428
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);
|
1429
1478
|
let {
|
1430
1479
|
patchIdsByModuleId,
|
1431
1480
|
patchesById,
|
@@ -1445,7 +1494,7 @@ class ValServer {
|
|
1445
1494
|
fileUpdates = res.value.fileUpdates;
|
1446
1495
|
}
|
1447
1496
|
const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
|
1448
|
-
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1497
|
+
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
|
1449
1498
|
}));
|
1450
1499
|
const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
|
1451
1500
|
const module = {
|
@@ -1499,13 +1548,17 @@ class ValServer {
|
|
1499
1548
|
|
1500
1549
|
/* */
|
1501
1550
|
|
1502
|
-
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1503
|
-
const serializedModuleContent = await this.getModule(moduleId
|
1504
|
-
|
1505
|
-
|
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
|
+
});
|
1506
1557
|
if (!applyPatches) {
|
1507
1558
|
return serializedModuleContent;
|
1508
1559
|
}
|
1560
|
+
const schema = serializedModuleContent.schema;
|
1561
|
+
const maybeSource = serializedModuleContent.source;
|
1509
1562
|
if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
|
1510
1563
|
return serializedModuleContent;
|
1511
1564
|
}
|
@@ -1541,17 +1594,19 @@ class ValServer {
|
|
1541
1594
|
};
|
1542
1595
|
}
|
1543
1596
|
}
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
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
|
+
}
|
1555
1610
|
}
|
1556
1611
|
return {
|
1557
1612
|
path: moduleId,
|
@@ -1566,7 +1621,7 @@ class ValServer {
|
|
1566
1621
|
// The reason is that validate will be called inside QuickJS (in the future, hopefully),
|
1567
1622
|
// which does not have access to the filesystem, at least not at the time of writing this comment.
|
1568
1623
|
// If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
|
1569
|
-
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies,
|
1624
|
+
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
|
1570
1625
|
const revalidatedValidationErrors = {};
|
1571
1626
|
for (const pathStr in validationErrors) {
|
1572
1627
|
const errorSourcePath = pathStr;
|
@@ -1588,7 +1643,7 @@ class ValServer {
|
|
1588
1643
|
if (updatedFileMetadata) {
|
1589
1644
|
const fileRes = await this.getFiles(fileRef, {
|
1590
1645
|
sha256: updatedFileMetadata.sha256
|
1591
|
-
}, cookies,
|
1646
|
+
}, cookies, reqHeaders);
|
1592
1647
|
if (fileRes.status === 200 && fileRes.body) {
|
1593
1648
|
const res = new Response(fileRes.body);
|
1594
1649
|
fileBuffer = Buffer.from(await res.arrayBuffer());
|
@@ -1598,14 +1653,23 @@ class ValServer {
|
|
1598
1653
|
});
|
1599
1654
|
}
|
1600
1655
|
}
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
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
|
+
}
|
1607
1669
|
});
|
1608
1670
|
}
|
1671
|
+
} else {
|
1672
|
+
console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
|
1609
1673
|
}
|
1610
1674
|
if (!fileBuffer) {
|
1611
1675
|
revalidatedValidationErrors[errorSourcePath].push({
|
@@ -1752,7 +1816,7 @@ class ValServer {
|
|
1752
1816
|
const moduleId = moduleIdStr;
|
1753
1817
|
const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
|
1754
1818
|
// TODO: refine to ModuleId and PatchId when parsing
|
1755
|
-
patchIdsByModuleId, patchesById, fileUpdates, true,
|
1819
|
+
patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
|
1756
1820
|
if (serializedModuleContent.errors) {
|
1757
1821
|
validationErrorsByModuleId[moduleId] = serializedModuleContent;
|
1758
1822
|
}
|
@@ -2256,8 +2320,8 @@ class LocalValServer extends ValServer {
|
|
2256
2320
|
// No RemoteFS so nothing to ensure
|
2257
2321
|
return fp.result.ok(undefined);
|
2258
2322
|
}
|
2259
|
-
getModule(moduleId) {
|
2260
|
-
return this.options.service.get(moduleId);
|
2323
|
+
getModule(moduleId, options) {
|
2324
|
+
return this.options.service.get(moduleId, "", options);
|
2261
2325
|
}
|
2262
2326
|
async execCommit(patches) {
|
2263
2327
|
for (const [patchId, moduleId, patch] of patches) {
|
@@ -2545,11 +2609,11 @@ class ProxyValServer extends ValServer {
|
|
2545
2609
|
|
2546
2610
|
/** Remote FS dependent methods: */
|
2547
2611
|
|
2548
|
-
async getModule(moduleId) {
|
2612
|
+
async getModule(moduleId, options) {
|
2549
2613
|
if (!this.lazyService) {
|
2550
2614
|
this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
|
2551
2615
|
}
|
2552
|
-
return this.lazyService.get(moduleId);
|
2616
|
+
return this.lazyService.get(moduleId, "", options);
|
2553
2617
|
}
|
2554
2618
|
execCommit(patches, cookies) {
|
2555
2619
|
return withAuth(this.options.valSecret, cookies, "execCommit", async ({
|
@@ -3411,7 +3475,8 @@ function createValApiRouter(route, valServerPromise, convert) {
|
|
3411
3475
|
return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
|
3412
3476
|
patch: url.searchParams.get("patch") || undefined,
|
3413
3477
|
schema: url.searchParams.get("schema") || undefined,
|
3414
|
-
source: url.searchParams.get("source") || undefined
|
3478
|
+
source: url.searchParams.get("source") || undefined,
|
3479
|
+
validate: url.searchParams.get("validate") || undefined
|
3415
3480
|
}, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
|
3416
3481
|
} else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
|
3417
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")) {
|
@@ -1045,6 +1077,11 @@ export const IS_DEV = false;
|
|
1045
1077
|
value: "export const ValNextProvider = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValNextProvider' in this file`) } } } )"
|
1046
1078
|
};
|
1047
1079
|
}
|
1080
|
+
if (modulePath.includes("/ValContext")) {
|
1081
|
+
return {
|
1082
|
+
value: "export const useValEvents = () => { throw Error(`Cannot use 'useValEvents' in this type of file`) }; export const ValContext = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValContext' in this file`) } } } ) export const ValEvents = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValEvents' in this file`) } } } )"
|
1083
|
+
};
|
1084
|
+
}
|
1048
1085
|
if (modulePath.includes("/ValImage")) {
|
1049
1086
|
return {
|
1050
1087
|
value: "export const ValImage = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValImage' in this file`) } } } )"
|
@@ -1110,6 +1147,11 @@ export const IS_DEV = false;
|
|
1110
1147
|
value: requestedName
|
1111
1148
|
};
|
1112
1149
|
}
|
1150
|
+
if (requestedName.includes("/ValContext")) {
|
1151
|
+
return {
|
1152
|
+
value: requestedName
|
1153
|
+
};
|
1154
|
+
}
|
1113
1155
|
if (requestedName.includes("/ValImage")) {
|
1114
1156
|
return {
|
1115
1157
|
value: requestedName
|
@@ -1158,8 +1200,12 @@ class Service {
|
|
1158
1200
|
this.runtime = runtime;
|
1159
1201
|
this.valConfigPath = valConfigPath || "./val.config";
|
1160
1202
|
}
|
1161
|
-
async get(moduleId, modulePath
|
1162
|
-
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
|
+
});
|
1163
1209
|
if (valModule.source && valModule.schema) {
|
1164
1210
|
const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
|
1165
1211
|
const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
|
@@ -1424,8 +1470,11 @@ class ValServer {
|
|
1424
1470
|
if (fp.result.isErr(ensureRes)) {
|
1425
1471
|
return ensureRes.error;
|
1426
1472
|
}
|
1427
|
-
const moduleIds = this.getAllModules(treePath);
|
1428
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);
|
1429
1478
|
let {
|
1430
1479
|
patchIdsByModuleId,
|
1431
1480
|
patchesById,
|
@@ -1445,7 +1494,7 @@ class ValServer {
|
|
1445
1494
|
fileUpdates = res.value.fileUpdates;
|
1446
1495
|
}
|
1447
1496
|
const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
|
1448
|
-
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1497
|
+
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
|
1449
1498
|
}));
|
1450
1499
|
const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
|
1451
1500
|
const module = {
|
@@ -1499,13 +1548,17 @@ class ValServer {
|
|
1499
1548
|
|
1500
1549
|
/* */
|
1501
1550
|
|
1502
|
-
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1503
|
-
const serializedModuleContent = await this.getModule(moduleId
|
1504
|
-
|
1505
|
-
|
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
|
+
});
|
1506
1557
|
if (!applyPatches) {
|
1507
1558
|
return serializedModuleContent;
|
1508
1559
|
}
|
1560
|
+
const schema = serializedModuleContent.schema;
|
1561
|
+
const maybeSource = serializedModuleContent.source;
|
1509
1562
|
if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
|
1510
1563
|
return serializedModuleContent;
|
1511
1564
|
}
|
@@ -1541,17 +1594,19 @@ class ValServer {
|
|
1541
1594
|
};
|
1542
1595
|
}
|
1543
1596
|
}
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
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
|
+
}
|
1555
1610
|
}
|
1556
1611
|
return {
|
1557
1612
|
path: moduleId,
|
@@ -1566,7 +1621,7 @@ class ValServer {
|
|
1566
1621
|
// The reason is that validate will be called inside QuickJS (in the future, hopefully),
|
1567
1622
|
// which does not have access to the filesystem, at least not at the time of writing this comment.
|
1568
1623
|
// If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
|
1569
|
-
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies,
|
1624
|
+
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
|
1570
1625
|
const revalidatedValidationErrors = {};
|
1571
1626
|
for (const pathStr in validationErrors) {
|
1572
1627
|
const errorSourcePath = pathStr;
|
@@ -1588,7 +1643,7 @@ class ValServer {
|
|
1588
1643
|
if (updatedFileMetadata) {
|
1589
1644
|
const fileRes = await this.getFiles(fileRef, {
|
1590
1645
|
sha256: updatedFileMetadata.sha256
|
1591
|
-
}, cookies,
|
1646
|
+
}, cookies, reqHeaders);
|
1592
1647
|
if (fileRes.status === 200 && fileRes.body) {
|
1593
1648
|
const res = new Response(fileRes.body);
|
1594
1649
|
fileBuffer = Buffer.from(await res.arrayBuffer());
|
@@ -1598,14 +1653,23 @@ class ValServer {
|
|
1598
1653
|
});
|
1599
1654
|
}
|
1600
1655
|
}
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
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
|
+
}
|
1607
1669
|
});
|
1608
1670
|
}
|
1671
|
+
} else {
|
1672
|
+
console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
|
1609
1673
|
}
|
1610
1674
|
if (!fileBuffer) {
|
1611
1675
|
revalidatedValidationErrors[errorSourcePath].push({
|
@@ -1752,7 +1816,7 @@ class ValServer {
|
|
1752
1816
|
const moduleId = moduleIdStr;
|
1753
1817
|
const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
|
1754
1818
|
// TODO: refine to ModuleId and PatchId when parsing
|
1755
|
-
patchIdsByModuleId, patchesById, fileUpdates, true,
|
1819
|
+
patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
|
1756
1820
|
if (serializedModuleContent.errors) {
|
1757
1821
|
validationErrorsByModuleId[moduleId] = serializedModuleContent;
|
1758
1822
|
}
|
@@ -2256,8 +2320,8 @@ class LocalValServer extends ValServer {
|
|
2256
2320
|
// No RemoteFS so nothing to ensure
|
2257
2321
|
return fp.result.ok(undefined);
|
2258
2322
|
}
|
2259
|
-
getModule(moduleId) {
|
2260
|
-
return this.options.service.get(moduleId);
|
2323
|
+
getModule(moduleId, options) {
|
2324
|
+
return this.options.service.get(moduleId, "", options);
|
2261
2325
|
}
|
2262
2326
|
async execCommit(patches) {
|
2263
2327
|
for (const [patchId, moduleId, patch] of patches) {
|
@@ -2545,11 +2609,11 @@ class ProxyValServer extends ValServer {
|
|
2545
2609
|
|
2546
2610
|
/** Remote FS dependent methods: */
|
2547
2611
|
|
2548
|
-
async getModule(moduleId) {
|
2612
|
+
async getModule(moduleId, options) {
|
2549
2613
|
if (!this.lazyService) {
|
2550
2614
|
this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
|
2551
2615
|
}
|
2552
|
-
return this.lazyService.get(moduleId);
|
2616
|
+
return this.lazyService.get(moduleId, "", options);
|
2553
2617
|
}
|
2554
2618
|
execCommit(patches, cookies) {
|
2555
2619
|
return withAuth(this.options.valSecret, cookies, "execCommit", async ({
|
@@ -3411,7 +3475,8 @@ function createValApiRouter(route, valServerPromise, convert) {
|
|
3411
3475
|
return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
|
3412
3476
|
patch: url.searchParams.get("patch") || undefined,
|
3413
3477
|
schema: url.searchParams.get("schema") || undefined,
|
3414
|
-
source: url.searchParams.get("source") || undefined
|
3478
|
+
source: url.searchParams.get("source") || undefined,
|
3479
|
+
validate: url.searchParams.get("validate") || undefined
|
3415
3480
|
}, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
|
3416
3481
|
} else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
|
3417
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")) {
|
@@ -1014,6 +1046,11 @@ export const IS_DEV = false;
|
|
1014
1046
|
value: "export const ValNextProvider = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValNextProvider' in this file`) } } } )"
|
1015
1047
|
};
|
1016
1048
|
}
|
1049
|
+
if (modulePath.includes("/ValContext")) {
|
1050
|
+
return {
|
1051
|
+
value: "export const useValEvents = () => { throw Error(`Cannot use 'useValEvents' in this type of file`) }; export const ValContext = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValContext' in this file`) } } } ) export const ValEvents = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValEvents' in this file`) } } } )"
|
1052
|
+
};
|
1053
|
+
}
|
1017
1054
|
if (modulePath.includes("/ValImage")) {
|
1018
1055
|
return {
|
1019
1056
|
value: "export const ValImage = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValImage' in this file`) } } } )"
|
@@ -1079,6 +1116,11 @@ export const IS_DEV = false;
|
|
1079
1116
|
value: requestedName
|
1080
1117
|
};
|
1081
1118
|
}
|
1119
|
+
if (requestedName.includes("/ValContext")) {
|
1120
|
+
return {
|
1121
|
+
value: requestedName
|
1122
|
+
};
|
1123
|
+
}
|
1082
1124
|
if (requestedName.includes("/ValImage")) {
|
1083
1125
|
return {
|
1084
1126
|
value: requestedName
|
@@ -1127,8 +1169,12 @@ class Service {
|
|
1127
1169
|
this.runtime = runtime;
|
1128
1170
|
this.valConfigPath = valConfigPath || "./val.config";
|
1129
1171
|
}
|
1130
|
-
async get(moduleId, modulePath
|
1131
|
-
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
|
+
});
|
1132
1178
|
if (valModule.source && valModule.schema) {
|
1133
1179
|
const resolved = Internal.resolvePath(modulePath, valModule.source, valModule.schema);
|
1134
1180
|
const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
|
@@ -1393,8 +1439,11 @@ class ValServer {
|
|
1393
1439
|
if (result.isErr(ensureRes)) {
|
1394
1440
|
return ensureRes.error;
|
1395
1441
|
}
|
1396
|
-
const moduleIds = this.getAllModules(treePath);
|
1397
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);
|
1398
1447
|
let {
|
1399
1448
|
patchIdsByModuleId,
|
1400
1449
|
patchesById,
|
@@ -1414,7 +1463,7 @@ class ValServer {
|
|
1414
1463
|
fileUpdates = res.value.fileUpdates;
|
1415
1464
|
}
|
1416
1465
|
const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
|
1417
|
-
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1466
|
+
return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
|
1418
1467
|
}));
|
1419
1468
|
const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
|
1420
1469
|
const module = {
|
@@ -1468,13 +1517,17 @@ class ValServer {
|
|
1468
1517
|
|
1469
1518
|
/* */
|
1470
1519
|
|
1471
|
-
async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches,
|
1472
|
-
const serializedModuleContent = await this.getModule(moduleId
|
1473
|
-
|
1474
|
-
|
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
|
+
});
|
1475
1526
|
if (!applyPatches) {
|
1476
1527
|
return serializedModuleContent;
|
1477
1528
|
}
|
1529
|
+
const schema = serializedModuleContent.schema;
|
1530
|
+
const maybeSource = serializedModuleContent.source;
|
1478
1531
|
if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
|
1479
1532
|
return serializedModuleContent;
|
1480
1533
|
}
|
@@ -1510,17 +1563,19 @@ class ValServer {
|
|
1510
1563
|
};
|
1511
1564
|
}
|
1512
1565
|
}
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
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
|
+
}
|
1524
1579
|
}
|
1525
1580
|
return {
|
1526
1581
|
path: moduleId,
|
@@ -1535,7 +1590,7 @@ class ValServer {
|
|
1535
1590
|
// The reason is that validate will be called inside QuickJS (in the future, hopefully),
|
1536
1591
|
// which does not have access to the filesystem, at least not at the time of writing this comment.
|
1537
1592
|
// If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
|
1538
|
-
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies,
|
1593
|
+
async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
|
1539
1594
|
const revalidatedValidationErrors = {};
|
1540
1595
|
for (const pathStr in validationErrors) {
|
1541
1596
|
const errorSourcePath = pathStr;
|
@@ -1557,7 +1612,7 @@ class ValServer {
|
|
1557
1612
|
if (updatedFileMetadata) {
|
1558
1613
|
const fileRes = await this.getFiles(fileRef, {
|
1559
1614
|
sha256: updatedFileMetadata.sha256
|
1560
|
-
}, cookies,
|
1615
|
+
}, cookies, reqHeaders);
|
1561
1616
|
if (fileRes.status === 200 && fileRes.body) {
|
1562
1617
|
const res = new Response(fileRes.body);
|
1563
1618
|
fileBuffer = Buffer.from(await res.arrayBuffer());
|
@@ -1567,14 +1622,23 @@ class ValServer {
|
|
1567
1622
|
});
|
1568
1623
|
}
|
1569
1624
|
}
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
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
|
+
}
|
1576
1638
|
});
|
1577
1639
|
}
|
1640
|
+
} else {
|
1641
|
+
console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
|
1578
1642
|
}
|
1579
1643
|
if (!fileBuffer) {
|
1580
1644
|
revalidatedValidationErrors[errorSourcePath].push({
|
@@ -1721,7 +1785,7 @@ class ValServer {
|
|
1721
1785
|
const moduleId = moduleIdStr;
|
1722
1786
|
const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
|
1723
1787
|
// TODO: refine to ModuleId and PatchId when parsing
|
1724
|
-
patchIdsByModuleId, patchesById, fileUpdates, true,
|
1788
|
+
patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
|
1725
1789
|
if (serializedModuleContent.errors) {
|
1726
1790
|
validationErrorsByModuleId[moduleId] = serializedModuleContent;
|
1727
1791
|
}
|
@@ -2225,8 +2289,8 @@ class LocalValServer extends ValServer {
|
|
2225
2289
|
// No RemoteFS so nothing to ensure
|
2226
2290
|
return result.ok(undefined);
|
2227
2291
|
}
|
2228
|
-
getModule(moduleId) {
|
2229
|
-
return this.options.service.get(moduleId);
|
2292
|
+
getModule(moduleId, options) {
|
2293
|
+
return this.options.service.get(moduleId, "", options);
|
2230
2294
|
}
|
2231
2295
|
async execCommit(patches) {
|
2232
2296
|
for (const [patchId, moduleId, patch] of patches) {
|
@@ -2514,11 +2578,11 @@ class ProxyValServer extends ValServer {
|
|
2514
2578
|
|
2515
2579
|
/** Remote FS dependent methods: */
|
2516
2580
|
|
2517
|
-
async getModule(moduleId) {
|
2581
|
+
async getModule(moduleId, options) {
|
2518
2582
|
if (!this.lazyService) {
|
2519
2583
|
this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
|
2520
2584
|
}
|
2521
|
-
return this.lazyService.get(moduleId);
|
2585
|
+
return this.lazyService.get(moduleId, "", options);
|
2522
2586
|
}
|
2523
2587
|
execCommit(patches, cookies) {
|
2524
2588
|
return withAuth(this.options.valSecret, cookies, "execCommit", async ({
|
@@ -3380,7 +3444,8 @@ function createValApiRouter(route, valServerPromise, convert) {
|
|
3380
3444
|
return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
|
3381
3445
|
patch: url.searchParams.get("patch") || undefined,
|
3382
3446
|
schema: url.searchParams.get("schema") || undefined,
|
3383
|
-
source: url.searchParams.get("source") || undefined
|
3447
|
+
source: url.searchParams.get("source") || undefined,
|
3448
|
+
validate: url.searchParams.get("validate") || undefined
|
3384
3449
|
}, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
|
3385
3450
|
} else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
|
3386
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",
|