@valbuild/server 0.16.4 → 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.
@@ -3,17 +3,19 @@ import { Service } from "./Service.js";
3
3
  import { ValServer } from "./ValServer.js";
4
4
  export type LocalValServerOptions = {
5
5
  service: Service;
6
+ git: {
7
+ commit?: string;
8
+ branch?: string;
9
+ };
6
10
  };
7
11
  export declare class LocalValServer implements ValServer {
8
12
  readonly options: LocalValServerOptions;
9
13
  constructor(options: LocalValServerOptions);
10
- getAllModules(req: express.Request, res: express.Response): Promise<void>;
11
14
  session(_req: express.Request, res: express.Response): Promise<void>;
15
+ getTree(req: express.Request, res: express.Response): Promise<void>;
12
16
  enable(req: express.Request, res: express.Response): Promise<void>;
13
- getIds(req: express.Request<{
14
- 0: string;
15
- }>, res: express.Response): Promise<void>;
16
- patchIds(req: express.Request<{
17
+ disable(req: express.Request, res: express.Response): Promise<void>;
18
+ postPatches(req: express.Request<{
17
19
  0: string;
18
20
  }>, res: express.Response): Promise<void>;
19
21
  private badRequest;
@@ -4,15 +4,11 @@ export interface ValServer {
4
4
  callback(req: express.Request, res: express.Response): Promise<void>;
5
5
  logout(req: express.Request, res: express.Response): Promise<void>;
6
6
  session(req: express.Request, res: express.Response): Promise<void>;
7
- getIds(req: express.Request<{
8
- 0: string;
9
- }>, res: express.Response): Promise<void>;
10
- getAllModules(req: express.Request<{
11
- 0: string;
12
- }>, res: express.Response): Promise<void>;
13
- patchIds(req: express.Request<{
7
+ postPatches(req: express.Request<{
14
8
  0: string;
15
9
  }>, res: express.Response): Promise<void>;
16
10
  commit(req: express.Request, res: express.Response): Promise<void>;
17
11
  enable(req: express.Request, res: express.Response): Promise<void>;
12
+ disable(req: express.Request, res: express.Response): Promise<void>;
13
+ getTree(req: express.Request, res: express.Response): Promise<void>;
18
14
  }
@@ -66,6 +66,16 @@ type ValServerOverrides = Partial<{
66
66
  * @example "https://app.val.build"
67
67
  */
68
68
  valBuildUrl: string;
69
+ /**
70
+ * The base url of Val content.
71
+ *
72
+ * Typically this should not be set.
73
+ *
74
+ * Can also be overridden using the VAL_CONTENT_URL env var.
75
+ *
76
+ * @example "https://content.val.build"
77
+ */
78
+ valContentUrl: string;
69
79
  /**
70
80
  * The full name of this Val project.
71
81
  *
@@ -74,6 +84,30 @@ type ValServerOverrides = Partial<{
74
84
  * @example "myorg/my-project"
75
85
  */
76
86
  valName: string;
87
+ /**
88
+ * After Val is enabled, redirect to this url.
89
+ *
90
+ * May be used to setup a custom flow after enabling Val.
91
+ *
92
+ *This can be set using the VAL_ENABLE_REDIRECT_URL env var.
93
+ *
94
+ * @example "/api/draft/enable"
95
+ */
96
+ valEnableRedirectUrl?: string;
97
+ /**
98
+ * After Val is disabled, redirect to this url.
99
+ *
100
+ * May be used to setup a custom flow after disabling Val.
101
+ *
102
+ * This can be set using the VAL_DISABLE_REDIRECT_URL env var.
103
+ *
104
+ * @example "/api/draft/enable"
105
+ */
106
+ valDisableRedirectUrl?: string;
107
+ }>;
108
+ export declare function safeReadGit(cwd: string): Promise<{
109
+ commit?: string;
110
+ branch?: string;
77
111
  }>;
78
112
  export declare function createRequestListener(route: string, opts: Opts): RequestListener;
79
113
  export {};
@@ -1,7 +1,7 @@
1
1
  export type { ServiceOptions } from "./Service.js";
2
2
  export { createService, Service } from "./Service.js";
3
3
  export { createRequestHandler } from "./createRequestHandler.js";
4
- export { createRequestListener } from "./hosting.js";
4
+ export { createRequestListener, safeReadGit } from "./hosting.js";
5
5
  export { ValModuleLoader } from "./ValModuleLoader.js";
6
6
  export { getCompilerOptions } from "./getCompilerOptions.js";
7
7
  export { ValSourceFileHandler } from "./ValSourceFileHandler.js";
@@ -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);
@@ -693,8 +711,8 @@ const patchSourceFile = (sourceFile, patch$1) => {
693
711
  };
694
712
 
695
713
  const getCompilerOptions = (rootDir, parseConfigHost) => {
696
- const tsConfigPath = path__default["default"].resolve(rootDir, "tsconfig.json");
697
- 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");
698
716
  let configFilePath;
699
717
  if (parseConfigHost.fileExists(jsConfigPath)) {
700
718
  configFilePath = jsConfigPath;
@@ -744,7 +762,7 @@ class ValSourceFileHandler {
744
762
  this.host.writeFile(filePath, content, encoding);
745
763
  }
746
764
  resolveSourceModulePath(containingFilePath, requestedModuleName) {
747
- 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);
748
766
  const resolvedModule = resolutionRes.resolvedModule;
749
767
  if (!resolvedModule) {
750
768
  throw Error(`Could not resolve module "${requestedModuleName}", base: "${containingFilePath}": No resolved modules returned: ${JSON.stringify(resolutionRes)}`);
@@ -864,6 +882,16 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
864
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 */ };"
865
883
  };
866
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
+ }
867
895
  return {
868
896
  value: moduleLoader.getModule(modulePath)
869
897
  };
@@ -884,6 +912,16 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
884
912
  value: requestedName
885
913
  };
886
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
+ }
887
925
  const modulePath = moduleLoader.resolveModulePath(baseModuleName, requestedName);
888
926
  return {
889
927
  value: modulePath
@@ -920,7 +958,7 @@ class Service {
920
958
  const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime);
921
959
  if (valModule.source && valModule.schema) {
922
960
  const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
923
- const sourcePath = [moduleId, resolved.path].join(".");
961
+ const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
924
962
  return {
925
963
  path: sourcePath,
926
964
  schema: resolved.schema instanceof core.Schema ? resolved.schema.serialize() : resolved.schema,
@@ -951,14 +989,14 @@ function createRequestHandler(valServer) {
951
989
  router.get("/authorize", valServer.authorize.bind(valServer));
952
990
  router.get("/callback", valServer.callback.bind(valServer));
953
991
  router.get("/logout", valServer.logout.bind(valServer));
954
- router.get("/ids/*", valServer.getIds.bind(valServer));
955
- router.get("/ids", valServer.getAllModules.bind(valServer));
956
- router.patch("/ids/*", express__default["default"].json({
957
- type: "application/json-patch+json",
992
+ router.post("/patches/*", express__default["default"].json({
993
+ type: "application/json",
958
994
  limit: "10mb"
959
- }), valServer.patchIds.bind(valServer));
995
+ }), valServer.postPatches.bind(valServer));
960
996
  router.post("/commit", valServer.commit.bind(valServer));
961
997
  router.get("/enable", valServer.enable.bind(valServer));
998
+ router.get("/disable", valServer.disable.bind(valServer));
999
+ router.get("/tree/*", valServer.getTree.bind(valServer));
962
1000
  return router;
963
1001
  }
964
1002
 
@@ -1062,19 +1100,13 @@ function encodeJwt(payload, sessionKey) {
1062
1100
  return `${jwtHeaderBase64}.${payloadBase64}.${crypto__default["default"].createHmac("sha256", sessionKey).update(`${jwtHeaderBase64}.${payloadBase64}`).digest("base64")}`;
1063
1101
  }
1064
1102
 
1065
- const VAL_SESSION_COOKIE = "val_session";
1066
- 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;
1067
1105
  const VAL_ENABLED_COOKIE = core.Internal.VAL_ENABLE_COOKIE_NAME;
1068
1106
  class ProxyValServer {
1069
1107
  constructor(options) {
1070
1108
  this.options = options;
1071
1109
  }
1072
-
1073
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1074
- getAllModules(_req, _res) {
1075
- // TODO:
1076
- throw new Error("Method not implemented.");
1077
- }
1078
1110
  async authorize(req, res) {
1079
1111
  const {
1080
1112
  redirect_to
@@ -1096,7 +1128,10 @@ class ProxyValServer {
1096
1128
  }).redirect(appAuthorizeUrl);
1097
1129
  }
1098
1130
  async enable(req, res) {
1099
- return 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);
1100
1135
  }
1101
1136
  async callback(req, res) {
1102
1137
  const {
@@ -1158,30 +1193,38 @@ class ProxyValServer {
1158
1193
  }
1159
1194
  });
1160
1195
  }
1161
- async getIds(req, res) {
1162
- return this.withAuth(req, res, async ({
1163
- token
1164
- }) => {
1165
- const id = getPathFromParams(req.params);
1166
- const url = new URL(`/api/val/modules/${encodeURIComponent(this.options.gitCommit)}${id}`, this.options.valBuildUrl);
1167
- const fetchRes = await fetch(url, {
1168
- headers: this.getAuthHeaders(token)
1169
- });
1170
- if (fetchRes.ok) {
1171
- res.status(fetchRes.status).json(await fetchRes.json());
1172
- } else {
1173
- res.sendStatus(fetchRes.status);
1174
- }
1175
- }).catch(e => {
1176
- res.status(500).send({
1177
- error: {
1178
- message: e === null || e === void 0 ? void 0 : e.message,
1179
- status: 500
1180
- }
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()
1181
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);
1182
1213
  });
1183
1214
  }
1184
- 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
+ });
1185
1228
  this.withAuth(req, res, async ({
1186
1229
  token
1187
1230
  }) => {
@@ -1197,12 +1240,11 @@ class ProxyValServer {
1197
1240
  res.status(401).json(patch$1.error);
1198
1241
  return;
1199
1242
  }
1200
- const id = getPathFromParams(req.params);
1201
- 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);
1202
1244
  // Proxy patch to val.build
1203
1245
  const fetchRes = await fetch(url, {
1204
- method: "PATCH",
1205
- headers: this.getAuthHeaders(token, "application/json-patch+json"),
1246
+ method: "POST",
1247
+ headers: this.getAuthHeaders(token, "application/json"),
1206
1248
  body: JSON.stringify(patch$1)
1207
1249
  });
1208
1250
  if (fetchRes.ok) {
@@ -1384,15 +1426,36 @@ function getStateFromCookie(stateCookie) {
1384
1426
  };
1385
1427
  }
1386
1428
  }
1387
- async function enable(req, res) {
1429
+ async function enable(req, res, redirectUrl) {
1388
1430
  const {
1389
1431
  redirect_to
1390
1432
  } = req.query;
1391
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
+ }
1392
1438
  res.cookie(VAL_ENABLED_COOKIE, "true", {
1393
1439
  httpOnly: false,
1394
1440
  sameSite: "lax"
1395
- }).redirect(redirect_to || "/");
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);
1396
1459
  } else {
1397
1460
  res.sendStatus(400);
1398
1461
  }
@@ -1418,53 +1481,75 @@ class LocalValServer {
1418
1481
  constructor(options) {
1419
1482
  this.options = options;
1420
1483
  }
1421
- getAllModules(req, res) {
1422
- // TODO: this barely works,
1423
- const rootDir = process.cwd();
1424
- const moduleIds = [];
1425
- // iterate over all .val files in the root directory
1426
- const walk = async dir => {
1427
- const files = await fs.promises.readdir(dir);
1428
- for (const file of files) {
1429
- if ((await fs.promises.stat(path__default["default"].join(dir, file))).isDirectory()) {
1430
- if (file === "node_modules") continue;
1431
- await walk(path__default["default"].join(dir, file));
1432
- } else {
1433
- if (file.endsWith(".val.js") || file.endsWith(".val.ts")) {
1434
- moduleIds.push(path__default["default"].join(dir, file).replace(rootDir, "").replace(".val.js", "").replace(".val.ts", ""));
1435
- }
1436
- }
1437
- }
1438
- };
1439
- return walk(rootDir).then(async () => {
1440
- res.send(JSON.stringify(await Promise.all(moduleIds.map(async moduleId => {
1441
- return await this.options.service.get(moduleId, "");
1442
- }))));
1443
- });
1444
- }
1445
1484
  async session(_req, res) {
1446
1485
  res.json({
1447
1486
  mode: "local"
1448
1487
  });
1449
1488
  }
1450
- async enable(req, res) {
1451
- return enable(req, res);
1452
- }
1453
- async getIds(req, res) {
1489
+ async getTree(req, res) {
1454
1490
  try {
1455
- console.log(req.params);
1456
- const path = getPathFromParams(req.params);
1457
- const [moduleId, modulePath] = core.Internal.splitModuleIdAndModulePath(path);
1458
- const valModule = await this.options.service.get(moduleId, modulePath);
1459
- res.json(valModule);
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
+ });
1460
1535
  } catch (err) {
1461
1536
  console.error(err);
1462
1537
  res.sendStatus(500);
1463
1538
  }
1464
1539
  }
1465
- async patchIds(req, res) {
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
+
1466
1550
  // First validate that the body has the right structure
1467
1551
  const patchJSON = PatchJSON.safeParse(req.body);
1552
+ console.log("patch id", id, patchJSON);
1468
1553
  if (!patchJSON.success) {
1469
1554
  res.status(401).json(patchJSON.error.issues);
1470
1555
  return;
@@ -1475,16 +1560,19 @@ class LocalValServer {
1475
1560
  res.status(401).json(patch$1.error);
1476
1561
  return;
1477
1562
  }
1478
- const id = getPathFromParams(req.params);
1479
1563
  try {
1480
- const valModule = await this.options.service.patch(id, patch$1.value);
1481
- res.json(valModule);
1564
+ await this.options.service.patch(id, patch$1.value);
1565
+ res.json({});
1482
1566
  } catch (err) {
1483
1567
  if (err instanceof patch.PatchError) {
1484
- res.status(401).send(err.message);
1568
+ res.status(400).send({
1569
+ message: err.message
1570
+ });
1485
1571
  } else {
1486
1572
  console.error(err);
1487
- res.status(500).send(err instanceof Error ? err.message : "Unknown error");
1573
+ res.status(500).send({
1574
+ message: err instanceof Error ? err.message : "Unknown error"
1575
+ });
1488
1576
  }
1489
1577
  }
1490
1578
  }
@@ -1526,6 +1614,7 @@ async function initHandlerOptions(route, opts) {
1526
1614
  if (!maybeApiKey || !maybeValSecret) {
1527
1615
  throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode");
1528
1616
  }
1617
+ const valContentUrl = opts.valContentUrl || process.env.VAL_CONTENT_URL || "https://content.val.build";
1529
1618
  const maybeGitCommit = opts.gitCommit || process.env.VAL_GIT_COMMIT;
1530
1619
  if (!maybeGitCommit) {
1531
1620
  throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode");
@@ -1544,18 +1633,82 @@ async function initHandlerOptions(route, opts) {
1544
1633
  apiKey: maybeApiKey,
1545
1634
  valSecret: maybeValSecret,
1546
1635
  valBuildUrl,
1636
+ valContentUrl,
1547
1637
  gitCommit: maybeGitCommit,
1548
1638
  gitBranch: maybeGitBranch,
1549
- 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
1550
1642
  };
1551
1643
  } else {
1552
- 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);
1553
1647
  return {
1554
1648
  mode: "local",
1555
- service
1649
+ service,
1650
+ git: {
1651
+ commit: process.env.VAL_GIT_COMMIT || git.commit,
1652
+ branch: process.env.VAL_GIT_BRANCH || git.branch
1653
+ }
1556
1654
  };
1557
1655
  }
1558
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
+ }
1559
1712
 
1560
1713
  // TODO: rename to createValApiHandlers?
1561
1714
  function createRequestListener(route, opts) {
@@ -1593,10 +1746,10 @@ class ValFSHost {
1593
1746
  return this.currentDirectory;
1594
1747
  }
1595
1748
  getCanonicalFileName(fileName) {
1596
- if (path__default["default"].isAbsolute(fileName)) {
1597
- return path__default["default"].normalize(fileName);
1749
+ if (path__namespace["default"].isAbsolute(fileName)) {
1750
+ return path__namespace["default"].normalize(fileName);
1598
1751
  }
1599
- return path__default["default"].resolve(this.getCurrentDirectory(), fileName);
1752
+ return path__namespace["default"].resolve(this.getCurrentDirectory(), fileName);
1600
1753
  }
1601
1754
  fileExists(fileName) {
1602
1755
  return this.valFS.fileExists(fileName);
@@ -1617,7 +1770,7 @@ async function createFixPatch(config, apply, sourcePath, validationError) {
1617
1770
  // TODO:
1618
1771
  throw Error("Cannot fix image without a file reference");
1619
1772
  }
1620
- const localFile = path__default["default"].join(config.projectRoot, maybeRef);
1773
+ const localFile = path__namespace["default"].join(config.projectRoot, maybeRef);
1621
1774
  const buffer = fs__default["default"].readFileSync(localFile);
1622
1775
  const sha256 = await getSHA256Hash(buffer);
1623
1776
  const imageSize = sizeOf__default["default"](buffer);
@@ -1734,3 +1887,4 @@ exports.formatSyntaxErrorTree = formatSyntaxErrorTree;
1734
1887
  exports.getCompilerOptions = getCompilerOptions;
1735
1888
  exports.getExpire = getExpire;
1736
1889
  exports.patchSourceFile = patchSourceFile;
1890
+ exports.safeReadGit = safeReadGit;