@valbuild/server 0.16.3 → 0.17.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.
@@ -17,8 +17,26 @@ var sizeOf = require('image-size');
17
17
 
18
18
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
19
19
 
20
+ function _interopNamespace(e) {
21
+ if (e && e.__esModule) return e;
22
+ var n = Object.create(null);
23
+ if (e) {
24
+ Object.keys(e).forEach(function (k) {
25
+ if (k !== 'default') {
26
+ var d = Object.getOwnPropertyDescriptor(e, k);
27
+ Object.defineProperty(n, k, d.get ? d : {
28
+ enumerable: true,
29
+ get: function () { return e[k]; }
30
+ });
31
+ }
32
+ });
33
+ }
34
+ n["default"] = e;
35
+ return Object.freeze(n);
36
+ }
37
+
20
38
  var ts__default = /*#__PURE__*/_interopDefault(ts);
21
- var path__default = /*#__PURE__*/_interopDefault(path);
39
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
22
40
  var fs__default = /*#__PURE__*/_interopDefault(fs);
23
41
  var express__default = /*#__PURE__*/_interopDefault(express);
24
42
  var z__default = /*#__PURE__*/_interopDefault(z);
@@ -572,16 +590,16 @@ import { Internal } from "@valbuild/core";
572
590
  globalThis.valModule = {
573
591
  id: valModule?.default && Internal.getValPath(valModule?.default),
574
592
  schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
575
- source: valModule?.default && Internal.getRawSource(valModule?.default),
593
+ source: valModule?.default && Internal.getSource(valModule?.default),
576
594
  validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
577
595
  valModule?.default && Internal.getValPath(valModule?.default) || "/",
578
- valModule?.default && Internal.getRawSource(valModule?.default)
596
+ valModule?.default && Internal.getSource(valModule?.default)
579
597
  )
580
598
  };
581
599
  `;
582
600
  const result = context.evalCode(code,
583
601
  // Synthetic module name
584
- path__default["default"].join(path__default["default"].dirname(valConfigPath), "<val>"));
602
+ path__namespace["default"].join(path__namespace["default"].dirname(valConfigPath), "<val>"));
585
603
  const fatalErrors = [];
586
604
  if (result.error) {
587
605
  const error = result.error.consume(context.dump);
@@ -686,12 +704,15 @@ function convertDataUrlToBase64(dataUrl) {
686
704
  return Buffer.from(base64, "base64");
687
705
  }
688
706
  const patchSourceFile = (sourceFile, patch$1) => {
707
+ if (typeof sourceFile === "string") {
708
+ return patch.applyPatch(ts__default["default"].createSourceFile("<val>", sourceFile, ts__default["default"].ScriptTarget.ES2015), ops, patch$1);
709
+ }
689
710
  return patch.applyPatch(sourceFile, ops, patch$1);
690
711
  };
691
712
 
692
713
  const getCompilerOptions = (rootDir, parseConfigHost) => {
693
- const tsConfigPath = path__default["default"].resolve(rootDir, "tsconfig.json");
694
- const jsConfigPath = path__default["default"].resolve(rootDir, "jsconfig.json");
714
+ const tsConfigPath = path__namespace["default"].resolve(rootDir, "tsconfig.json");
715
+ const jsConfigPath = path__namespace["default"].resolve(rootDir, "jsconfig.json");
695
716
  let configFilePath;
696
717
  if (parseConfigHost.fileExists(jsConfigPath)) {
697
718
  configFilePath = jsConfigPath;
@@ -741,7 +762,7 @@ class ValSourceFileHandler {
741
762
  this.host.writeFile(filePath, content, encoding);
742
763
  }
743
764
  resolveSourceModulePath(containingFilePath, requestedModuleName) {
744
- const resolutionRes = ts__default["default"].resolveModuleName(requestedModuleName, path__default["default"].isAbsolute(containingFilePath) ? containingFilePath : path__default["default"].resolve(this.projectRoot, containingFilePath), this.compilerOptions, this.host, undefined, undefined, ts__default["default"].ModuleKind.ESNext);
765
+ const resolutionRes = ts__default["default"].resolveModuleName(requestedModuleName, path__namespace["default"].isAbsolute(containingFilePath) ? containingFilePath : path__namespace["default"].resolve(this.projectRoot, containingFilePath), this.compilerOptions, this.host, undefined, undefined, ts__default["default"].ModuleKind.ESNext);
745
766
  const resolvedModule = resolutionRes.resolvedModule;
746
767
  if (!resolvedModule) {
747
768
  throw Error(`Could not resolve module "${requestedModuleName}", base: "${containingFilePath}": No resolved modules returned: ${JSON.stringify(resolutionRes)}`);
@@ -861,6 +882,16 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
861
882
  value: "export const useVal = () => { throw Error(`Cannot use 'useVal' in this type of file`) }; export const fetchVal = () => { throw Error(`Cannot use 'fetchVal' in this type of file`) }; export const autoTagJSX = () => { /* ignore */ };"
862
883
  };
863
884
  }
885
+ if (modulePath.startsWith("next")) {
886
+ return {
887
+ value: "export default new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'next' in this file`) } } } )"
888
+ };
889
+ }
890
+ if (modulePath.startsWith("react")) {
891
+ return {
892
+ value: "export default new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'react' in this file`) } } } )"
893
+ };
894
+ }
864
895
  return {
865
896
  value: moduleLoader.getModule(modulePath)
866
897
  };
