@valbuild/server 0.26.0 → 0.27.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/package.json +7 -4
- package/.babelrc.json +0 -5
- package/CHANGELOG.md +0 -0
- package/jest.config.js +0 -4
- package/src/LocalValServer.ts +0 -167
- package/src/ProxyValServer.ts +0 -542
- package/src/SerializedModuleContent.ts +0 -36
- package/src/Service.ts +0 -126
- package/src/ValFS.ts +0 -22
- package/src/ValFSHost.ts +0 -66
- package/src/ValModuleLoader.test.ts +0 -75
- package/src/ValModuleLoader.ts +0 -158
- package/src/ValQuickJSRuntime.ts +0 -85
- package/src/ValServer.ts +0 -24
- package/src/ValSourceFileHandler.ts +0 -57
- package/src/createFixPatch.ts +0 -170
- package/src/createRequestHandler.ts +0 -27
- package/src/expressHelpers.ts +0 -5
- package/src/getCompilerOptions.ts +0 -50
- package/src/hosting.ts +0 -290
- package/src/index.ts +0 -16
- package/src/jwt.ts +0 -93
- package/src/patch/ts/ops.test.ts +0 -937
- package/src/patch/ts/ops.ts +0 -897
- package/src/patch/ts/syntax.ts +0 -371
- package/src/patch/ts/valModule.test.ts +0 -26
- package/src/patch/ts/valModule.ts +0 -110
- package/src/patch/validation.ts +0 -81
- package/src/patchValFile.ts +0 -110
- package/src/readValFile.test.ts +0 -49
- package/src/readValFile.ts +0 -96
- package/test/example-projects/basic-next-javascript/jsconfig.json +0 -8
- package/test/example-projects/basic-next-javascript/package.json +0 -23
- package/test/example-projects/basic-next-javascript/pages/blogs.val.js +0 -20
- package/test/example-projects/basic-next-javascript/val.config.js +0 -4
- package/test/example-projects/basic-next-src-typescript/package.json +0 -23
- package/test/example-projects/basic-next-src-typescript/src/pages/blogs.val.ts +0 -20
- package/test/example-projects/basic-next-src-typescript/src/val.config.ts +0 -5
- package/test/example-projects/basic-next-src-typescript/tsconfig.json +0 -24
- package/test/example-projects/basic-next-typescript/package.json +0 -23
- package/test/example-projects/basic-next-typescript/pages/blogs.val.ts +0 -20
- package/test/example-projects/basic-next-typescript/tsconfig.json +0 -25
- package/test/example-projects/basic-next-typescript/val.config.ts +0 -5
- package/test/example-projects/typescript-description-files/README.md +0 -2
- package/test/example-projects/typescript-description-files/jsconfig.json +0 -8
- package/test/example-projects/typescript-description-files/package.json +0 -23
- package/test/example-projects/typescript-description-files/pages/blogs.val.d.ts +0 -7
- package/test/example-projects/typescript-description-files/pages/blogs.val.js +0 -19
- package/test/example-projects/typescript-description-files/val.config.d.ts +0 -3
- package/test/example-projects/typescript-description-files/val.config.js +0 -5
- package/tsconfig.json +0 -12
package/src/ProxyValServer.ts
DELETED
@@ -1,542 +0,0 @@
|
|
1
|
-
import express from "express";
|
2
|
-
import crypto from "crypto";
|
3
|
-
import { decodeJwt, encodeJwt, getExpire } from "./jwt";
|
4
|
-
import { PatchJSON } from "./patch/validation";
|
5
|
-
import { ValServer } from "./ValServer";
|
6
|
-
import { z } from "zod";
|
7
|
-
import { Internal } from "@valbuild/core";
|
8
|
-
import { Readable } from "stream";
|
9
|
-
|
10
|
-
const VAL_SESSION_COOKIE = Internal.VAL_SESSION_COOKIE;
|
11
|
-
const VAL_STATE_COOKIE = Internal.VAL_STATE_COOKIE;
|
12
|
-
const VAL_ENABLED_COOKIE = Internal.VAL_ENABLE_COOKIE_NAME;
|
13
|
-
|
14
|
-
export type ProxyValServerOptions = {
|
15
|
-
apiKey: string;
|
16
|
-
route: string;
|
17
|
-
valSecret: string;
|
18
|
-
valBuildUrl: string;
|
19
|
-
valContentUrl: string;
|
20
|
-
gitCommit: string;
|
21
|
-
gitBranch: string;
|
22
|
-
valName: string;
|
23
|
-
valEnableRedirectUrl?: string;
|
24
|
-
valDisableRedirectUrl?: string;
|
25
|
-
};
|
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
|
-
|
51
|
-
export class ProxyValServer implements ValServer {
|
52
|
-
constructor(readonly options: ProxyValServerOptions) {}
|
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
|
-
|
89
|
-
async authorize(req: express.Request, res: express.Response): Promise<void> {
|
90
|
-
const { redirect_to } = req.query;
|
91
|
-
if (typeof redirect_to !== "string") {
|
92
|
-
res.redirect(
|
93
|
-
this.getAppErrorUrl("Login failed: missing redirect_to param")
|
94
|
-
);
|
95
|
-
return;
|
96
|
-
}
|
97
|
-
const token = crypto.randomUUID();
|
98
|
-
const redirectUrl = new URL(redirect_to);
|
99
|
-
const appAuthorizeUrl = this.getAuthorizeUrl(
|
100
|
-
`${redirectUrl.origin}/${this.options.route}`,
|
101
|
-
token
|
102
|
-
);
|
103
|
-
res
|
104
|
-
.cookie(VAL_STATE_COOKIE, createStateCookie({ redirect_to, token }), {
|
105
|
-
httpOnly: true,
|
106
|
-
sameSite: "lax",
|
107
|
-
expires: new Date(Date.now() + 1000 * 60 * 60), // 1 hour
|
108
|
-
})
|
109
|
-
.redirect(appAuthorizeUrl);
|
110
|
-
}
|
111
|
-
|
112
|
-
async enable(req: express.Request, res: express.Response): Promise<void> {
|
113
|
-
return enable(req, res, this.options.valEnableRedirectUrl);
|
114
|
-
}
|
115
|
-
async disable(req: express.Request, res: express.Response): Promise<void> {
|
116
|
-
return disable(req, res, this.options.valEnableRedirectUrl);
|
117
|
-
}
|
118
|
-
|
119
|
-
async callback(req: express.Request, res: express.Response): Promise<void> {
|
120
|
-
const { success: callbackReqSuccess, error: callbackReqError } =
|
121
|
-
verifyCallbackReq(req.cookies[VAL_STATE_COOKIE], req.query);
|
122
|
-
res.clearCookie(VAL_STATE_COOKIE); // we don't need this anymore
|
123
|
-
|
124
|
-
if (callbackReqError !== null) {
|
125
|
-
res.redirect(
|
126
|
-
this.getAppErrorUrl(
|
127
|
-
`Authorization callback failed. Details: ${callbackReqError}`
|
128
|
-
)
|
129
|
-
);
|
130
|
-
return;
|
131
|
-
}
|
132
|
-
|
133
|
-
const data = await this.consumeCode(callbackReqSuccess.code);
|
134
|
-
if (data === null) {
|
135
|
-
res.redirect(this.getAppErrorUrl("Failed to exchange code for user"));
|
136
|
-
return;
|
137
|
-
}
|
138
|
-
const exp = getExpire();
|
139
|
-
const cookie = encodeJwt(
|
140
|
-
{
|
141
|
-
...data,
|
142
|
-
exp, // this is the client side exp
|
143
|
-
},
|
144
|
-
this.options.valSecret
|
145
|
-
);
|
146
|
-
|
147
|
-
res
|
148
|
-
.cookie(VAL_SESSION_COOKIE, cookie, {
|
149
|
-
httpOnly: true,
|
150
|
-
sameSite: "strict",
|
151
|
-
secure: true,
|
152
|
-
expires: new Date(exp * 1000), // NOTE: this is not used for authorization, only for authentication
|
153
|
-
})
|
154
|
-
.redirect(callbackReqSuccess.redirect_uri || "/");
|
155
|
-
}
|
156
|
-
|
157
|
-
async logout(_req: express.Request, res: express.Response): Promise<void> {
|
158
|
-
res
|
159
|
-
.clearCookie(VAL_SESSION_COOKIE)
|
160
|
-
.clearCookie(VAL_STATE_COOKIE)
|
161
|
-
.sendStatus(200);
|
162
|
-
}
|
163
|
-
|
164
|
-
async withAuth<T>(
|
165
|
-
req: express.Request,
|
166
|
-
res: express.Response,
|
167
|
-
handler: (data: IntegratedServerJwtPayload) => Promise<T>
|
168
|
-
): Promise<T | undefined> {
|
169
|
-
const cookie = req.cookies[VAL_SESSION_COOKIE];
|
170
|
-
if (typeof cookie === "string") {
|
171
|
-
const verification = IntegratedServerJwtPayload.safeParse(
|
172
|
-
decodeJwt(cookie, this.options.valSecret)
|
173
|
-
);
|
174
|
-
if (!verification.success) {
|
175
|
-
res.sendStatus(401);
|
176
|
-
return;
|
177
|
-
}
|
178
|
-
return handler(verification.data);
|
179
|
-
} else {
|
180
|
-
res.sendStatus(401);
|
181
|
-
}
|
182
|
-
}
|
183
|
-
|
184
|
-
async session(req: express.Request, res: express.Response): Promise<void> {
|
185
|
-
return this.withAuth(req, res, async (data) => {
|
186
|
-
const url = new URL(
|
187
|
-
`/api/val/${this.options.valName}/auth/session`,
|
188
|
-
this.options.valBuildUrl
|
189
|
-
);
|
190
|
-
const fetchRes = await fetch(url, {
|
191
|
-
headers: this.getAuthHeaders(data.token, "application/json"),
|
192
|
-
});
|
193
|
-
if (fetchRes.ok) {
|
194
|
-
res
|
195
|
-
.status(fetchRes.status)
|
196
|
-
.json({ mode: "proxy", ...(await fetchRes.json()) });
|
197
|
-
} else {
|
198
|
-
res.sendStatus(fetchRes.status);
|
199
|
-
}
|
200
|
-
});
|
201
|
-
}
|
202
|
-
|
203
|
-
async getTree(req: express.Request, res: express.Response): Promise<void> {
|
204
|
-
return this.withAuth(req, res, async (data) => {
|
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
|
-
}
|
214
|
-
const params = new URLSearchParams({
|
215
|
-
patch: (patch === "true").toString(),
|
216
|
-
schema: (schema === "true").toString(),
|
217
|
-
source: (source === "true").toString(),
|
218
|
-
commit,
|
219
|
-
});
|
220
|
-
const url = new URL(
|
221
|
-
`/v1/tree/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`,
|
222
|
-
this.options.valContentUrl
|
223
|
-
);
|
224
|
-
const json = await fetch(url, {
|
225
|
-
headers: this.getAuthHeaders(data.token, "application/json"),
|
226
|
-
}).then((res) => res.json());
|
227
|
-
res.send(json);
|
228
|
-
});
|
229
|
-
}
|
230
|
-
|
231
|
-
async postPatches(
|
232
|
-
req: express.Request<{ 0: string }>,
|
233
|
-
res: express.Response
|
234
|
-
): Promise<void> {
|
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
|
-
});
|
241
|
-
return;
|
242
|
-
}
|
243
|
-
const params = new URLSearchParams({
|
244
|
-
commit,
|
245
|
-
});
|
246
|
-
this.withAuth(req, res, async ({ token }) => {
|
247
|
-
// First validate that the body has the right structure
|
248
|
-
const patchJSON = z.record(PatchJSON).safeParse(req.body);
|
249
|
-
if (!patchJSON.success) {
|
250
|
-
res.status(401).json(patchJSON.error.issues);
|
251
|
-
return;
|
252
|
-
}
|
253
|
-
// Then parse/validate
|
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
|
-
// }
|
261
|
-
const url = new URL(
|
262
|
-
`/v1/patches/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`,
|
263
|
-
this.options.valContentUrl
|
264
|
-
);
|
265
|
-
// Proxy patch to val.build
|
266
|
-
const fetchRes = await fetch(url, {
|
267
|
-
method: "POST",
|
268
|
-
headers: this.getAuthHeaders(token, "application/json"),
|
269
|
-
body: JSON.stringify(patch),
|
270
|
-
});
|
271
|
-
if (fetchRes.ok) {
|
272
|
-
res.status(fetchRes.status).json(await fetchRes.json());
|
273
|
-
} else {
|
274
|
-
res.sendStatus(fetchRes.status);
|
275
|
-
}
|
276
|
-
}).catch((e) => {
|
277
|
-
res.status(500).send({ error: { message: e?.message, status: 500 } });
|
278
|
-
});
|
279
|
-
}
|
280
|
-
|
281
|
-
async commit(req: express.Request, res: express.Response): Promise<void> {
|
282
|
-
this.withAuth(req, res, async ({ token }) => {
|
283
|
-
const url = new URL(
|
284
|
-
`/api/val/commit/${encodeURIComponent(this.options.gitBranch)}`,
|
285
|
-
this.options.valBuildUrl
|
286
|
-
);
|
287
|
-
const fetchRes = await fetch(url, {
|
288
|
-
method: "POST",
|
289
|
-
headers: this.getAuthHeaders(token),
|
290
|
-
});
|
291
|
-
if (fetchRes.ok) {
|
292
|
-
res.status(fetchRes.status).json(await fetchRes.json());
|
293
|
-
} else {
|
294
|
-
res.sendStatus(fetchRes.status);
|
295
|
-
}
|
296
|
-
});
|
297
|
-
}
|
298
|
-
|
299
|
-
private getAuthHeaders(
|
300
|
-
token: string,
|
301
|
-
type?: "application/json" | "application/json-patch+json"
|
302
|
-
):
|
303
|
-
| { Authorization: string }
|
304
|
-
| { "Content-Type": string; Authorization: string } {
|
305
|
-
if (!type) {
|
306
|
-
return {
|
307
|
-
Authorization: `Bearer ${token}`,
|
308
|
-
};
|
309
|
-
}
|
310
|
-
return {
|
311
|
-
"Content-Type": type,
|
312
|
-
Authorization: `Bearer ${token}`,
|
313
|
-
};
|
314
|
-
}
|
315
|
-
|
316
|
-
private async consumeCode(code: string): Promise<{
|
317
|
-
sub: string;
|
318
|
-
exp: number;
|
319
|
-
org: string;
|
320
|
-
project: string;
|
321
|
-
token: string;
|
322
|
-
} | null> {
|
323
|
-
const url = new URL(
|
324
|
-
`/api/val/${this.options.valName}/auth/token`,
|
325
|
-
this.options.valBuildUrl
|
326
|
-
);
|
327
|
-
url.searchParams.set("code", encodeURIComponent(code));
|
328
|
-
return fetch(url, {
|
329
|
-
method: "POST",
|
330
|
-
headers: this.getAuthHeaders(this.options.apiKey, "application/json"), // NOTE: we use apiKey as auth on this endpoint (we do not have a token yet)
|
331
|
-
})
|
332
|
-
.then(async (res) => {
|
333
|
-
if (res.status === 200) {
|
334
|
-
const token = await res.text();
|
335
|
-
const verification = ValAppJwtPayload.safeParse(decodeJwt(token));
|
336
|
-
if (!verification.success) {
|
337
|
-
return null;
|
338
|
-
}
|
339
|
-
return {
|
340
|
-
...verification.data,
|
341
|
-
token,
|
342
|
-
};
|
343
|
-
} else {
|
344
|
-
console.debug("Failed to get data from code: ", res.status);
|
345
|
-
return null;
|
346
|
-
}
|
347
|
-
})
|
348
|
-
.catch((err) => {
|
349
|
-
console.debug("Failed to get user from code: ", err);
|
350
|
-
return null;
|
351
|
-
});
|
352
|
-
}
|
353
|
-
|
354
|
-
private getAuthorizeUrl(publicValApiRoute: string, token: string): string {
|
355
|
-
const url = new URL(
|
356
|
-
`/auth/${this.options.valName}/authorize`,
|
357
|
-
this.options.valBuildUrl
|
358
|
-
);
|
359
|
-
url.searchParams.set(
|
360
|
-
"redirect_uri",
|
361
|
-
encodeURIComponent(`${publicValApiRoute}/callback`)
|
362
|
-
);
|
363
|
-
url.searchParams.set("state", token);
|
364
|
-
return url.toString();
|
365
|
-
}
|
366
|
-
|
367
|
-
private getAppErrorUrl(error: string): string {
|
368
|
-
const url = new URL("/authorize", this.options.valBuildUrl);
|
369
|
-
url.searchParams.set("error", encodeURIComponent(error));
|
370
|
-
return url.toString();
|
371
|
-
}
|
372
|
-
}
|
373
|
-
|
374
|
-
function verifyCallbackReq(
|
375
|
-
stateCookie: string,
|
376
|
-
queryParams: Record<string, unknown>
|
377
|
-
):
|
378
|
-
| {
|
379
|
-
success: { code: string; redirect_uri?: string };
|
380
|
-
error: null;
|
381
|
-
}
|
382
|
-
| { success: false; error: string } {
|
383
|
-
if (typeof stateCookie !== "string") {
|
384
|
-
return { success: false, error: "No state cookie" };
|
385
|
-
}
|
386
|
-
|
387
|
-
const { code, state: tokenFromQuery } = queryParams;
|
388
|
-
|
389
|
-
if (typeof code !== "string") {
|
390
|
-
return { success: false, error: "No code query param" };
|
391
|
-
}
|
392
|
-
if (typeof tokenFromQuery !== "string") {
|
393
|
-
return { success: false, error: "No state query param" };
|
394
|
-
}
|
395
|
-
|
396
|
-
const { success: cookieStateSuccess, error: cookieStateError } =
|
397
|
-
getStateFromCookie(stateCookie);
|
398
|
-
|
399
|
-
if (cookieStateError !== null) {
|
400
|
-
return { success: false, error: cookieStateError };
|
401
|
-
}
|
402
|
-
|
403
|
-
if (cookieStateSuccess.token !== tokenFromQuery) {
|
404
|
-
return { success: false, error: "Invalid state token" };
|
405
|
-
}
|
406
|
-
|
407
|
-
return {
|
408
|
-
success: { code, redirect_uri: cookieStateSuccess.redirect_to },
|
409
|
-
error: null,
|
410
|
-
};
|
411
|
-
}
|
412
|
-
|
413
|
-
type StateCookie = {
|
414
|
-
redirect_to: string;
|
415
|
-
token: string;
|
416
|
-
};
|
417
|
-
|
418
|
-
function getStateFromCookie(stateCookie: string):
|
419
|
-
| {
|
420
|
-
success: StateCookie;
|
421
|
-
error: null;
|
422
|
-
}
|
423
|
-
| { success: false; error: string } {
|
424
|
-
try {
|
425
|
-
const decoded = Buffer.from(stateCookie, "base64").toString("utf8");
|
426
|
-
const parsed = JSON.parse(decoded) as unknown;
|
427
|
-
|
428
|
-
if (!parsed) {
|
429
|
-
return {
|
430
|
-
success: false,
|
431
|
-
error: "Invalid state cookie: could not parse",
|
432
|
-
};
|
433
|
-
}
|
434
|
-
if (typeof parsed !== "object") {
|
435
|
-
return {
|
436
|
-
success: false,
|
437
|
-
error: "Invalid state cookie: parsed object is not an object",
|
438
|
-
};
|
439
|
-
}
|
440
|
-
if ("token" in parsed && "redirect_to" in parsed) {
|
441
|
-
const { token, redirect_to } = parsed;
|
442
|
-
if (typeof token !== "string") {
|
443
|
-
return {
|
444
|
-
success: false,
|
445
|
-
error: "Invalid state cookie: no token in parsed object",
|
446
|
-
};
|
447
|
-
}
|
448
|
-
if (typeof redirect_to !== "string") {
|
449
|
-
return {
|
450
|
-
success: false,
|
451
|
-
error: "Invalid state cookie: no redirect_to in parsed object",
|
452
|
-
};
|
453
|
-
}
|
454
|
-
return {
|
455
|
-
success: {
|
456
|
-
token,
|
457
|
-
redirect_to,
|
458
|
-
},
|
459
|
-
error: null,
|
460
|
-
};
|
461
|
-
} else {
|
462
|
-
return {
|
463
|
-
success: false,
|
464
|
-
error: "Invalid state cookie: no token or redirect_to in parsed object",
|
465
|
-
};
|
466
|
-
}
|
467
|
-
} catch (err) {
|
468
|
-
return {
|
469
|
-
success: false,
|
470
|
-
error: "Invalid state cookie: could not parse",
|
471
|
-
};
|
472
|
-
}
|
473
|
-
}
|
474
|
-
|
475
|
-
export async function enable(
|
476
|
-
req: express.Request,
|
477
|
-
res: express.Response,
|
478
|
-
redirectUrl?: string
|
479
|
-
): Promise<void> {
|
480
|
-
const { redirect_to } = req.query;
|
481
|
-
if (typeof redirect_to === "string" || typeof redirect_to === "undefined") {
|
482
|
-
let redirectUrlToUse = redirect_to || "/";
|
483
|
-
if (redirectUrl) {
|
484
|
-
redirectUrlToUse =
|
485
|
-
redirectUrl + "?redirect_to=" + encodeURIComponent(redirectUrlToUse);
|
486
|
-
}
|
487
|
-
res
|
488
|
-
.cookie(VAL_ENABLED_COOKIE, "true", {
|
489
|
-
httpOnly: false,
|
490
|
-
sameSite: "lax",
|
491
|
-
})
|
492
|
-
.redirect(redirectUrlToUse);
|
493
|
-
} else {
|
494
|
-
res.sendStatus(400);
|
495
|
-
}
|
496
|
-
}
|
497
|
-
|
498
|
-
export async function disable(
|
499
|
-
req: express.Request,
|
500
|
-
res: express.Response,
|
501
|
-
redirectUrl?: string
|
502
|
-
): Promise<void> {
|
503
|
-
const { redirect_to } = req.query;
|
504
|
-
if (typeof redirect_to === "string" || typeof redirect_to === "undefined") {
|
505
|
-
let redirectUrlToUse = redirect_to || "/";
|
506
|
-
if (redirectUrl) {
|
507
|
-
redirectUrlToUse =
|
508
|
-
redirectUrl + "?redirect_to=" + encodeURIComponent(redirectUrlToUse);
|
509
|
-
}
|
510
|
-
res
|
511
|
-
.cookie(VAL_ENABLED_COOKIE, "false", {
|
512
|
-
httpOnly: false,
|
513
|
-
sameSite: "lax",
|
514
|
-
})
|
515
|
-
.redirect(redirectUrlToUse);
|
516
|
-
} else {
|
517
|
-
res.sendStatus(400);
|
518
|
-
}
|
519
|
-
}
|
520
|
-
|
521
|
-
function createStateCookie(state: StateCookie): string {
|
522
|
-
return Buffer.from(JSON.stringify(state), "utf8").toString("base64");
|
523
|
-
}
|
524
|
-
|
525
|
-
const ValAppJwtPayload = z.object({
|
526
|
-
sub: z.string(),
|
527
|
-
exp: z.number(),
|
528
|
-
project: z.string(),
|
529
|
-
org: z.string(),
|
530
|
-
});
|
531
|
-
type ValAppJwtPayload = z.infer<typeof ValAppJwtPayload>;
|
532
|
-
|
533
|
-
const IntegratedServerJwtPayload = z.object({
|
534
|
-
sub: z.string(),
|
535
|
-
exp: z.number(),
|
536
|
-
token: z.string(),
|
537
|
-
org: z.string(),
|
538
|
-
project: z.string(),
|
539
|
-
});
|
540
|
-
export type IntegratedServerJwtPayload = z.infer<
|
541
|
-
typeof IntegratedServerJwtPayload
|
542
|
-
>;
|
@@ -1,36 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
type Source,
|
3
|
-
type SerializedSchema,
|
4
|
-
ValidationErrors,
|
5
|
-
} from "@valbuild/core";
|
6
|
-
import { ModuleId, type SourcePath } from "@valbuild/core/src/val";
|
7
|
-
|
8
|
-
export const FATAL_ERROR_TYPES = [
|
9
|
-
"no-schema",
|
10
|
-
"no-source",
|
11
|
-
"invalid-id",
|
12
|
-
"no-module",
|
13
|
-
] as const;
|
14
|
-
export type FatalErrorType = (typeof FATAL_ERROR_TYPES)[number];
|
15
|
-
|
16
|
-
export type SerializedModuleContent =
|
17
|
-
| {
|
18
|
-
source: Source;
|
19
|
-
schema: SerializedSchema;
|
20
|
-
path: SourcePath;
|
21
|
-
errors: false;
|
22
|
-
}
|
23
|
-
| {
|
24
|
-
source?: Source;
|
25
|
-
schema?: SerializedSchema;
|
26
|
-
path: SourcePath;
|
27
|
-
errors: {
|
28
|
-
invalidModuleId?: ModuleId;
|
29
|
-
validation?: ValidationErrors;
|
30
|
-
fatal?: {
|
31
|
-
message: string;
|
32
|
-
stack?: string;
|
33
|
-
type?: FatalErrorType;
|
34
|
-
}[];
|
35
|
-
};
|
36
|
-
};
|
package/src/Service.ts
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
import { newQuickJSWASMModule, QuickJSRuntime } from "quickjs-emscripten";
|
2
|
-
import { patchValFile } from "./patchValFile";
|
3
|
-
import { readValFile } from "./readValFile";
|
4
|
-
import { Patch } from "@valbuild/core/patch";
|
5
|
-
import { ValModuleLoader } from "./ValModuleLoader";
|
6
|
-
import { newValQuickJSRuntime } from "./ValQuickJSRuntime";
|
7
|
-
import { ValSourceFileHandler } from "./ValSourceFileHandler";
|
8
|
-
import ts from "typescript";
|
9
|
-
import { getCompilerOptions } from "./getCompilerOptions";
|
10
|
-
import { IValFSHost } from "./ValFSHost";
|
11
|
-
import fs from "fs";
|
12
|
-
import { SerializedModuleContent } from "./SerializedModuleContent";
|
13
|
-
import {
|
14
|
-
ModuleId,
|
15
|
-
ModulePath,
|
16
|
-
Internal,
|
17
|
-
SourcePath,
|
18
|
-
Schema,
|
19
|
-
} from "@valbuild/core";
|
20
|
-
|
21
|
-
export type ServiceOptions = {
|
22
|
-
/**
|
23
|
-
* Relative path to the val.config.js file from the root directory.
|
24
|
-
*
|
25
|
-
* @example "./val.config"
|
26
|
-
*/
|
27
|
-
valConfigPath: string;
|
28
|
-
/**
|
29
|
-
* Disable cache for transpilation
|
30
|
-
*
|
31
|
-
* @default false
|
32
|
-
* */
|
33
|
-
disableCache?: boolean;
|
34
|
-
};
|
35
|
-
|
36
|
-
export async function createService(
|
37
|
-
projectRoot: string,
|
38
|
-
opts: ServiceOptions,
|
39
|
-
host: IValFSHost = {
|
40
|
-
...ts.sys,
|
41
|
-
writeFile: fs.writeFileSync,
|
42
|
-
},
|
43
|
-
loader?: ValModuleLoader
|
44
|
-
): Promise<Service> {
|
45
|
-
const compilerOptions = getCompilerOptions(projectRoot, host);
|
46
|
-
const sourceFileHandler = new ValSourceFileHandler(
|
47
|
-
projectRoot,
|
48
|
-
compilerOptions,
|
49
|
-
host
|
50
|
-
);
|
51
|
-
const module = await newQuickJSWASMModule();
|
52
|
-
const runtime = await newValQuickJSRuntime(
|
53
|
-
module,
|
54
|
-
loader ||
|
55
|
-
new ValModuleLoader(projectRoot, compilerOptions, sourceFileHandler, host)
|
56
|
-
);
|
57
|
-
return new Service(opts, sourceFileHandler, runtime);
|
58
|
-
}
|
59
|
-
|
60
|
-
export class Service {
|
61
|
-
readonly valConfigPath: string;
|
62
|
-
|
63
|
-
constructor(
|
64
|
-
{ valConfigPath }: ServiceOptions,
|
65
|
-
private readonly sourceFileHandler: ValSourceFileHandler,
|
66
|
-
private readonly runtime: QuickJSRuntime
|
67
|
-
) {
|
68
|
-
this.valConfigPath = valConfigPath;
|
69
|
-
}
|
70
|
-
|
71
|
-
async get(
|
72
|
-
moduleId: ModuleId,
|
73
|
-
modulePath: ModulePath
|
74
|
-
): Promise<SerializedModuleContent> {
|
75
|
-
const valModule = await readValFile(
|
76
|
-
moduleId,
|
77
|
-
this.valConfigPath,
|
78
|
-
this.runtime
|
79
|
-
);
|
80
|
-
|
81
|
-
if (valModule.source && valModule.schema) {
|
82
|
-
const resolved = Internal.resolvePath(
|
83
|
-
modulePath,
|
84
|
-
valModule.source,
|
85
|
-
valModule.schema
|
86
|
-
);
|
87
|
-
const sourcePath = (
|
88
|
-
resolved.path ? [moduleId, resolved.path].join(".") : moduleId
|
89
|
-
) as SourcePath;
|
90
|
-
return {
|
91
|
-
path: sourcePath,
|
92
|
-
schema:
|
93
|
-
resolved.schema instanceof Schema
|
94
|
-
? resolved.schema.serialize()
|
95
|
-
: resolved.schema,
|
96
|
-
source: resolved.source,
|
97
|
-
errors:
|
98
|
-
valModule.errors && valModule.errors.validation
|
99
|
-
? {
|
100
|
-
validation: valModule.errors.validation || undefined,
|
101
|
-
fatal: valModule.errors.fatal || undefined,
|
102
|
-
}
|
103
|
-
: false,
|
104
|
-
};
|
105
|
-
} else {
|
106
|
-
return valModule;
|
107
|
-
}
|
108
|
-
}
|
109
|
-
|
110
|
-
async patch(
|
111
|
-
moduleId: string,
|
112
|
-
patch: Patch
|
113
|
-
): Promise<SerializedModuleContent> {
|
114
|
-
return patchValFile(
|
115
|
-
moduleId,
|
116
|
-
this.valConfigPath,
|
117
|
-
patch,
|
118
|
-
this.sourceFileHandler,
|
119
|
-
this.runtime
|
120
|
-
);
|
121
|
-
}
|
122
|
-
|
123
|
-
dispose() {
|
124
|
-
this.runtime.dispose();
|
125
|
-
}
|
126
|
-
}
|