@valbuild/server 0.21.2 → 0.22.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
@@ -6,10 +6,12 @@ import { deepEqual, isNotRoot, PatchError, parseAndValidateArrayIndex, applyPatc
|
|
6
6
|
import * as path from 'path';
|
7
7
|
import path__default from 'path';
|
8
8
|
import fs, { promises } from 'fs';
|
9
|
+
import { transform } from 'sucrase';
|
9
10
|
import express, { Router } from 'express';
|
10
11
|
import { createRequestHandler as createRequestHandler$1 } from '@valbuild/ui/server';
|
11
12
|
import z, { z as z$1 } from 'zod';
|
12
13
|
import crypto from 'crypto';
|
14
|
+
import { Readable } from 'stream';
|
13
15
|
import sizeOf from 'image-size';
|
14
16
|
|
15
17
|
class ValSyntaxError {
|
@@ -609,6 +611,7 @@ globalThis.valModule = {
|
|
609
611
|
const error = result.error.consume(context.dump);
|
610
612
|
console.error(`Fatal error reading val file: ${error.message}\n`, error.stack);
|
611
613
|
return {
|
614
|
+
path: id,
|
612
615
|
errors: {
|
613
616
|
invalidModuleId: id,
|
614
617
|
fatal: [{
|
@@ -624,12 +627,12 @@ globalThis.valModule = {
|
|
624
627
|
fatalErrors.push(`Could not find any modules at: ${id}`);
|
625
628
|
} else {
|
626
629
|
if (valModule.id !== id) {
|
627
|
-
fatalErrors.push(`
|
630
|
+
fatalErrors.push(`Wrong val.content id! In the file of with: '${id}', found: '${valModule.id}'`);
|
628
631
|
}
|
629
632
|
if (!(valModule !== null && valModule !== void 0 && valModule.schema)) {
|
630
633
|
fatalErrors.push(`Expected val id: '${id}' to have a schema`);
|
631
634
|
}
|
632
|
-
if (
|
635
|
+
if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined) {
|
633
636
|
fatalErrors.push(`Expected val id: '${id}' to have a source`);
|
634
637
|
}
|
635
638
|
}
|
@@ -649,7 +652,7 @@ globalThis.valModule = {
|
|
649
652
|
};
|
650
653
|
}
|
651
654
|
return {
|
652
|
-
path: valModule.id,
|
655
|
+
path: valModule.id || id,
|
653
656
|
// NOTE: we use path here, since SerializedModuleContent (maybe bad name?) can be used for whole modules as well as subparts of modules
|
654
657
|
source: valModule.source,
|
655
658
|
schema: valModule.schema,
|
@@ -703,9 +706,6 @@ const patchValFile = async (id, valConfigPath, patch, sourceFileHandler, runtime
|
|
703
706
|
sourceFileHandler.writeFile("." + filePath, convertDataUrlToBase64(content).toString("binary"), "binary");
|
704
707
|
}
|
705
708
|
}
|
706
|
-
for (const [ref, patch] of Object.entries(derefRes.value.remotePatches)) {
|
707
|
-
throw Error(`Cannot update remote ${ref} with ${JSON.stringify(patch)}: not implemented`);
|
708
|
-
}
|
709
709
|
sourceFileHandler.writeSourceFile(newSourceFile.value);
|
710
710
|
return readValFile(id, valConfigPath, runtime);
|
711
711
|
};
|
@@ -784,15 +784,23 @@ class ValSourceFileHandler {
|
|
784
784
|
const JsFileLookupMapping = [
|
785
785
|
// NOTE: first one matching will be used
|
786
786
|
[".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js", ".esm.js", ".mjs.js"]]];
|
787
|
+
const MAX_CACHE_SIZE = 100 * 1024 * 1024; // 100 mb
|
788
|
+
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
|
789
|
+
|
787
790
|
class ValModuleLoader {
|
788
|
-
constructor(projectRoot,
|
791
|
+
constructor(projectRoot,
|
792
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
793
|
+
compilerOptions, sourceFileHandler, host = {
|
789
794
|
...ts.sys,
|
790
795
|
writeFile: fs.writeFileSync
|
791
|
-
}) {
|
796
|
+
}, disableCache = false) {
|
792
797
|
this.projectRoot = projectRoot;
|
793
798
|
this.compilerOptions = compilerOptions;
|
794
799
|
this.sourceFileHandler = sourceFileHandler;
|
795
800
|
this.host = host;
|
801
|
+
this.disableCache = disableCache;
|
802
|
+
this.cache = {};
|
803
|
+
this.cacheSize = 0;
|
796
804
|
}
|
797
805
|
getModule(modulePath) {
|
798
806
|
if (!modulePath) {
|
@@ -802,18 +810,31 @@ class ValModuleLoader {
|
|
802
810
|
if (!code) {
|
803
811
|
throw Error(`Could not read file "${modulePath}"`);
|
804
812
|
}
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
813
|
+
let compiledCode;
|
814
|
+
if (this.cache[code] && !this.disableCache) {
|
815
|
+
// TODO: use hash instead of code as key
|
816
|
+
compiledCode = this.cache[code];
|
817
|
+
} else {
|
818
|
+
compiledCode = transform(code, {
|
819
|
+
filePath: modulePath,
|
820
|
+
disableESTransforms: true,
|
821
|
+
transforms: ["typescript"]
|
822
|
+
}).code;
|
823
|
+
if (!this.disableCache) {
|
824
|
+
if (this.cacheSize > MAX_CACHE_SIZE) {
|
825
|
+
console.warn("Cache size exceeded, clearing cache");
|
826
|
+
this.cache = {};
|
827
|
+
this.cacheSize = 0;
|
828
|
+
}
|
829
|
+
if (code.length < MAX_OBJECT_KEY_SIZE) {
|
830
|
+
this.cache[code] = compiledCode;
|
831
|
+
this.cacheSize += code.length + compiledCode.length; // code is mostly ASCII so 1 byte per char
|
832
|
+
}
|
833
|
+
}
|
834
|
+
}
|
816
835
|
|
836
|
+
return compiledCode;
|
837
|
+
}
|
817
838
|
resolveModulePath(containingFilePath, requestedModuleName) {
|
818
839
|
var _this$host$realpath, _this$host;
|
819
840
|
let sourceFileName = this.sourceFileHandler.resolveSourceModulePath(containingFilePath, requestedModuleName);
|
@@ -973,11 +994,9 @@ class Service {
|
|
973
994
|
path: sourcePath,
|
974
995
|
schema: resolved.schema instanceof Schema ? resolved.schema.serialize() : resolved.schema,
|
975
996
|
source: resolved.source,
|
976
|
-
errors: valModule.errors && valModule.errors.validation
|
977
|
-
validation: valModule.errors.validation
|
978
|
-
|
979
|
-
} : undefined,
|
980
|
-
fatal: valModule.errors && valModule.errors.fatal ? valModule.errors.fatal : undefined
|
997
|
+
errors: valModule.errors && valModule.errors.validation ? {
|
998
|
+
validation: valModule.errors.validation || undefined,
|
999
|
+
fatal: valModule.errors.fatal || undefined
|
981
1000
|
} : false
|
982
1001
|
};
|
983
1002
|
} else {
|
@@ -1007,13 +1026,10 @@ function createRequestHandler(valServer) {
|
|
1007
1026
|
router.get("/enable", valServer.enable.bind(valServer));
|
1008
1027
|
router.get("/disable", valServer.disable.bind(valServer));
|
1009
1028
|
router.get("/tree/*", valServer.getTree.bind(valServer));
|
1029
|
+
router.get("/files/*", valServer.getFiles.bind(valServer));
|
1010
1030
|
return router;
|
1011
1031
|
}
|
1012
1032
|
|
1013
|
-
function getPathFromParams(params) {
|
1014
|
-
return `/${params[0]}`;
|
1015
|
-
}
|
1016
|
-
|
1017
1033
|
const JSONValueT = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(JSONValueT), z.record(JSONValueT)]));
|
1018
1034
|
|
1019
1035
|
/**
|
@@ -1051,6 +1067,7 @@ const OperationJSONT = z.discriminatedUnion("op", [z.object({
|
|
1051
1067
|
}).strict(), z.object({
|
1052
1068
|
op: z.literal("file"),
|
1053
1069
|
path: z.string(),
|
1070
|
+
filePath: z.string(),
|
1054
1071
|
value: z.string()
|
1055
1072
|
}).strict()]);
|
1056
1073
|
const PatchJSON = z.array(OperationJSONT);
|
@@ -1117,10 +1134,61 @@ function encodeJwt(payload, sessionKey) {
|
|
1117
1134
|
const VAL_SESSION_COOKIE = Internal.VAL_SESSION_COOKIE;
|
1118
1135
|
const VAL_STATE_COOKIE = Internal.VAL_STATE_COOKIE;
|
1119
1136
|
const VAL_ENABLED_COOKIE = Internal.VAL_ENABLE_COOKIE_NAME;
|
1137
|
+
class BrowserReadableStreamWrapper extends Readable {
|
1138
|
+
constructor(readableStream) {
|
1139
|
+
super();
|
1140
|
+
this.reader = readableStream.getReader();
|
1141
|
+
}
|
1142
|
+
_read() {
|
1143
|
+
this.reader.read().then(({
|
1144
|
+
done,
|
1145
|
+
value
|
1146
|
+
}) => {
|
1147
|
+
if (done) {
|
1148
|
+
this.push(null); // No more data to read
|
1149
|
+
} else {
|
1150
|
+
this.push(Buffer.from(value));
|
1151
|
+
}
|
1152
|
+
}).catch(error => {
|
1153
|
+
this.emit("error", error);
|
1154
|
+
});
|
1155
|
+
}
|
1156
|
+
}
|
1120
1157
|
class ProxyValServer {
|
1121
1158
|
constructor(options) {
|
1122
1159
|
this.options = options;
|
1123
1160
|
}
|
1161
|
+
async getFiles(req, res) {
|
1162
|
+
return this.withAuth(req, res, async data => {
|
1163
|
+
const url = new URL(`/v1/files/${this.options.valName}/${req.params["0"]}`, this.options.valContentUrl);
|
1164
|
+
if (typeof req.query.sha256 === "string") {
|
1165
|
+
url.searchParams.append("sha256", req.query.sha256);
|
1166
|
+
} else {
|
1167
|
+
console.warn("Missing sha256 query param");
|
1168
|
+
}
|
1169
|
+
const fetchRes = await fetch(url, {
|
1170
|
+
headers: this.getAuthHeaders(data.token)
|
1171
|
+
});
|
1172
|
+
const contentType = fetchRes.headers.get("content-type");
|
1173
|
+
if (contentType !== null) {
|
1174
|
+
res.setHeader("Content-Type", contentType);
|
1175
|
+
}
|
1176
|
+
const contentLength = fetchRes.headers.get("content-length");
|
1177
|
+
if (contentLength !== null) {
|
1178
|
+
res.setHeader("Content-Length", contentLength);
|
1179
|
+
}
|
1180
|
+
if (fetchRes.ok) {
|
1181
|
+
if (fetchRes.body) {
|
1182
|
+
new BrowserReadableStreamWrapper(fetchRes.body).pipe(res);
|
1183
|
+
} else {
|
1184
|
+
console.warn("No body in response");
|
1185
|
+
res.sendStatus(500);
|
1186
|
+
}
|
1187
|
+
} else {
|
1188
|
+
res.sendStatus(fetchRes.status);
|
1189
|
+
}
|
1190
|
+
});
|
1191
|
+
}
|
1124
1192
|
async authorize(req, res) {
|
1125
1193
|
const {
|
1126
1194
|
redirect_to
|
@@ -1214,10 +1282,18 @@ class ProxyValServer {
|
|
1214
1282
|
schema,
|
1215
1283
|
source
|
1216
1284
|
} = req.query;
|
1285
|
+
const commit = this.options.gitCommit;
|
1286
|
+
if (!commit) {
|
1287
|
+
res.status(401).json({
|
1288
|
+
error: "Could not detect the git commit. Check if env is missing VAL_GIT_COMMIT."
|
1289
|
+
});
|
1290
|
+
return;
|
1291
|
+
}
|
1217
1292
|
const params = new URLSearchParams({
|
1218
1293
|
patch: (patch === "true").toString(),
|
1219
1294
|
schema: (schema === "true").toString(),
|
1220
|
-
source: (source === "true").toString()
|
1295
|
+
source: (source === "true").toString(),
|
1296
|
+
commit
|
1221
1297
|
});
|
1222
1298
|
const url = new URL(`/v1/tree/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`, this.options.valContentUrl);
|
1223
1299
|
const json = await fetch(url, {
|
@@ -1227,12 +1303,10 @@ class ProxyValServer {
|
|
1227
1303
|
});
|
1228
1304
|
}
|
1229
1305
|
async postPatches(req, res) {
|
1230
|
-
const
|
1231
|
-
|
1232
|
-
} = req.query;
|
1233
|
-
if (typeof commit !== "string" || typeof commit === "undefined") {
|
1306
|
+
const commit = this.options.gitCommit;
|
1307
|
+
if (!commit) {
|
1234
1308
|
res.status(401).json({
|
1235
|
-
error: "
|
1309
|
+
error: "Could not detect the git commit. Check if env is missing VAL_GIT_COMMIT."
|
1236
1310
|
});
|
1237
1311
|
return;
|
1238
1312
|
}
|
@@ -1243,18 +1317,20 @@ class ProxyValServer {
|
|
1243
1317
|
token
|
1244
1318
|
}) => {
|
1245
1319
|
// First validate that the body has the right structure
|
1246
|
-
const patchJSON = PatchJSON.safeParse(req.body);
|
1320
|
+
const patchJSON = z$1.record(PatchJSON).safeParse(req.body);
|
1247
1321
|
if (!patchJSON.success) {
|
1248
1322
|
res.status(401).json(patchJSON.error.issues);
|
1249
1323
|
return;
|
1250
1324
|
}
|
1251
1325
|
// Then parse/validate
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1326
|
+
// TODO:
|
1327
|
+
const patch = patchJSON.data;
|
1328
|
+
// const patch = parsePatch(patchJSON.data);
|
1329
|
+
// if (result.isErr(patch)) {
|
1330
|
+
// res.status(401).json(patch.error);
|
1331
|
+
// return;
|
1332
|
+
// }
|
1333
|
+
const url = new URL(`/v1/patches/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`, this.options.valContentUrl);
|
1258
1334
|
// Proxy patch to val.build
|
1259
1335
|
const fetchRes = await fetch(url, {
|
1260
1336
|
method: "POST",
|
@@ -1535,7 +1611,8 @@ class LocalValServer {
|
|
1535
1611
|
const modules = Object.fromEntries(serializedModuleContent.map(serializedModuleContent => {
|
1536
1612
|
const module = {
|
1537
1613
|
schema: serializedModuleContent.schema,
|
1538
|
-
source: serializedModuleContent.source
|
1614
|
+
source: serializedModuleContent.source,
|
1615
|
+
errors: serializedModuleContent.errors
|
1539
1616
|
};
|
1540
1617
|
return [serializedModuleContent.path, module];
|
1541
1618
|
}));
|
@@ -1543,9 +1620,7 @@ class LocalValServer {
|
|
1543
1620
|
modules,
|
1544
1621
|
git: this.options.git
|
1545
1622
|
};
|
1546
|
-
|
1547
|
-
res.send(JSON.stringify(apiTreeResponse));
|
1548
|
-
});
|
1623
|
+
res.send(JSON.stringify(apiTreeResponse));
|
1549
1624
|
} catch (err) {
|
1550
1625
|
console.error(err);
|
1551
1626
|
res.sendStatus(500);
|
@@ -1558,24 +1633,23 @@ class LocalValServer {
|
|
1558
1633
|
return disable(req, res);
|
1559
1634
|
}
|
1560
1635
|
async postPatches(req, res) {
|
1561
|
-
var _getPathFromParams;
|
1562
|
-
const id = (_getPathFromParams = getPathFromParams(req.params)) === null || _getPathFromParams === void 0 ? void 0 : _getPathFromParams.replace("/~", "");
|
1563
|
-
|
1564
1636
|
// First validate that the body has the right structure
|
1565
|
-
const patchJSON = PatchJSON.safeParse(req.body);
|
1566
|
-
console.log("patch id", id, patchJSON);
|
1637
|
+
const patchJSON = z$1.record(PatchJSON).safeParse(req.body);
|
1567
1638
|
if (!patchJSON.success) {
|
1568
1639
|
res.status(401).json(patchJSON.error.issues);
|
1569
1640
|
return;
|
1570
1641
|
}
|
1571
|
-
// Then parse/validate
|
1572
|
-
const patch = parsePatch(patchJSON.data);
|
1573
|
-
if (result.isErr(patch)) {
|
1574
|
-
res.status(401).json(patch.error);
|
1575
|
-
return;
|
1576
|
-
}
|
1577
1642
|
try {
|
1578
|
-
|
1643
|
+
for (const moduleId in patchJSON.data) {
|
1644
|
+
// Then parse/validate
|
1645
|
+
// TODO: validate all and then fail instead:
|
1646
|
+
const patch = parsePatch(patchJSON.data[moduleId]);
|
1647
|
+
if (result.isErr(patch)) {
|
1648
|
+
res.status(401).json(patch.error);
|
1649
|
+
return;
|
1650
|
+
}
|
1651
|
+
await this.options.service.patch(moduleId, patch.value);
|
1652
|
+
}
|
1579
1653
|
res.json({});
|
1580
1654
|
} catch (err) {
|
1581
1655
|
if (err instanceof PatchError) {
|
@@ -1606,6 +1680,9 @@ class LocalValServer {
|
|
1606
1680
|
logout(req, res) {
|
1607
1681
|
return this.badRequest(req, res);
|
1608
1682
|
}
|
1683
|
+
getFiles(req, res) {
|
1684
|
+
return this.badRequest(req, res);
|
1685
|
+
}
|
1609
1686
|
}
|
1610
1687
|
|
1611
1688
|
async function _createRequestListener(route, opts) {
|
package/package.json
CHANGED
@@ -12,7 +12,7 @@
|
|
12
12
|
"./package.json": "./package.json"
|
13
13
|
},
|
14
14
|
"types": "dist/valbuild-server.cjs.d.ts",
|
15
|
-
"version": "0.
|
15
|
+
"version": "0.22.0",
|
16
16
|
"scripts": {
|
17
17
|
"typecheck": "tsc --noEmit",
|
18
18
|
"test": "jest",
|
@@ -25,11 +25,12 @@
|
|
25
25
|
"concurrently": "^7.6.0"
|
26
26
|
},
|
27
27
|
"dependencies": {
|
28
|
-
"@valbuild/core": "~0.
|
29
|
-
"@valbuild/ui": "~0.
|
28
|
+
"@valbuild/core": "~0.22.0",
|
29
|
+
"@valbuild/ui": "~0.22.0",
|
30
30
|
"express": "^4.18.2",
|
31
31
|
"image-size": "^1.0.2",
|
32
32
|
"quickjs-emscripten": "^0.21.1",
|
33
|
+
"sucrase": "^3.34.0",
|
33
34
|
"ts-morph": "^17.0.1",
|
34
35
|
"typescript": "^4.9.4",
|
35
36
|
"zod": "^3.20.6"
|
package/src/LocalValServer.ts
CHANGED
@@ -2,13 +2,13 @@ import express from "express";
|
|
2
2
|
import { Service } from "./Service";
|
3
3
|
import { result } from "@valbuild/core/fp";
|
4
4
|
import { parsePatch, PatchError } from "@valbuild/core/patch";
|
5
|
-
import { getPathFromParams } from "./expressHelpers";
|
6
5
|
import { PatchJSON } from "./patch/validation";
|
7
6
|
import { ValServer } from "./ValServer";
|
8
7
|
import { ApiTreeResponse, ModuleId, ModulePath } from "@valbuild/core";
|
9
8
|
import { disable, enable } from "./ProxyValServer";
|
10
9
|
import { promises as fs } from "fs";
|
11
10
|
import path from "path";
|
11
|
+
import { z } from "zod";
|
12
12
|
|
13
13
|
export type LocalValServerOptions = {
|
14
14
|
service: Service;
|
@@ -80,6 +80,7 @@ export class LocalValServer implements ValServer {
|
|
80
80
|
{
|
81
81
|
schema: serializedModuleContent.schema,
|
82
82
|
source: serializedModuleContent.source,
|
83
|
+
errors: serializedModuleContent.errors,
|
83
84
|
};
|
84
85
|
return [serializedModuleContent.path, module];
|
85
86
|
})
|
@@ -88,9 +89,7 @@ export class LocalValServer implements ValServer {
|
|
88
89
|
modules,
|
89
90
|
git: this.options.git,
|
90
91
|
};
|
91
|
-
|
92
|
-
res.send(JSON.stringify(apiTreeResponse));
|
93
|
-
});
|
92
|
+
res.send(JSON.stringify(apiTreeResponse));
|
94
93
|
} catch (err) {
|
95
94
|
console.error(err);
|
96
95
|
res.sendStatus(500);
|
@@ -109,23 +108,24 @@ export class LocalValServer implements ValServer {
|
|
109
108
|
req: express.Request<{ 0: string }>,
|
110
109
|
res: express.Response
|
111
110
|
): Promise<void> {
|
112
|
-
const id = getPathFromParams(req.params)?.replace("/~", "");
|
113
|
-
|
114
111
|
// First validate that the body has the right structure
|
115
|
-
const patchJSON = PatchJSON.safeParse(req.body);
|
116
|
-
console.log("patch id", id, patchJSON);
|
112
|
+
const patchJSON = z.record(PatchJSON).safeParse(req.body);
|
117
113
|
if (!patchJSON.success) {
|
118
114
|
res.status(401).json(patchJSON.error.issues);
|
119
115
|
return;
|
120
116
|
}
|
121
|
-
|
122
|
-
const patch = parsePatch(patchJSON.data);
|
123
|
-
if (result.isErr(patch)) {
|
124
|
-
res.status(401).json(patch.error);
|
125
|
-
return;
|
126
|
-
}
|
117
|
+
|
127
118
|
try {
|
128
|
-
|
119
|
+
for (const moduleId in patchJSON.data) {
|
120
|
+
// Then parse/validate
|
121
|
+
// TODO: validate all and then fail instead:
|
122
|
+
const patch = parsePatch(patchJSON.data[moduleId]);
|
123
|
+
if (result.isErr(patch)) {
|
124
|
+
res.status(401).json(patch.error);
|
125
|
+
return;
|
126
|
+
}
|
127
|
+
await this.options.service.patch(moduleId, patch.value);
|
128
|
+
}
|
129
129
|
res.json({});
|
130
130
|
} catch (err) {
|
131
131
|
if (err instanceof PatchError) {
|
@@ -161,4 +161,7 @@ export class LocalValServer implements ValServer {
|
|
161
161
|
logout(req: express.Request, res: express.Response): Promise<void> {
|
162
162
|
return this.badRequest(req, res);
|
163
163
|
}
|
164
|
+
getFiles(req: express.Request, res: express.Response): Promise<void> {
|
165
|
+
return this.badRequest(req, res);
|
166
|
+
}
|
164
167
|
}
|
package/src/ProxyValServer.ts
CHANGED
@@ -2,11 +2,10 @@ import express from "express";
|
|
2
2
|
import crypto from "crypto";
|
3
3
|
import { decodeJwt, encodeJwt, getExpire } from "./jwt";
|
4
4
|
import { PatchJSON } from "./patch/validation";
|
5
|
-
import { result } from "@valbuild/core/fp";
|
6
5
|
import { ValServer } from "./ValServer";
|
7
6
|
import { z } from "zod";
|
8
|
-
import { parsePatch } from "@valbuild/core/patch";
|
9
7
|
import { Internal } from "@valbuild/core";
|
8
|
+
import { Readable } from "stream";
|
10
9
|
|
11
10
|
const VAL_SESSION_COOKIE = Internal.VAL_SESSION_COOKIE;
|
12
11
|
const VAL_STATE_COOKIE = Internal.VAL_STATE_COOKIE;
|
@@ -25,9 +24,68 @@ export type ProxyValServerOptions = {
|
|
25
24
|
valDisableRedirectUrl?: string;
|
26
25
|
};
|
27
26
|
|
27
|
+
class BrowserReadableStreamWrapper extends Readable {
|
28
|
+
private reader: ReadableStreamDefaultReader<Uint8Array>;
|
29
|
+
|
30
|
+
constructor(readableStream: ReadableStream<Uint8Array>) {
|
31
|
+
super();
|
32
|
+
this.reader = readableStream.getReader();
|
33
|
+
}
|
34
|
+
|
35
|
+
_read() {
|
36
|
+
this.reader
|
37
|
+
.read()
|
38
|
+
.then(({ done, value }) => {
|
39
|
+
if (done) {
|
40
|
+
this.push(null); // No more data to read
|
41
|
+
} else {
|
42
|
+
this.push(Buffer.from(value));
|
43
|
+
}
|
44
|
+
})
|
45
|
+
.catch((error) => {
|
46
|
+
this.emit("error", error);
|
47
|
+
});
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
28
51
|
export class ProxyValServer implements ValServer {
|
29
52
|
constructor(readonly options: ProxyValServerOptions) {}
|
30
53
|
|
54
|
+
async getFiles(req: express.Request, res: express.Response): Promise<void> {
|
55
|
+
return this.withAuth(req, res, async (data) => {
|
56
|
+
const url = new URL(
|
57
|
+
`/v1/files/${this.options.valName}/${req.params["0"]}`,
|
58
|
+
this.options.valContentUrl
|
59
|
+
);
|
60
|
+
if (typeof req.query.sha256 === "string") {
|
61
|
+
url.searchParams.append("sha256", req.query.sha256 as string);
|
62
|
+
} else {
|
63
|
+
console.warn("Missing sha256 query param");
|
64
|
+
}
|
65
|
+
const fetchRes = await fetch(url, {
|
66
|
+
headers: this.getAuthHeaders(data.token),
|
67
|
+
});
|
68
|
+
const contentType = fetchRes.headers.get("content-type");
|
69
|
+
if (contentType !== null) {
|
70
|
+
res.setHeader("Content-Type", contentType);
|
71
|
+
}
|
72
|
+
const contentLength = fetchRes.headers.get("content-length");
|
73
|
+
if (contentLength !== null) {
|
74
|
+
res.setHeader("Content-Length", contentLength);
|
75
|
+
}
|
76
|
+
if (fetchRes.ok) {
|
77
|
+
if (fetchRes.body) {
|
78
|
+
new BrowserReadableStreamWrapper(fetchRes.body).pipe(res);
|
79
|
+
} else {
|
80
|
+
console.warn("No body in response");
|
81
|
+
res.sendStatus(500);
|
82
|
+
}
|
83
|
+
} else {
|
84
|
+
res.sendStatus(fetchRes.status);
|
85
|
+
}
|
86
|
+
});
|
87
|
+
}
|
88
|
+
|
31
89
|
async authorize(req: express.Request, res: express.Response): Promise<void> {
|
32
90
|
const { redirect_to } = req.query;
|
33
91
|
if (typeof redirect_to !== "string") {
|
@@ -145,10 +203,19 @@ export class ProxyValServer implements ValServer {
|
|
145
203
|
async getTree(req: express.Request, res: express.Response): Promise<void> {
|
146
204
|
return this.withAuth(req, res, async (data) => {
|
147
205
|
const { patch, schema, source } = req.query;
|
206
|
+
const commit = this.options.gitCommit;
|
207
|
+
if (!commit) {
|
208
|
+
res.status(401).json({
|
209
|
+
error:
|
210
|
+
"Could not detect the git commit. Check if env is missing VAL_GIT_COMMIT.",
|
211
|
+
});
|
212
|
+
return;
|
213
|
+
}
|
148
214
|
const params = new URLSearchParams({
|
149
215
|
patch: (patch === "true").toString(),
|
150
216
|
schema: (schema === "true").toString(),
|
151
217
|
source: (source === "true").toString(),
|
218
|
+
commit,
|
152
219
|
});
|
153
220
|
const url = new URL(
|
154
221
|
`/v1/tree/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`,
|
@@ -165,9 +232,12 @@ export class ProxyValServer implements ValServer {
|
|
165
232
|
req: express.Request<{ 0: string }>,
|
166
233
|
res: express.Response
|
167
234
|
): Promise<void> {
|
168
|
-
const
|
169
|
-
if (
|
170
|
-
res.status(401).json({
|
235
|
+
const commit = this.options.gitCommit;
|
236
|
+
if (!commit) {
|
237
|
+
res.status(401).json({
|
238
|
+
error:
|
239
|
+
"Could not detect the git commit. Check if env is missing VAL_GIT_COMMIT.",
|
240
|
+
});
|
171
241
|
return;
|
172
242
|
}
|
173
243
|
const params = new URLSearchParams({
|
@@ -175,19 +245,21 @@ export class ProxyValServer implements ValServer {
|
|
175
245
|
});
|
176
246
|
this.withAuth(req, res, async ({ token }) => {
|
177
247
|
// First validate that the body has the right structure
|
178
|
-
const patchJSON = PatchJSON.safeParse(req.body);
|
248
|
+
const patchJSON = z.record(PatchJSON).safeParse(req.body);
|
179
249
|
if (!patchJSON.success) {
|
180
250
|
res.status(401).json(patchJSON.error.issues);
|
181
251
|
return;
|
182
252
|
}
|
183
253
|
// Then parse/validate
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
254
|
+
// TODO:
|
255
|
+
const patch = patchJSON.data;
|
256
|
+
// const patch = parsePatch(patchJSON.data);
|
257
|
+
// if (result.isErr(patch)) {
|
258
|
+
// res.status(401).json(patch.error);
|
259
|
+
// return;
|
260
|
+
// }
|
189
261
|
const url = new URL(
|
190
|
-
`/v1/
|
262
|
+
`/v1/patches/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`,
|
191
263
|
this.options.valContentUrl
|
192
264
|
);
|
193
265
|
// Proxy patch to val.build
|
@@ -11,6 +11,7 @@ export const FATAL_ERROR_TYPES = [
|
|
11
11
|
"invalid-id",
|
12
12
|
"no-module",
|
13
13
|
] as const;
|
14
|
+
export type FatalErrorType = (typeof FATAL_ERROR_TYPES)[number];
|
14
15
|
|
15
16
|
export type SerializedModuleContent =
|
16
17
|
| {
|
@@ -22,14 +23,14 @@ export type SerializedModuleContent =
|
|
22
23
|
| {
|
23
24
|
source?: Source;
|
24
25
|
schema?: SerializedSchema;
|
25
|
-
path
|
26
|
+
path: SourcePath;
|
26
27
|
errors: {
|
27
28
|
invalidModuleId?: ModuleId;
|
28
29
|
validation?: ValidationErrors;
|
29
30
|
fatal?: {
|
30
31
|
message: string;
|
31
|
-
stack?: string
|
32
|
-
type?:
|
32
|
+
stack?: string;
|
33
|
+
type?: FatalErrorType;
|
33
34
|
}[];
|
34
35
|
};
|
35
36
|
};
|
package/src/Service.ts
CHANGED
@@ -22,9 +22,15 @@ export type ServiceOptions = {
|
|
22
22
|
/**
|
23
23
|
* Relative path to the val.config.js file from the root directory.
|
24
24
|
*
|
25
|
-
* @example
|
25
|
+
* @example "./val.config"
|
26
26
|
*/
|
27
27
|
valConfigPath: string;
|
28
|
+
/**
|
29
|
+
* Disable cache for transpilation
|
30
|
+
*
|
31
|
+
* @default false
|
32
|
+
* */
|
33
|
+
disableCache?: boolean;
|
28
34
|
};
|
29
35
|
|
30
36
|
export async function createService(
|
@@ -89,19 +95,10 @@ export class Service {
|
|
89
95
|
: resolved.schema,
|
90
96
|
source: resolved.source,
|
91
97
|
errors:
|
92
|
-
valModule.errors &&
|
93
|
-
valModule.errors.validation &&
|
94
|
-
valModule.errors.validation[sourcePath]
|
98
|
+
valModule.errors && valModule.errors.validation
|
95
99
|
? {
|
96
|
-
validation: valModule.errors.validation
|
97
|
-
|
98
|
-
[sourcePath]: valModule.errors.validation[sourcePath],
|
99
|
-
}
|
100
|
-
: undefined,
|
101
|
-
fatal:
|
102
|
-
valModule.errors && valModule.errors.fatal
|
103
|
-
? valModule.errors.fatal
|
104
|
-
: undefined,
|
100
|
+
validation: valModule.errors.validation || undefined,
|
101
|
+
fatal: valModule.errors.fatal || undefined,
|
105
102
|
}
|
106
103
|
: false,
|
107
104
|
};
|