@@ -881,6 +912,16 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
881
912
  value: requestedName
882
913
  };
883
914
  }
915
+ if (requestedName.startsWith("next")) {
916
+ return {
917
+ value: requestedName
918
+ };
919
+ }
920
+ if (requestedName.startsWith("react")) {
921
+ return {
922
+ value: requestedName
923
+ };
924
+ }
884
925
  const modulePath = moduleLoader.resolveModulePath(baseModuleName, requestedName);
885
926
  return {
886
927
  value: modulePath
@@ -917,7 +958,7 @@ class Service {
917
958
  const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime);
918
959
  if (valModule.source && valModule.schema) {
919
960
  const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
920
- const sourcePath = [moduleId, resolved.path].join(".");
961
+ const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
921
962
  return {
922
963
  path: sourcePath,
923
964
  schema: resolved.schema instanceof core.Schema ? resolved.schema.serialize() : resolved.schema,
@@ -948,15 +989,21 @@ function createRequestHandler(valServer) {
948
989
  router.get("/authorize", valServer.authorize.bind(valServer));
949
990
  router.get("/callback", valServer.callback.bind(valServer));
950
991
  router.get("/logout", valServer.logout.bind(valServer));
951
- router.get("/ids/*", valServer.getIds.bind(valServer));
952
- router.patch("/ids/*", express__default["default"].json({
953
- type: "application/json-patch+json",
992
+ router.post("/patches/*", express__default["default"].json({
993
+ type: "application/json",
954
994
  limit: "10mb"
955
- }), valServer.patchIds.bind(valServer));
995
+ }), valServer.postPatches.bind(valServer));
956
996
  router.post("/commit", valServer.commit.bind(valServer));
997
+ router.get("/enable", valServer.enable.bind(valServer));
998
+ router.get("/disable", valServer.disable.bind(valServer));
999
+ router.get("/tree/*", valServer.getTree.bind(valServer));
957
1000
  return router;
958
1001
  }
959
1002
 
1003
+ function getPathFromParams(params) {
1004
+ return `/${params[0]}`;
1005
+ }
1006
+
960
1007
  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)]));
961
1008
 
