@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.
@@ -5,30 +5,53 @@ import { parsePatch, PatchError } from "@valbuild/core/patch";
5
5
  import { getPathFromParams } from "./expressHelpers";
6
6
  import { PatchJSON } from "./patch/validation";
7
7
  import { ValServer } from "./ValServer";
8
- import { Internal, ModuleId, ModulePath } from "@valbuild/core";
9
- import { enable } from "./ProxyValServer";
8
+ import { ApiTreeResponse, ModuleId, ModulePath } from "@valbuild/core";
9
+ import { disable, enable } from "./ProxyValServer";
10
10
  import { promises as fs } from "fs";
11
11
  import path from "path";
12
12
 
13
13
  export type LocalValServerOptions = {
14
14
  service: Service;
15
+ git: {
16
+ commit?: string;
17
+ branch?: string;
18
+ };
15
19
  };
16
20
 
17
21
  export class LocalValServer implements ValServer {
18
22
  constructor(readonly options: LocalValServerOptions) {}
19
- getAllModules(req: express.Request, res: express.Response): Promise<void> {
20
- // TODO: this barely works,
21
- const rootDir = process.cwd();
22
- const moduleIds: string[] = [];
23
- // iterate over all .val files in the root directory
24
- const walk = async (dir: string) => {
25
- const files = await fs.readdir(dir);
26
- for (const file of files) {
27
- if ((await fs.stat(path.join(dir, file))).isDirectory()) {
28
- if (file === "node_modules") continue;
29
- await walk(path.join(dir, file));
30
- } else {
31
- if (file.endsWith(".val.js") || file.endsWith(".val.ts")) {
23
+
24
+ async session(_req: express.Request, res: express.Response): Promise<void> {
25
+ res.json({
26
+ mode: "local",
27
+ });
28
+ }
29
+
30
+ async getTree(req: express.Request, res: express.Response): Promise<void> {
31
+ try {
32
+ // TODO: use the params: patch, schema, source
33
+ const treePath = req.params["0"].replace("~", "");
34
+ const rootDir = process.cwd();
35
+ const moduleIds: string[] = [];
36
+ // iterate over all .val files in the root directory
37
+ const walk = async (dir: string) => {
38
+ const files = await fs.readdir(dir);
39
+ for (const file of files) {
40
+ if ((await fs.stat(path.join(dir, file))).isDirectory()) {
41
+ if (file === "node_modules") continue;
42
+ await walk(path.join(dir, file));
43
+ } else {
44
+ const isValFile =
45
+ file.endsWith(".val.js") || file.endsWith(".val.ts");
46
+ if (!isValFile) {
47
+ continue;
48
+ }
49
+ if (
50
+ treePath &&
51
+ !path.join(dir, file).replace(rootDir, "").startsWith(treePath)
52
+ ) {
53
+ continue;
54
+ }
32
55
  moduleIds.push(
33
56
  path
34
57
  .join(dir, file)
@@ -38,59 +61,59 @@ export class LocalValServer implements ValServer {
38
61
  );
39
62
  }
40
63
  }
41
- }
42
- };
64
+ };
65
+ const serializedModuleContent = await walk(rootDir).then(async () => {
66
+ return Promise.all(
67
+ moduleIds.map(async (moduleId) => {
68
+ return await this.options.service.get(
69
+ moduleId as ModuleId,
70
+ "" as ModulePath
71
+ );
72
+ })
73
+ );
74
+ });
43
75
 
44
- return walk(rootDir).then(async () => {
45
- res.send(
46
- JSON.stringify(
47
- await Promise.all(
48
- moduleIds.map(async (moduleId) => {
49
- return await this.options.service.get(
50
- moduleId as ModuleId,
51
- "" as ModulePath
52
- );
53
- })
54
- )
55
- )
76
+ //
77
+ const modules = Object.fromEntries(
78
+ serializedModuleContent.map((serializedModuleContent) => {
79
+ const module: ApiTreeResponse["modules"][keyof ApiTreeResponse["modules"]] =
80
+ {
81
+ schema: serializedModuleContent.schema,
82
+ source: serializedModuleContent.source,
83
+ };
84
+ return [serializedModuleContent.path, module];
85
+ })
56
86
  );
57
- });
58
- }
59
-
60
- async session(_req: express.Request, res: express.Response): Promise<void> {
61
- res.json({
62
- mode: "local",
63
- });
87
+ const apiTreeResponse: ApiTreeResponse = {
88
+ modules,
89
+ git: this.options.git,
90
+ };
91
+ return walk(rootDir).then(async () => {
92
+ res.send(JSON.stringify(apiTreeResponse));
93
+ });
94
+ } catch (err) {
95
+ console.error(err);
96
+ res.sendStatus(500);
97
+ }
64
98
  }
65
99
 
66
100
  async enable(req: express.Request, res: express.Response): Promise<void> {
67
101
  return enable(req, res);
68
102
  }
69
103
 
70
- async getIds(
71
- req: express.Request<{ 0: string }>,
72
- res: express.Response
73
- ): Promise<void> {
74
- try {
75
- console.log(req.params);
76
- const path = getPathFromParams(req.params);
77
- const [moduleId, modulePath] = Internal.splitModuleIdAndModulePath(path);
78
-
79
- const valModule = await this.options.service.get(moduleId, modulePath);
80
-
81
- res.json(valModule);
82
- } catch (err) {
83
- console.error(err);
84
- res.sendStatus(500);
85
- }
104
+ async disable(req: express.Request, res: express.Response): Promise<void> {
105
+ return disable(req, res);
86
106
  }
87
107
 
88
- async patchIds(
108
+ async postPatches(
89
109
  req: express.Request<{ 0: string }>,
90
110
  res: express.Response
91
111
  ): Promise<void> {
112
+ const id = getPathFromParams(req.params)?.replace("/~", "");
113
+
92
114
  // First validate that the body has the right structure
93
115
  const patchJSON = PatchJSON.safeParse(req.body);
116
+ console.log("patch id", id, patchJSON);
94
117
  if (!patchJSON.success) {
95
118
  res.status(401).json(patchJSON.error.issues);
96
119
  return;
@@ -101,18 +124,17 @@ export class LocalValServer implements ValServer {
101
124
  res.status(401).json(patch.error);
102
125
  return;
103
126
  }
104
- const id = getPathFromParams(req.params);
105
127
  try {
106
- const valModule = await this.options.service.patch(id, patch.value);
107
- res.json(valModule);
128
+ await this.options.service.patch(id, patch.value);
129
+ res.json({});
108
130
  } catch (err) {
109
131
  if (err instanceof PatchError) {
110
- res.status(401).send(err.message);
132
+ res.status(400).send({ message: err.message });
111
133
  } else {
112
134
  console.error(err);
113
- res
114
- .status(500)
115
- .send(err instanceof Error ? err.message : "Unknown error");
135
+ res.status(500).send({
136
+ message: err instanceof Error ? err.message : "Unknown error",
137
+ });
116
138
  }
117
139
  }
118
140
  }
@@ -3,14 +3,13 @@ import crypto from "crypto";
3
3
  import { decodeJwt, encodeJwt, getExpire } from "./jwt";
4
4
  import { PatchJSON } from "./patch/validation";
5
5
  import { result } from "@valbuild/core/fp";
6
- import { getPathFromParams } from "./expressHelpers";
7
6
  import { ValServer } from "./ValServer";
8
7
  import { z } from "zod";
9
8
  import { parsePatch } from "@valbuild/core/patch";
10
9
  import { Internal } from "@valbuild/core";
11
10
 
12
- const VAL_SESSION_COOKIE = "val_session";
13
- const VAL_STATE_COOKIE = "val_state";
11
+ const VAL_SESSION_COOKIE = Internal.VAL_SESSION_COOKIE;
12
+ const VAL_STATE_COOKIE = Internal.VAL_STATE_COOKIE;
14
13
  const VAL_ENABLED_COOKIE = Internal.VAL_ENABLE_COOKIE_NAME;
15
14
 
16
15
  export type ProxyValServerOptions = {
@@ -18,20 +17,17 @@ export type ProxyValServerOptions = {
18
17
  route: string;
19
18
  valSecret: string;
20
19
  valBuildUrl: string;
20
+ valContentUrl: string;
21
21
  gitCommit: string;
22
22
  gitBranch: string;
23
23
  valName: string;
24
+ valEnableRedirectUrl?: string;
25
+ valDisableRedirectUrl?: string;
24
26
  };
25
27
 
26
28
  export class ProxyValServer implements ValServer {
27
29
  constructor(readonly options: ProxyValServerOptions) {}
28
30
 
29
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
30
- getAllModules(_req: express.Request, _res: express.Response): Promise<void> {
31
- // TODO:
32
- throw new Error("Method not implemented.");
33
- }
34
-
35
31
  async authorize(req: express.Request, res: express.Response): Promise<void> {
36
32
  const { redirect_to } = req.query;
37
33
  if (typeof redirect_to !== "string") {
@@ -56,7 +52,10 @@ export class ProxyValServer implements ValServer {
56
52
  }
57
53
 
58
54
  async enable(req: express.Request, res: express.Response): Promise<void> {
59
- return enable(req, res);
55
+ return enable(req, res, this.options.valEnableRedirectUrl);
56
+ }
57
+ async disable(req: express.Request, res: express.Response): Promise<void> {
58
+ return disable(req, res, this.options.valEnableRedirectUrl);
60
59
  }
61
60
 
62
61
  async callback(req: express.Request, res: express.Response): Promise<void> {
@@ -143,33 +142,37 @@ export class ProxyValServer implements ValServer {
143
142
  });
144
143
  }
145
144
 
146
- async getIds(
147
- req: express.Request<{ 0: string }>,
148
- res: express.Response
149
- ): Promise<void> {
150
- return this.withAuth(req, res, async ({ token }) => {
151
- const id = getPathFromParams(req.params);
145
+ async getTree(req: express.Request, res: express.Response): Promise<void> {
146
+ return this.withAuth(req, res, async (data) => {
147
+ const { patch, schema, source } = req.query;
148
+ const params = new URLSearchParams({
149
+ patch: (patch === "true").toString(),
150
+ schema: (schema === "true").toString(),
151
+ source: (source === "true").toString(),
152
+ });
152
153
  const url = new URL(
153
- `/api/val/modules/${encodeURIComponent(this.options.gitCommit)}${id}`,
154
- this.options.valBuildUrl
154
+ `/v1/tree/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`,
155
+ this.options.valContentUrl
155
156
  );
156
- const fetchRes = await fetch(url, {
157
- headers: this.getAuthHeaders(token),
158
- });
159
- if (fetchRes.ok) {
160
- res.status(fetchRes.status).json(await fetchRes.json());
161
- } else {
162
- res.sendStatus(fetchRes.status);
163
- }
164
- }).catch((e) => {
165
- res.status(500).send({ error: { message: e?.message, status: 500 } });
157
+ const json = await fetch(url, {
158
+ headers: this.getAuthHeaders(data.token, "application/json"),
159
+ }).then((res) => res.json());
160
+ res.send(json);
166
161
  });
167
162
  }
168
163
 
169
- async patchIds(
164
+ async postPatches(
170
165
  req: express.Request<{ 0: string }>,
171
166
  res: express.Response
172
167
  ): Promise<void> {
168
+ const { commit } = req.query;
169
+ if (typeof commit !== "string" || typeof commit === "undefined") {
170
+ res.status(401).json({ error: "Missing or invalid commit query param" });
171
+ return;
172
+ }
173
+ const params = new URLSearchParams({
174
+ commit,
175
+ });
173
176
  this.withAuth(req, res, async ({ token }) => {
174
177
  // First validate that the body has the right structure
175
178
  const patchJSON = PatchJSON.safeParse(req.body);
@@ -183,15 +186,14 @@ export class ProxyValServer implements ValServer {
183
186
  res.status(401).json(patch.error);
184
187
  return;
185
188
  }
186
- const id = getPathFromParams(req.params);
187
189
  const url = new URL(
188
- `/api/val/modules/${encodeURIComponent(this.options.gitCommit)}${id}`,
189
- this.options.valBuildUrl
190
+ `/v1/tree/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`,
191
+ this.options.valContentUrl
190
192
  );
191
193
  // Proxy patch to val.build
192
194
  const fetchRes = await fetch(url, {
193
- method: "PATCH",
194
- headers: this.getAuthHeaders(token, "application/json-patch+json"),
195
+ method: "POST",
196
+ headers: this.getAuthHeaders(token, "application/json"),
195
197
  body: JSON.stringify(patch),
196
198
  });
197
199
  if (fetchRes.ok) {
@@ -400,16 +402,44 @@ function getStateFromCookie(stateCookie: string):
400
402
 
401
403
  export async function enable(
402
404
  req: express.Request,
403
- res: express.Response
405
+ res: express.Response,
406
+ redirectUrl?: string
404
407
  ): Promise<void> {
405
408
  const { redirect_to } = req.query;
406
409
  if (typeof redirect_to === "string" || typeof redirect_to === "undefined") {
410
+ let redirectUrlToUse = redirect_to || "/";
411
+ if (redirectUrl) {
412
+ redirectUrlToUse =
413
+ redirectUrl + "?redirect_to=" + encodeURIComponent(redirectUrlToUse);
414
+ }
407
415
  res
408
416
  .cookie(VAL_ENABLED_COOKIE, "true", {
409
417
  httpOnly: false,
410
418
  sameSite: "lax",
411
419
  })
412
- .redirect(redirect_to || "/");
420
+ .redirect(redirectUrlToUse);
421
+ } else {
422
+ res.sendStatus(400);
423
+ }
424
+ }
425
+ export async function disable(
426
+ req: express.Request,
427
+ res: express.Response,
428
+ redirectUrl?: string
429
+ ): Promise<void> {
430
+ const { redirect_to } = req.query;
431
+ if (typeof redirect_to === "string" || typeof redirect_to === "undefined") {
432
+ let redirectUrlToUse = redirect_to || "/";
433
+ if (redirectUrl) {
434
+ redirectUrlToUse =
435
+ redirectUrl + "?redirect_to=" + encodeURIComponent(redirectUrlToUse);
436
+ }
437
+ res
438
+ .cookie(VAL_ENABLED_COOKIE, "false", {
439
+ httpOnly: false,
440
+ sameSite: "lax",
441
+ })
442
+ .redirect(redirectUrlToUse);
413
443
  } else {
414
444
  res.sendStatus(400);
415
445
  }
package/src/Service.ts CHANGED
@@ -16,7 +16,6 @@ import {
16
16
  Internal,
17
17
  SourcePath,
18
18
  Schema,
19
- SelectorSource,
20
19
  } from "@valbuild/core";
21
20
 
22
21
  export type ServiceOptions = {
@@ -79,11 +78,13 @@ export class Service {
79
78
  valModule.source,
80
79
  valModule.schema
81
80
  );
82
- const sourcePath = [moduleId, resolved.path].join(".") as SourcePath;
81
+ const sourcePath = (
82
+ resolved.path ? [moduleId, resolved.path].join(".") : moduleId
83
+ ) as SourcePath;
83
84
  return {
84
85
  path: sourcePath,
85
86
  schema:
86
- resolved.schema instanceof Schema<SelectorSource>
87
+ resolved.schema instanceof Schema
87
88
  ? resolved.schema.serialize()
88
89
  : resolved.schema,
89
90
  source: resolved.source,
@@ -34,6 +34,18 @@ export async function newValQuickJSRuntime(
34
34
  "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 */ };",
35
35
  };
36
36
  }
37
+ if (modulePath.startsWith("next")) {
38
+ return {
39
+ value:
40
+ "export default new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'next' in this file`) } } } )",
41
+ };
42
+ }
43
+ if (modulePath.startsWith("react")) {
44
+ return {
45
+ value:
46
+ "export default new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'react' in this file`) } } } )",
47
+ };
48
+ }
37
49
  return { value: moduleLoader.getModule(modulePath) };
38
50
  } catch (e) {
39
51
  return {
@@ -49,6 +61,12 @@ export async function newValQuickJSRuntime(
49
61
  if (requestedName === "@valbuild/react/stega") {
50
62
  return { value: requestedName };
51
63
  }
64
+ if (requestedName.startsWith("next")) {
65
+ return { value: requestedName };
66
+ }
67
+ if (requestedName.startsWith("react")) {
68
+ return { value: requestedName };
69
+ }
52
70
  const modulePath = moduleLoader.resolveModulePath(
53
71
  baseModuleName,
54
72
  requestedName
package/src/ValServer.ts CHANGED
@@ -9,17 +9,7 @@ export interface ValServer {
9
9
 
10
10
  session(req: express.Request, res: express.Response): Promise<void>;
11
11
 
12
- getIds(
13
- req: express.Request<{ 0: string }>,
14
- res: express.Response
15
- ): Promise<void>;
16
-
17
- getAllModules(
18
- req: express.Request<{ 0: string }>,
19
- res: express.Response
20
- ): Promise<void>;
21
-
22
- patchIds(
12
+ postPatches(
23
13
  req: express.Request<{ 0: string }>,
24
14
  res: express.Response
25
15
  ): Promise<void>;
@@ -27,4 +17,7 @@ export interface ValServer {
27
17
  commit(req: express.Request, res: express.Response): Promise<void>;
28
18
 
29
19
  enable(req: express.Request, res: express.Response): Promise<void>;
20
+ disable(req: express.Request, res: express.Response): Promise<void>;
21
+
22
+ getTree(req: express.Request, res: express.Response): Promise<void>;
30
23
  }
@@ -10,17 +10,17 @@ export function createRequestHandler(valServer: ValServer): RequestHandler {
10
10
  router.get("/authorize", valServer.authorize.bind(valServer));
11
11
  router.get("/callback", valServer.callback.bind(valServer));
12
12
  router.get("/logout", valServer.logout.bind(valServer));
13
- router.get<{ 0: string }>("/ids/*", valServer.getIds.bind(valServer));
14
- router.get("/ids", valServer.getAllModules.bind(valServer));
15
- router.patch<{ 0: string }>(
16
- "/ids/*",
13
+ router.post<{ 0: string }>(
14
+ "/patches/*",
17
15
  express.json({
18
- type: "application/json-patch+json",
16
+ type: "application/json",
19
17
  limit: "10mb",
20
18
  }),
21
- valServer.patchIds.bind(valServer)
19
+ valServer.postPatches.bind(valServer)
22
20
  );
23
21
  router.post("/commit", valServer.commit.bind(valServer));
24
22
  router.get("/enable", valServer.enable.bind(valServer));
23
+ router.get("/disable", valServer.disable.bind(valServer));
24
+ router.get("/tree/*", valServer.getTree.bind(valServer));
25
25
  return router;
26
26
  }
package/src/hosting.ts CHANGED
@@ -5,6 +5,8 @@ import { createRequestHandler } from "./createRequestHandler";
5
5
  import { ValServer } from "./ValServer";
6
6
  import { LocalValServer, LocalValServerOptions } from "./LocalValServer";
7
7
  import { ProxyValServer, ProxyValServerOptions } from "./ProxyValServer";
8
+ import { promises as fs } from "fs";
9
+ import * as path from "path";
8
10
 
9
11
  type Opts = ValServerOverrides & ServiceOptions;
10
12
 
@@ -73,6 +75,16 @@ type ValServerOverrides = Partial<{
73
75
  * @example "https://app.val.build"
74
76
  */
75
77
  valBuildUrl: string;
78
+ /**
79
+ * The base url of Val content.
80
+ *
81
+ * Typically this should not be set.
82
+ *
83
+ * Can also be overridden using the VAL_CONTENT_URL env var.
84
+ *
85
+ * @example "https://content.val.build"
86
+ */
87
+ valContentUrl: string;
76
88
  /**
77
89
  * The full name of this Val project.
78
90
  *
@@ -81,6 +93,26 @@ type ValServerOverrides = Partial<{
81
93
  * @example "myorg/my-project"
82
94
  */
83
95
  valName: string;
96
+ /**
97
+ * After Val is enabled, redirect to this url.
98
+ *
99
+ * May be used to setup a custom flow after enabling Val.
100
+ *
101
+ *This can be set using the VAL_ENABLE_REDIRECT_URL env var.
102
+ *
103
+ * @example "/api/draft/enable"
104
+ */
105
+ valEnableRedirectUrl?: string;
106
+ /**
107
+ * After Val is disabled, redirect to this url.
108
+ *
109
+ * May be used to setup a custom flow after disabling Val.
110
+ *
111
+ * This can be set using the VAL_DISABLE_REDIRECT_URL env var.
112
+ *
113
+ * @example "/api/draft/enable"
114
+ */
115
+ valDisableRedirectUrl?: string;
84
116
  }>;
85
117
 
86
118
  async function _createRequestListener(
@@ -119,6 +151,10 @@ async function initHandlerOptions(
119
151
  "VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode"
120
152
  );
121
153
  }
154
+ const valContentUrl =
155
+ opts.valContentUrl ||
156
+ process.env.VAL_CONTENT_URL ||
157
+ "https://content.val.build";
122
158
  const maybeGitCommit = opts.gitCommit || process.env.VAL_GIT_COMMIT;
123
159
  if (!maybeGitCommit) {
124
160
  throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode");
@@ -137,19 +173,104 @@ async function initHandlerOptions(
137
173
  apiKey: maybeApiKey,
138
174
  valSecret: maybeValSecret,
139
175
  valBuildUrl,
176
+ valContentUrl,
140
177
  gitCommit: maybeGitCommit,
141
178
  gitBranch: maybeGitBranch,
142
179
  valName: maybeValName,
180
+ valEnableRedirectUrl:
181
+ opts.valEnableRedirectUrl || process.env.VAL_ENABLE_REDIRECT_URL,
182
+ valDisableRedirectUrl:
183
+ opts.valDisableRedirectUrl || process.env.VAL_DISABLE_REDIRECT_URL,
143
184
  };
144
185
  } else {
145
- const service = await createService(process.cwd(), opts);
186
+ const cwd = process.cwd();
187
+ const service = await createService(cwd, opts);
188
+ const git = await safeReadGit(cwd);
146
189
  return {
147
190
  mode: "local",
148
191
  service,
192
+ git: {
193
+ commit: process.env.VAL_GIT_COMMIT || git.commit,
194
+ branch: process.env.VAL_GIT_BRANCH || git.branch,
195
+ },
149
196
  };
150
197
  }
151
198
  }
152
199
 
200
+ export async function safeReadGit(
201
+ cwd: string
202
+ ): Promise<{ commit?: string; branch?: string }> {
203
+ async function findGitHead(
204
+ currentDir: string,
205
+ depth: number
206
+ ): Promise<{ commit?: string; branch?: string }> {
207
+ const gitHeadPath = path.join(currentDir, ".git", "HEAD");
208
+ if (depth > 1000) {
209
+ console.error(
210
+ `Reached max depth while scanning for .git folder. Current working dir: ${cwd}.`
211
+ );
212
+ return {
213
+ commit: undefined,
214
+ branch: undefined,
215
+ };
216
+ }
217
+
218
+ try {
219
+ const headContents = await fs.readFile(gitHeadPath, "utf-8");
220
+ const match = headContents.match(/^ref: refs\/heads\/(.+)/);
221
+ if (match) {
222
+ const branchName = match[1];
223
+ return {
224
+ branch: branchName,
225
+ commit: await readCommit(currentDir, branchName),
226
+ };
227
+ } else {
228
+ return {
229
+ commit: undefined,
230
+ branch: undefined,
231
+ };
232
+ }
233
+ } catch (error) {
234
+ const parentDir = path.dirname(currentDir);
235
+
236
+ // We've reached the root directory
237
+ if (parentDir === currentDir) {
238
+ return {
239
+ commit: undefined,
240
+ branch: undefined,
241
+ };
242
+ }
243
+ return findGitHead(parentDir, depth + 1);
244
+ }
245
+ }
246
+
247
+ try {
248
+ return findGitHead(cwd, 0);
249
+ } catch (err) {
250
+ console.error("Error while reading .git", err);
251
+ return {
252
+ commit: undefined,
253
+ branch: undefined,
254
+ };
255
+ }
256
+ }
257
+
258
+ async function readCommit(
259
+ gitDir: string,
260
+ branchName: string
261
+ ): Promise<string | undefined> {
262
+ try {
263
+ return (
264
+ await fs.readFile(
265
+ path.join(gitDir, ".git", "refs", "heads", branchName),
266
+ "utf-8"
267
+ )
268
+ ).trim();
269
+ } catch (err) {
270
+ return undefined;
271
+ }
272
+ }
273
+
153
274
  // TODO: rename to createValApiHandlers?
154
275
  export function createRequestListener(
155
276
  route: string,
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type { ServiceOptions } from "./Service";
2
2
  export { createService, Service } from "./Service";
3
3
  export { createRequestHandler } from "./createRequestHandler";
4
- export { createRequestListener } from "./hosting";
4
+ export { createRequestListener, safeReadGit } from "./hosting";
5
5
  export { ValModuleLoader } from "./ValModuleLoader";
6
6
  export { getCompilerOptions } from "./getCompilerOptions";
7
7
  export { ValSourceFileHandler } from "./ValSourceFileHandler";
@@ -1,4 +1,3 @@
1
- import { describe, test, expect } from "@jest/globals";
2
1
  import ts from "typescript";
3
2
  import { TSOps } from "./ops";
4
3
  import { result, array, pipe } from "@valbuild/core/fp";
@@ -16,10 +16,10 @@ import { Internal } from "@valbuild/core";
16
16
  globalThis.valModule = {
17
17
  id: valModule?.default && Internal.getValPath(valModule?.default),
18
18
  schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
19
- source: valModule?.default && Internal.getRawSource(valModule?.default),
19
+ source: valModule?.default && Internal.getSource(valModule?.default),
20
20
  validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
21
21
  valModule?.default && Internal.getValPath(valModule?.default) || "/",
22
- valModule?.default && Internal.getRawSource(valModule?.default)
22
+ valModule?.default && Internal.getSource(valModule?.default)
23
23
  )
24
24
  };
25
25
  `;