@valbuild/server 0.21.2 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/declarations/src/LocalValServer.d.ts +1 -0
- package/dist/declarations/src/SerializedModuleContent.d.ts +4 -3
- package/dist/declarations/src/Service.d.ts +7 -1
- package/dist/declarations/src/ValModuleLoader.d.ts +4 -1
- package/dist/declarations/src/ValServer.d.ts +1 -0
- package/dist/valbuild-server.cjs.dev.js +135 -58
- package/dist/valbuild-server.cjs.prod.js +135 -58
- package/dist/valbuild-server.esm.js +134 -57
- package/package.json +4 -3
- package/src/LocalValServer.ts +18 -15
- package/src/ProxyValServer.ts +84 -12
- package/src/SerializedModuleContent.ts +4 -3
- package/src/Service.ts +10 -13
- package/src/ValModuleLoader.ts +36 -13
- package/src/ValServer.ts +1 -0
- package/src/createRequestHandler.ts +1 -0
- package/src/patch/validation.ts +1 -0
- package/src/patchValFile.ts +0 -8
- package/src/readValFile.ts +5 -4
@@ -9,10 +9,12 @@ var core = require('@valbuild/core');
|
|
9
9
|
var patch = require('@valbuild/core/patch');
|
10
10
|
var path = require('path');
|
11
11
|
var fs = require('fs');
|
12
|
+
var sucrase = require('sucrase');
|
12
13
|
var express = require('express');
|
13
14
|
var server = require('@valbuild/ui/server');
|
14
15
|
var z = require('zod');
|
15
16
|
var crypto = require('crypto');
|
17
|
+
var stream = require('stream');
|
16
18
|
var sizeOf = require('image-size');
|
17
19
|
|
18
20
|
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
@@ -640,6 +642,7 @@ globalThis.valModule = {
|
|
640
642
|
const error = result.error.consume(context.dump);
|
641
643
|
console.error(`Fatal error reading val file: ${error.message}\n`, error.stack);
|
642
644
|
return {
|
645
|
+
path: id,
|
643
646
|
errors: {
|
644
647
|
invalidModuleId: id,
|
645
648
|
fatal: [{
|
@@ -655,12 +658,12 @@ globalThis.valModule = {
|
|
655
658
|
fatalErrors.push(`Could not find any modules at: ${id}`);
|
656
659
|
} else {
|
657
660
|
if (valModule.id !== id) {
|
658
|
-
fatalErrors.push(`
|
661
|
+
fatalErrors.push(`Wrong val.content id! In the file of with: '${id}', found: '${valModule.id}'`);
|
659
662
|
}
|
660
663
|
if (!(valModule !== null && valModule !== void 0 && valModule.schema)) {
|
661
664
|
fatalErrors.push(`Expected val id: '${id}' to have a schema`);
|
662
665
|
}
|
663
|
-
if (
|
666
|
+
if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined) {
|
664
667
|
fatalErrors.push(`Expected val id: '${id}' to have a source`);
|
665
668
|
}
|
666
669
|
}
|
@@ -680,7 +683,7 @@ globalThis.valModule = {
|
|
680
683
|
};
|
681
684
|
}
|
682
685
|
return {
|
683
|
-
path: valModule.id,
|
686
|
+
path: valModule.id || id,
|
684
687
|
// NOTE: we use path here, since SerializedModuleContent (maybe bad name?) can be used for whole modules as well as subparts of modules
|
685
688
|
source: valModule.source,
|
686
689
|
schema: valModule.schema,
|
@@ -734,9 +737,6 @@ const patchValFile = async (id, valConfigPath, patch$1, sourceFileHandler, runti
|
|
734
737
|
sourceFileHandler.writeFile("." + filePath, convertDataUrlToBase64(content).toString("binary"), "binary");
|
735
738
|
}
|
736
739
|
}
|
737
|
-
for (const [ref, patch] of Object.entries(derefRes.value.remotePatches)) {
|
738
|
-
throw Error(`Cannot update remote ${ref} with ${JSON.stringify(patch)}: not implemented`);
|
739
|
-
}
|
740
740
|
sourceFileHandler.writeSourceFile(newSourceFile.value);
|
741
741
|
return readValFile(id, valConfigPath, runtime);
|
742
742
|
};
|
@@ -815,15 +815,23 @@ class ValSourceFileHandler {
|
|
815
815
|
const JsFileLookupMapping = [
|
816
816
|
// NOTE: first one matching will be used
|
817
817
|
[".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js", ".esm.js", ".mjs.js"]]];
|
818
|
+
const MAX_CACHE_SIZE = 100 * 1024 * 1024; // 100 mb
|
819
|
+
const MAX_OBJECT_KEY_SIZE = 2 ** 27; // https://stackoverflow.com/questions/13367391/is-there-a-limit-on-length-of-the-key-string-in-js-object
|
820
|
+
|
818
821
|
class ValModuleLoader {
|
819
|
-
constructor(projectRoot,
|
822
|
+
constructor(projectRoot,
|
823
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
824
|
+
compilerOptions, sourceFileHandler, host = {
|
820
825
|
...ts__default["default"].sys,
|
821
826
|
writeFile: fs__default["default"].writeFileSync
|
822
|
-
}) {
|
827
|
+
}, disableCache = false) {
|
823
828
|
this.projectRoot = projectRoot;
|
824
829
|
this.compilerOptions = compilerOptions;
|
825
830
|
this.sourceFileHandler = sourceFileHandler;
|
826
831
|
this.host = host;
|
832
|
+
this.disableCache = disableCache;
|
833
|
+
this.cache = {};
|
834
|
+
this.cacheSize = 0;
|
827
835
|
}
|
828
836
|
getModule(modulePath) {
|
829
837
|
if (!modulePath) {
|
@@ -833,18 +841,31 @@ class ValModuleLoader {
|
|
833
841
|
if (!code) {
|
834
842
|
throw Error(`Could not read file "${modulePath}"`);
|
835
843
|
}
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
844
|
+
let compiledCode;
|
845
|
+
if (this.cache[code] && !this.disableCache) {
|
846
|
+
// TODO: use hash instead of code as key
|
847
|
+
compiledCode = this.cache[code];
|
848
|
+
} else {
|
849
|
+
compiledCode = sucrase.transform(code, {
|
850
|
+
filePath: modulePath,
|
851
|
+
disableESTransforms: true,
|
852
|
+
transforms: ["typescript"]
|
853
|
+
}).code;
|
854
|
+
if (!this.disableCache) {
|
855
|
+
if (this.cacheSize > MAX_CACHE_SIZE) {
|
856
|
+
console.warn("Cache size exceeded, clearing cache");
|
857
|
+
this.cache = {};
|
858
|
+
this.cacheSize = 0;
|
859
|
+
}
|
860
|
+
if (code.length < MAX_OBJECT_KEY_SIZE) {
|
861
|
+
this.cache[code] = compiledCode;
|
862
|
+
this.cacheSize += code.length + compiledCode.length; // code is mostly ASCII so 1 byte per char
|
863
|
+
}
|
864
|
+
}
|
865
|
+
}
|
847
866
|
|
867
|
+
return compiledCode;
|
868
|
+
}
|
848
869
|
resolveModulePath(containingFilePath, requestedModuleName) {
|
849
870
|
var _this$host$realpath, _this$host;
|
850
871
|
let sourceFileName = this.sourceFileHandler.resolveSourceModulePath(containingFilePath, requestedModuleName);
|
@@ -1004,11 +1025,9 @@ class Service {
|
|
1004
1025
|
path: sourcePath,
|
1005
1026
|
schema: resolved.schema instanceof core.Schema ? resolved.schema.serialize() : resolved.schema,
|
1006
1027
|
source: resolved.source,
|
1007
|
-
errors: valModule.errors && valModule.errors.validation
|
1008
|
-
validation: valModule.errors.validation
|
1009
|
-
|
1010
|
-
} : undefined,
|
1011
|
-
fatal: valModule.errors && valModule.errors.fatal ? valModule.errors.fatal : undefined
|
1028
|
+
errors: valModule.errors && valModule.errors.validation ? {
|
1029
|
+
validation: valModule.errors.validation || undefined,
|
1030
|
+
fatal: valModule.errors.fatal || undefined
|
1012
1031
|
} : false
|
1013
1032
|
};
|
1014
1033
|
} else {
|
@@ -1038,13 +1057,10 @@ function createRequestHandler(valServer) {
|
|
1038
1057
|
router.get("/enable", valServer.enable.bind(valServer));
|
1039
1058
|
router.get("/disable", valServer.disable.bind(valServer));
|
1040
1059
|
router.get("/tree/*", valServer.getTree.bind(valServer));
|
1060
|
+
router.get("/files/*", valServer.getFiles.bind(valServer));
|
1041
1061
|
return router;
|
1042
1062
|
}
|
1043
1063
|
|
1044
|
-
function getPathFromParams(params) {
|
1045
|
-
return `/${params[0]}`;
|
1046
|
-
}
|
1047
|
-
|
1048
1064
|
const JSONValueT = z__default["default"].lazy(() => z__default["default"].union([z__default["default"].string(), z__default["default"].number(), z__default["default"].boolean(), z__default["default"].null(), z__default["default"].array(JSONValueT), z__default["default"].record(JSONValueT)]));
|
1049
1065
|
|
1050
1066
|
/**
|
@@ -1082,6 +1098,7 @@ const OperationJSONT = z__default["default"].discriminatedUnion("op", [z__defaul
|
|
1082
1098
|
}).strict(), z__default["default"].object({
|
1083
1099
|
op: z__default["default"].literal("file"),
|
1084
1100
|
path: z__default["default"].string(),
|
1101
|
+
filePath: z__default["default"].string(),
|
1085
1102
|
value: z__default["default"].string()
|
1086
1103
|
}).strict()]);
|
1087
1104
|
const PatchJSON = z__default["default"].array(OperationJSONT);
|
@@ -1148,10 +1165,61 @@ function encodeJwt(payload, sessionKey) {
|
|
1148
1165
|
const VAL_SESSION_COOKIE = core.Internal.VAL_SESSION_COOKIE;
|
1149
1166
|
const VAL_STATE_COOKIE = core.Internal.VAL_STATE_COOKIE;
|
1150
1167
|
const VAL_ENABLED_COOKIE = core.Internal.VAL_ENABLE_COOKIE_NAME;
|
1168
|
+
class BrowserReadableStreamWrapper extends stream.Readable {
|
1169
|
+
constructor(readableStream) {
|
1170
|
+
super();
|
1171
|
+
this.reader = readableStream.getReader();
|
1172
|
+
}
|
1173
|
+
_read() {
|
1174
|
+
this.reader.read().then(({
|
1175
|
+
done,
|
1176
|
+
value
|
1177
|
+
}) => {
|
1178
|
+
if (done) {
|
1179
|
+
this.push(null); // No more data to read
|
1180
|
+
} else {
|
1181
|
+
this.push(Buffer.from(value));
|
1182
|
+
}
|
1183
|
+
}).catch(error => {
|
1184
|
+
this.emit("error", error);
|
1185
|
+
});
|
1186
|
+
}
|
1187
|
+
}
|
1151
1188
|
class ProxyValServer {
|
1152
1189
|
constructor(options) {
|
1153
1190
|
this.options = options;
|
1154
1191
|
}
|
1192
|
+
async getFiles(req, res) {
|
1193
|
+
return this.withAuth(req, res, async data => {
|
1194
|
+
const url = new URL(`/v1/files/${this.options.valName}/${req.params["0"]}`, this.options.valContentUrl);
|
1195
|
+
if (typeof req.query.sha256 === "string") {
|
1196
|
+
url.searchParams.append("sha256", req.query.sha256);
|
1197
|
+
} else {
|
1198
|
+
console.warn("Missing sha256 query param");
|
1199
|
+
}
|
1200
|
+
const fetchRes = await fetch(url, {
|
1201
|
+
headers: this.getAuthHeaders(data.token)
|
1202
|
+
});
|
1203
|
+
const contentType = fetchRes.headers.get("content-type");
|
1204
|
+
if (contentType !== null) {
|
1205
|
+
res.setHeader("Content-Type", contentType);
|
1206
|
+
}
|
1207
|
+
const contentLength = fetchRes.headers.get("content-length");
|
1208
|
+
if (contentLength !== null) {
|
1209
|
+
res.setHeader("Content-Length", contentLength);
|
1210
|
+
}
|
1211
|
+
if (fetchRes.ok) {
|
1212
|
+
if (fetchRes.body) {
|
1213
|
+
new BrowserReadableStreamWrapper(fetchRes.body).pipe(res);
|
1214
|
+
} else {
|
1215
|
+
console.warn("No body in response");
|
1216
|
+
res.sendStatus(500);
|
1217
|
+
}
|
1218
|
+
} else {
|
1219
|
+
res.sendStatus(fetchRes.status);
|
1220
|
+
}
|
1221
|
+
});
|
1222
|
+
}
|
1155
1223
|
async authorize(req, res) {
|
1156
1224
|
const {
|
1157
1225
|
redirect_to
|
@@ -1245,10 +1313,18 @@ class ProxyValServer {
|
|
1245
1313
|
schema,
|
1246
1314
|
source
|
1247
1315
|
} = req.query;
|
1316
|
+
const commit = this.options.gitCommit;
|
1317
|
+
if (!commit) {
|
1318
|
+
res.status(401).json({
|
1319
|
+
error: "Could not detect the git commit. Check if env is missing VAL_GIT_COMMIT."
|
1320
|
+
});
|
1321
|
+
return;
|
1322
|
+
}
|
1248
1323
|
const params = new URLSearchParams({
|
1249
1324
|
patch: (patch === "true").toString(),
|
1250
1325
|
schema: (schema === "true").toString(),
|
1251
|
-
source: (source === "true").toString()
|
1326
|
+
source: (source === "true").toString(),
|
1327
|
+
commit
|
1252
1328
|
});
|
1253
1329
|
const url = new URL(`/v1/tree/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`, this.options.valContentUrl);
|
1254
1330
|
const json = await fetch(url, {
|
@@ -1258,12 +1334,10 @@ class ProxyValServer {
|
|
1258
1334
|
});
|
1259
1335
|
}
|
1260
1336
|
async postPatches(req, res) {
|
1261
|
-
const
|
1262
|
-
|
1263
|
-
} = req.query;
|
1264
|
-
if (typeof commit !== "string" || typeof commit === "undefined") {
|
1337
|
+
const commit = this.options.gitCommit;
|
1338
|
+
if (!commit) {
|
1265
1339
|
res.status(401).json({
|
1266
|
-
error: "
|
1340
|
+
error: "Could not detect the git commit. Check if env is missing VAL_GIT_COMMIT."
|
1267
1341
|
});
|
1268
1342
|
return;
|
1269
1343
|
}
|
@@ -1274,23 +1348,25 @@ class ProxyValServer {
|
|
1274
1348
|
token
|
1275
1349
|
}) => {
|
1276
1350
|
// First validate that the body has the right structure
|
1277
|
-
const patchJSON = PatchJSON.safeParse(req.body);
|
1351
|
+
const patchJSON = z.z.record(PatchJSON).safeParse(req.body);
|
1278
1352
|
if (!patchJSON.success) {
|
1279
1353
|
res.status(401).json(patchJSON.error.issues);
|
1280
1354
|
return;
|
1281
1355
|
}
|
1282
1356
|
// Then parse/validate
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1357
|
+
// TODO:
|
1358
|
+
const patch = patchJSON.data;
|
1359
|
+
// const patch = parsePatch(patchJSON.data);
|
1360
|
+
// if (result.isErr(patch)) {
|
1361
|
+
// res.status(401).json(patch.error);
|
1362
|
+
// return;
|
1363
|
+
// }
|
1364
|
+
const url = new URL(`/v1/patches/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`, this.options.valContentUrl);
|
1289
1365
|
// Proxy patch to val.build
|
1290
1366
|
const fetchRes = await fetch(url, {
|
1291
1367
|
method: "POST",
|
1292
1368
|
headers: this.getAuthHeaders(token, "application/json"),
|
1293
|
-
body: JSON.stringify(patch
|
1369
|
+
body: JSON.stringify(patch)
|
1294
1370
|
});
|
1295
1371
|
if (fetchRes.ok) {
|
1296
1372
|
res.status(fetchRes.status).json(await fetchRes.json());
|
@@ -1566,7 +1642,8 @@ class LocalValServer {
|
|
1566
1642
|
const modules = Object.fromEntries(serializedModuleContent.map(serializedModuleContent => {
|
1567
1643
|
const module = {
|
1568
1644
|
schema: serializedModuleContent.schema,
|
1569
|
-
source: serializedModuleContent.source
|
1645
|
+
source: serializedModuleContent.source,
|
1646
|
+
errors: serializedModuleContent.errors
|
1570
1647
|
};
|
1571
1648
|
return [serializedModuleContent.path, module];
|
1572
1649
|
}));
|
@@ -1574,9 +1651,7 @@ class LocalValServer {
|
|
1574
1651
|
modules,
|
1575
1652
|
git: this.options.git
|
1576
1653
|
};
|
1577
|
-
|
1578
|
-
res.send(JSON.stringify(apiTreeResponse));
|
1579
|
-
});
|
1654
|
+
res.send(JSON.stringify(apiTreeResponse));
|
1580
1655
|
} catch (err) {
|
1581
1656
|
console.error(err);
|
1582
1657
|
res.sendStatus(500);
|
@@ -1589,24 +1664,23 @@ class LocalValServer {
|
|
1589
1664
|
return disable(req, res);
|
1590
1665
|
}
|
1591
1666
|
async postPatches(req, res) {
|
1592
|
-
var _getPathFromParams;
|
1593
|
-
const id = (_getPathFromParams = getPathFromParams(req.params)) === null || _getPathFromParams === void 0 ? void 0 : _getPathFromParams.replace("/~", "");
|
1594
|
-
|
1595
1667
|
// First validate that the body has the right structure
|
1596
|
-
const patchJSON = PatchJSON.safeParse(req.body);
|
1597
|
-
console.log("patch id", id, patchJSON);
|
1668
|
+
const patchJSON = z.z.record(PatchJSON).safeParse(req.body);
|
1598
1669
|
if (!patchJSON.success) {
|
1599
1670
|
res.status(401).json(patchJSON.error.issues);
|
1600
1671
|
return;
|
1601
1672
|
}
|
1602
|
-
// Then parse/validate
|
1603
|
-
const patch$1 = patch.parsePatch(patchJSON.data);
|
1604
|
-
if (fp.result.isErr(patch$1)) {
|
1605
|
-
res.status(401).json(patch$1.error);
|
1606
|
-
return;
|
1607
|
-
}
|
1608
1673
|
try {
|
1609
|
-
|
1674
|
+
for (const moduleId in patchJSON.data) {
|
1675
|
+
// Then parse/validate
|
1676
|
+
// TODO: validate all and then fail instead:
|
1677
|
+
const patch$1 = patch.parsePatch(patchJSON.data[moduleId]);
|
1678
|
+
if (fp.result.isErr(patch$1)) {
|
1679
|
+
res.status(401).json(patch$1.error);
|
1680
|
+
return;
|
1681
|
+
}
|
1682
|
+
await this.options.service.patch(moduleId, patch$1.value);
|
1683
|
+
}
|
1610
1684
|
res.json({});
|
1611
1685
|
} catch (err) {
|
1612
1686
|
if (err instanceof patch.PatchError) {
|
@@ -1637,6 +1711,9 @@ class LocalValServer {
|
|
1637
1711
|
logout(req, res) {
|
1638
1712
|
return this.badRequest(req, res);
|
1639
1713
|
}
|
1714
|
+
getFiles(req, res) {
|
1715
|
+
return this.badRequest(req, res);
|
1716
|
+
}
|
1640
1717
|
}
|
1641
1718
|
|
1642
1719
|
async function _createRequestListener(route, opts) {
|