962
1009
  /**
@@ -994,75 +1041,6 @@ const OperationJSONT = z__default["default"].discriminatedUnion("op", [z__defaul
994
1041
  }).strict()]);
995
1042
  const PatchJSON = z__default["default"].array(OperationJSONT);
996
1043
 
997
- function getPathFromParams(params) {
998
- return `/${params[0]}`;
999
- }
1000
-
1001
- class LocalValServer {
1002
- constructor(options) {
1003
- this.options = options;
1004
- }
1005
- async session(_req, res) {
1006
- res.json({
1007
- mode: "local"
1008
- });
1009
- }
1010
- async getIds(req, res) {
1011
- try {
1012
- console.log(req.params);
1013
- const path = getPathFromParams(req.params);
1014
- const [moduleId, modulePath] = core.Internal.splitModuleIdAndModulePath(path);
1015
- const valModule = await this.options.service.get(moduleId, modulePath);
1016
- res.json(valModule);
1017
- } catch (err) {
1018
- console.error(err);
1019
- res.sendStatus(500);
1020
- }
1021
- }
1022
- async patchIds(req, res) {
1023
- // First validate that the body has the right structure
1024
- const patchJSON = PatchJSON.safeParse(req.body);
1025
- if (!patchJSON.success) {
1026
- res.status(401).json(patchJSON.error.issues);
1027
- return;
1028
- }
1029
- // Then parse/validate
1030
- const patch$1 = patch.parsePatch(patchJSON.data);
1031
- if (fp.result.isErr(patch$1)) {
1032
- res.status(401).json(patch$1.error);
1033
- return;
1034
- }
1035
- const id = getPathFromParams(req.params);
1036
- try {
1037
- const valModule = await this.options.service.patch(id, patch$1.value);
1038
- res.json(valModule);
1039
- } catch (err) {
1040
- if (err instanceof patch.PatchError) {
1041
- res.status(401).send(err.message);
1042
- } else {
1043
- console.error(err);
1044
- res.status(500).send(err instanceof Error ? err.message : "Unknown error");
1045
- }
1046
- }
1047
- }
1048
- async badRequest(req, res) {
1049
- console.debug("Local server does handle this request", req.url);
1050
- res.sendStatus(400);
1051
- }
1052
- commit(req, res) {
1053
- return this.badRequest(req, res);
1054
- }
1055
- authorize(req, res) {
1056
- return this.badRequest(req, res);
1057
- }
1058
- callback(req, res) {
1059
- return this.badRequest(req, res);
1060
- }
1061
- logout(req, res) {
1062
- return this.badRequest(req, res);
1063
- }
1064
- }
1065
-
1066
1044
  function decodeJwt(token, secretKey) {
1067
1045
  const [headerBase64, payloadBase64, signatureBase64, ...rest] = token.split(".");
1068
1046
  if (!headerBase64 || !payloadBase64 || !signatureBase64 || rest.length > 0) {
@@ -1122,8 +1100,9 @@ function encodeJwt(payload, sessionKey) {
1122
1100
  return `${jwtHeaderBase64}.${payloadBase64}.${crypto__default["default"].createHmac("sha256", sessionKey).update(`${jwtHeaderBase64}.${payloadBase64}`).digest("base64")}`;
1123
1101
  }
1124
1102
 
1125
- const VAL_SESSION_COOKIE = "val_session";
1126
- const VAL_STATE_COOKIE = "val_state";
1103
+ const VAL_SESSION_COOKIE = core.Internal.VAL_SESSION_COOKIE;
1104
+ const VAL_STATE_COOKIE = core.Internal.VAL_STATE_COOKIE;
1105
+ const VAL_ENABLED_COOKIE = core.Internal.VAL_ENABLE_COOKIE_NAME;
1127
1106
  class ProxyValServer {
1128
1107
  constructor(options) {
1129
1108
  this.options = options;
@@ -1148,6 +1127,12 @@ class ProxyValServer {
1148
1127
  expires: new Date(Date.now() + 1000 * 60 * 60) // 1 hour
1149
1128
  }).redirect(appAuthorizeUrl);
1150
1129
  }
1130
+ async enable(req, res) {
1131
+ return enable(req, res, this.options.valEnableRedirectUrl);
1132
+ }
1133
+ async disable(req, res) {
1134
+ return disable(req, res, this.options.valEnableRedirectUrl);
1135
+ }
1151
1136
  async callback(req, res) {
1152
1137
  const {
1153
1138
  success: callbackReqSuccess,
@@ -1193,7 +1178,6 @@ class ProxyValServer {
1193
1178
  }
1194
1179
  }
1195
1180
  async session(req, res) {
1196
- console.log("hit session");
1197
1181
  return this.withAuth(req, res, async data => {
1198
1182
  const url = new URL(`/api/val/${this.options.valName}/auth/session`, this.options.valBuildUrl);
1199
1183
  const fetchRes = await fetch(url, {
@@ -1209,30 +1193,38 @@ class ProxyValServer {
1209
1193
  }
1210
1194
  });
1211
1195
  }
1212
- async getIds(req, res) {
1213
- return this.withAuth(req, res, async ({
1214
- token
1215
- }) => {
1216
- const id = getPathFromParams(req.params);
1217
- const url = new URL(`/api/val/modules/${encodeURIComponent(this.options.gitCommit)}${id}`, this.options.valBuildUrl);
1218
- const fetchRes = await fetch(url, {
1219
- headers: this.getAuthHeaders(token)
1220
- });
1221
- if (fetchRes.ok) {
1222
- res.status(fetchRes.status).json(await fetchRes.json());
1223
- } else {
1224
- res.sendStatus(fetchRes.status);
1225
- }
1226
- }).catch(e => {
1227
- res.status(500).send({
1228
- error: {
1229
- message: e === null || e === void 0 ? void 0 : e.message,
1230
- status: 500
1231
- }
1196
+ async getTree(req, res) {
1197
+ return this.withAuth(req, res, async data => {
1198
+ const {
1199
+ patch,
1200
+ schema,
1201
+ source
1202
+ } = req.query;
1203
+ const params = new URLSearchParams({
1204
+ patch: (patch === "true").toString(),
1205
+ schema: (schema === "true").toString(),
1206
+ source: (source === "true").toString()
1232
1207
  });
1208
+ const url = new URL(`/v1/tree/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`, this.options.valContentUrl);
1209
+ const json = await fetch(url, {
1210
+ headers: this.getAuthHeaders(data.token, "application/json")
1211
+ }).then(res => res.json());
1212
+ res.send(json);
1233
1213
  });
1234
1214
  }
1235
- async patchIds(req, res) {
1215
+ async postPatches(req, res) {
1216
+ const {
1217
+ commit
1218
+ } = req.query;
1219
+ if (typeof commit !== "string" || typeof commit === "undefined") {
1220
+ res.status(401).json({
1221
+ error: "Missing or invalid commit query param"
1222
+ });
1223
+ return;
1224
+ }
1225
+ const params = new URLSearchParams({
1226
+ commit
1227
+ });
1236
1228
  this.withAuth(req, res, async ({
1237
1229
  token
1238
1230
  }) => {
@@ -1248,12 +1240,11 @@ class ProxyValServer {
1248
1240
  res.status(401).json(patch$1.error);
1249
1241
  return;
1250
1242
  }
1251
- const id = getPathFromParams(req.params);
1252
- const url = new URL(`/api/val/modules/${encodeURIComponent(this.options.gitCommit)}${id}`, this.options.valBuildUrl);
1243
+ const url = new URL(`/v1/tree/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`, this.options.valContentUrl);
1253
1244
  // Proxy patch to val.build
1254
1245
  const fetchRes = await fetch(url, {
1255
- method: "PATCH",
1256
- headers: this.getAuthHeaders(token, "application/json-patch+json"),
1246
+ method: "POST",
1247
+ headers: this.getAuthHeaders(token, "application/json"),
1257
1248
  body: JSON.stringify(patch$1)
1258
1249
  });
1259
1250
  if (fetchRes.ok) {
@@ -1435,6 +1426,40 @@ function getStateFromCookie(stateCookie) {
1435
1426
  };
1436
1427
  }
1437
1428
  }
1429
+ async function enable(req, res, redirectUrl) {
1430
+ const {
1431
+ redirect_to
1432
+ } = req.query;
1433
+ if (typeof redirect_to === "string" || typeof redirect_to === "undefined") {
1434
+ let redirectUrlToUse = redirect_to || "/";
1435
+ if (redirectUrl) {
1436
+ redirectUrlToUse = redirectUrl + "?redirect_to=" + encodeURIComponent(redirectUrlToUse);
1437
+ }
1438
+ res.cookie(VAL_ENABLED_COOKIE, "true", {
1439
+ httpOnly: false,
1440
+ sameSite: "lax"
1441
+ }).redirect(redirectUrlToUse);
1442
+ } else {
1443
+ res.sendStatus(400);
1444
+ }
1445
+ }
1446
+ async function disable(req, res, redirectUrl) {
1447
+ const {
1448
+ redirect_to
1449
+ } = req.query;
1450
+ if (typeof redirect_to === "string" || typeof redirect_to === "undefined") {
1451
+ let redirectUrlToUse = redirect_to || "/";
1452
+ if (redirectUrl) {
1453
+ redirectUrlToUse = redirectUrl + "?redirect_to=" + encodeURIComponent(redirectUrlToUse);
1454
+ }
1455
+ res.cookie(VAL_ENABLED_COOKIE, "false", {
1456
+ httpOnly: false,
1457
+ sameSite: "lax"
1458
+ }).redirect(redirectUrlToUse);
1459
+ } else {
1460
+ res.sendStatus(400);
1461
+ }
1462
+ }
1438
1463
  function createStateCookie(state) {
1439
1464
  return Buffer.from(JSON.stringify(state), "utf8").toString("base64");
1440
1465
  }
@@ -1452,6 +1477,123 @@ const IntegratedServerJwtPayload = z.z.object({
1452
1477
  project: z.z.string()
1453
1478
  });
1454
1479
 
1480
+ class LocalValServer {
1481
+ constructor(options) {
1482
+ this.options = options;
1483
+ }
1484
+ async session(_req, res) {
1485
+ res.json({
1486
+ mode: "local"
1487
+ });
1488
+ }
1489
+ async getTree(req, res) {
1490
+ try {
1491
+ // TODO: use the params: patch, schema, source
1492
+ const treePath = req.params["0"].replace("~", "");
1493
+ const rootDir = process.cwd();
1494
+ const moduleIds = [];
1495
+ // iterate over all .val files in the root directory
1496
+ const walk = async dir => {
1497
+ const files = await fs.promises.readdir(dir);
1498
+ for (const file of files) {
1499
+ if ((await fs.promises.stat(path__namespace["default"].join(dir, file))).isDirectory()) {
1500
+ if (file === "node_modules") continue;
1501
+ await walk(path__namespace["default"].join(dir, file));
1502
+ } else {
1503
+ const isValFile = file.endsWith(".val.js") || file.endsWith(".val.ts");
1504
+ if (!isValFile) {
1505
+ continue;
1506
+ }
1507
+ if (treePath && !path__namespace["default"].join(dir, file).replace(rootDir, "").startsWith(treePath)) {
1508
+ continue;
1509
+ }
1510
+ moduleIds.push(path__namespace["default"].join(dir, file).replace(rootDir, "").replace(".val.js", "").replace(".val.ts", ""));
1511
+ }
1512
+ }
1513
+ };
1514
+ const serializedModuleContent = await walk(rootDir).then(async () => {
1515
+ return Promise.all(moduleIds.map(async moduleId => {
1516
+ return await this.options.service.get(moduleId, "");
1517
+ }));
1518
+ });
1519
+
1520
+ //
1521
+ const modules = Object.fromEntries(serializedModuleContent.map(serializedModuleContent => {
1522
+ const module = {
1523
+ schema: serializedModuleContent.schema,
1524
+ source: serializedModuleContent.source
1525
+ };
1526
+ return [serializedModuleContent.path, module];
1527
+ }));
1528
+ const apiTreeResponse = {
1529
+ modules,
1530
+ git: this.options.git
1531
+ };
1532
+ return walk(rootDir).then(async () => {
1533
+ res.send(JSON.stringify(apiTreeResponse));
1534
+ });
1535
+ } catch (err) {
1536
+ console.error(err);
1537
+ res.sendStatus(500);
1538
+ }
1539
+ }
1540
+ async enable(req, res) {
1541
+ return enable(req, res);
1542
+ }
1543
+ async disable(req, res) {
1544
+ return disable(req, res);
1545
+ }
1546
+ async postPatches(req, res) {
1547
+ var _getPathFromParams;
1548
+ const id = (_getPathFromParams = getPathFromParams(req.params)) === null || _getPathFromParams === void 0 ? void 0 : _getPathFromParams.replace("/~", "");
1549
+
1550
+ // First validate that the body has the right structure
1551
+ const patchJSON = PatchJSON.safeParse(req.body);
1552
+ console.log("patch id", id, patchJSON);
1553
+ if (!patchJSON.success) {
1554
+ res.status(401).json(patchJSON.error.issues);
1555
+ return;
1556
+ }
1557
+ // Then parse/validate
1558
+ const patch$1 = patch.parsePatch(patchJSON.data);
1559
+ if (fp.result.isErr(patch$1)) {
1560
+ res.status(401).json(patch$1.error);
1561
+ return;
1562
+ }
1563
+ try {
1564
+ await this.options.service.patch(id, patch$1.value);
1565
+ res.json({});
1566
+ } catch (err) {
1567
+ if (err instanceof patch.PatchError) {
1568
+ res.status(400).send({
1569
+ message: err.message
1570
+ });
1571
+ } else {
1572
+ console.error(err);
1573
+ res.status(500).send({
1574
+ message: err instanceof Error ? err.message : "Unknown error"
1575
+ });
1576
+ }
1577
+ }
1578
+ }
1579
+ async badRequest(req, res) {
1580
+ console.debug("Local server does handle this request", req.url);
1581
+ res.sendStatus(400);
1582
+ }
1583
+ commit(req, res) {
1584
+ return this.badRequest(req, res);
1585
+ }
1586
+ authorize(req, res) {
1587
+ return this.badRequest(req, res);
1588
+ }
1589
+ callback(req, res) {
1590
+ return this.badRequest(req, res);
1591
+ }
1592
+ logout(req, res) {
1593
+ return this.badRequest(req, res);
1594
+ }
1595
+ }
1596
+
1455
1597
  async function _createRequestListener(route, opts) {
1456
1598
  const serverOpts = await initHandlerOptions(route, opts);
1457
1599
  let valServer;
@@ -1472,6 +1614,7 @@ async function initHandlerOptions(route, opts) {
1472
1614
  if (!maybeApiKey || !maybeValSecret) {
1473
1615
  throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode");
1474
1616
  }
1617
+ const valContentUrl = opts.valContentUrl || process.env.VAL_CONTENT_URL || "https://content.val.build";
1475
1618
  const maybeGitCommit = opts.gitCommit || process.env.VAL_GIT_COMMIT;
1476
1619
  if (!maybeGitCommit) {
1477
1620
  throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode");
@@ -1490,18 +1633,82 @@ async function initHandlerOptions(route, opts) {
1490
1633
  apiKey: maybeApiKey,
1491
1634
  valSecret: maybeValSecret,
1492
1635
  valBuildUrl,
1636
+ valContentUrl,
1493
1637
  gitCommit: maybeGitCommit,
1494
1638
  gitBranch: maybeGitBranch,
1495
- valName: maybeValName
1639
+ valName: maybeValName,
1640
+ valEnableRedirectUrl: opts.valEnableRedirectUrl || process.env.VAL_ENABLE_REDIRECT_URL,
1641
+ valDisableRedirectUrl: opts.valDisableRedirectUrl || process.env.VAL_DISABLE_REDIRECT_URL
1496
1642
  };
1497
1643
  } else {
1498
- const service = await createService(process.cwd(), opts);
1644
+ const cwd = process.cwd();
1645
+ const service = await createService(cwd, opts);
1646
+ const git = await safeReadGit(cwd);
1499
1647
  return {
1500
1648
  mode: "local",
1501
- service
1649
+ service,
1650
+ git: {
1651
+ commit: process.env.VAL_GIT_COMMIT || git.commit,
1652
+ branch: process.env.VAL_GIT_BRANCH || git.branch
1653
+ }
1502
1654
  };
1503
1655
  }
1504
1656
  }
1657
+ async function safeReadGit(cwd) {
1658
+ async function findGitHead(currentDir, depth) {
1659
+ const gitHeadPath = path__namespace.join(currentDir, ".git", "HEAD");
1660
+ if (depth > 1000) {
1661
+ console.error(`Reached max depth while scanning for .git folder. Current working dir: ${cwd}.`);
1662
+ return {
1663
+ commit: undefined,
1664
+ branch: undefined
1665
+ };
1666
+ }
1667
+ try {
1668
+ const headContents = await fs.promises.readFile(gitHeadPath, "utf-8");
1669
+ const match = headContents.match(/^ref: refs\/heads\/(.+)/);
1670
+ if (match) {
1671
+ const branchName = match[1];
1672
+ return {
1673
+ branch: branchName,
1674
+ commit: await readCommit(currentDir, branchName)
1675
+ };
1676
+ } else {
1677
+ return {
1678
+ commit: undefined,
1679
+ branch: undefined
1680
+ };
1681
+ }
1682
+ } catch (error) {
1683
+ const parentDir = path__namespace.dirname(currentDir);
1684
+
1685
+ // We've reached the root directory
1686
+ if (parentDir === currentDir) {
1687
+ return {
1688
+ commit: undefined,
1689
+ branch: undefined
1690
+ };
1691
+ }
1692
+ return findGitHead(parentDir, depth + 1);
1693
+ }
1694
+ }
1695
+ try {
1696
+ return findGitHead(cwd, 0);
1697
+ } catch (err) {
1698
+ console.error("Error while reading .git", err);
1699
+ return {
1700
+ commit: undefined,
1701
+ branch: undefined
1702
+ };
1703
+ }
1704
+ }
1705
+ async function readCommit(gitDir, branchName) {
1706
+ try {
1707
+ return (await fs.promises.readFile(path__namespace.join(gitDir, ".git", "refs", "heads", branchName), "utf-8")).trim();
1708
+ } catch (err) {
1709
+ return undefined;
1710
+ }
1711
+ }
1505
1712
 
1506
1713
  // TODO: rename to createValApiHandlers?
1507
1714
  function createRequestListener(route, opts) {
@@ -1539,10 +1746,10 @@ class ValFSHost {
1539
1746
  return this.currentDirectory;
1540
1747
  }
1541
1748
  getCanonicalFileName(fileName) {
1542
- if (path__default["default"].isAbsolute(fileName)) {
1543
- return path__default["default"].normalize(fileName);
1749
+ if (path__namespace["default"].isAbsolute(fileName)) {
1750
+ return path__namespace["default"].normalize(fileName);
1544
1751
  }
1545
- return path__default["default"].resolve(this.getCurrentDirectory(), fileName);
1752
+ return path__namespace["default"].resolve(this.getCurrentDirectory(), fileName);
1546
1753
  }
1547
1754
  fileExists(fileName) {
1548
1755
  return this.valFS.fileExists(fileName);
@@ -1563,7 +1770,7 @@ async function createFixPatch(config, apply, sourcePath, validationError) {
1563
1770
  // TODO:
1564
1771
  throw Error("Cannot fix image without a file reference");
1565
1772
  }
1566
- const localFile = path__default["default"].join(config.projectRoot, maybeRef);
1773
+ const localFile = path__namespace["default"].join(config.projectRoot, maybeRef);
1567
1774
  const buffer = fs__default["default"].readFileSync(localFile);
1568
1775
  const sha256 = await getSHA256Hash(buffer);
1569
1776
  const imageSize = sizeOf__default["default"](buffer);
@@ -1680,3 +1887,4 @@ exports.formatSyntaxErrorTree = formatSyntaxErrorTree;
1680
1887
  exports.getCompilerOptions = getCompilerOptions;
1681
1888
  exports.getExpire = getExpire;
1682
1889
  exports.patchSourceFile = patchSourceFile;
1890
+ exports.safeReadGit = safeReadGit;