@valbuild/server 0.63.2 → 0.64.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.
@@ -11,8 +11,8 @@ var fsPath = require('path');
11
11
  var fs = require('fs');
12
12
  var sucrase = require('sucrase');
13
13
  var ui = require('@valbuild/ui');
14
- var server = require('@valbuild/ui/server');
15
14
  var internal = require('@valbuild/shared/internal');
15
+ var server = require('@valbuild/ui/server');
16
16
  var crypto$1 = require('crypto');
17
17
  var z = require('zod');
18
18
  var sizeOf = require('image-size');
@@ -1279,10 +1279,10 @@ async function extractImageMetadata(filename, input) {
1279
1279
  let mimeType = null;
1280
1280
  if (imageSize.type) {
1281
1281
  const possibleMimeType = `image/${imageSize.type}`;
1282
- if (internal.MIME_TYPES_TO_EXT[possibleMimeType]) {
1282
+ if (core.Internal.MIME_TYPES_TO_EXT[possibleMimeType]) {
1283
1283
  mimeType = possibleMimeType;
1284
1284
  }
1285
- const filenameBasedLookup = internal.filenameToMimeType(filename);
1285
+ const filenameBasedLookup = core.Internal.filenameToMimeType(filename);
1286
1286
  if (filenameBasedLookup) {
1287
1287
  mimeType = filenameBasedLookup;
1288
1288
  }
@@ -1312,7 +1312,7 @@ function getSha256(mimeType, input) {
1312
1312
  `data:${mimeType};base64,${input.toString("base64")}`));
1313
1313
  }
1314
1314
  async function extractFileMetadata(filename, input) {
1315
- let mimeType = internal.filenameToMimeType(filename);
1315
+ let mimeType = core.Internal.filenameToMimeType(filename);
1316
1316
  if (!mimeType) {
1317
1317
  mimeType = "application/octet-stream";
1318
1318
  }
@@ -1520,10 +1520,14 @@ class ValOps {
1520
1520
  if (!errors[path]) {
1521
1521
  errors[path] = [];
1522
1522
  }
1523
- errors[path].push({
1523
+ errors[path].push(...patches.map(({
1524
+ patchId
1525
+ }) => ({
1526
+ patchId,
1524
1527
  invalidPath: true,
1528
+ skipped: true,
1525
1529
  error: new patch.PatchError(`Module at path: '${path}' not found`)
1526
- });
1530
+ })));
1527
1531
  }
1528
1532
  patchedSources[path] = sources[path];
1529
1533
  for (const {
@@ -1532,15 +1536,17 @@ class ValOps {
1532
1536
  if (errors[path]) {
1533
1537
  errors[path].push({
1534
1538
  patchId: patchId,
1539
+ skipped: true,
1535
1540
  error: new patch.PatchError(`Cannot apply patch: previous errors exists`)
1536
1541
  });
1537
1542
  } else {
1538
1543
  const patchData = analysis.patches[patchId];
1539
1544
  if (!patchData) {
1540
- errors[path].push({
1545
+ errors[path] = [{
1541
1546
  patchId: patchId,
1547
+ skipped: false,
1542
1548
  error: new patch.PatchError(`Patch not found`)
1543
- });
1549
+ }];
1544
1550
  continue;
1545
1551
  }
1546
1552
  const applicableOps = [];
@@ -1569,6 +1575,7 @@ class ValOps {
1569
1575
  }
1570
1576
  errors[path].push({
1571
1577
  patchId: patchId,
1578
+ skipped: false,
1572
1579
  error: patchRes.error
1573
1580
  });
1574
1581
  } else {
@@ -1837,7 +1844,7 @@ class ValOps {
1837
1844
  let sourceFileText = unescape(tsSourceFile.getText(tsSourceFile).replace(/\\u/g, "%u"));
1838
1845
  if ((_this$options = this.options) !== null && _this$options !== void 0 && _this$options.formatter) {
1839
1846
  try {
1840
- sourceFileText = this.options.formatter(sourceFileText, path);
1847
+ sourceFileText = await this.options.formatter(sourceFileText, path);
1841
1848
  } catch (err) {
1842
1849
  errors.push({
1843
1850
  message: "Could not format source file: " + (err instanceof Error ? err.message : "Unknown error")
@@ -2267,7 +2274,7 @@ class ValOpsFS extends ValOps {
2267
2274
  throw new Error("Could not parse patch id from file name. Files found: " + patchJsonFiles.join(", "));
2268
2275
  }
2269
2276
  const patchId = patchIdNum.toString();
2270
- if (includes && !includes.includes(patchId)) {
2277
+ if (includes && includes.length > 0 && !includes.includes(patchId)) {
2271
2278
  continue;
2272
2279
  }
2273
2280
  const parsedFSPatchRes = this.parseJsonFile(this.getPatchFilePath(patchId), FSPatch);
@@ -2296,22 +2303,23 @@ class ValOpsFS extends ValOps {
2296
2303
  patches
2297
2304
  };
2298
2305
  }
2299
- async getPatchOpsById(patchIds) {
2300
- return this.readPatches(patchIds);
2301
- }
2302
- async findPatches(filters) {
2306
+ async fetchPatches(filters) {
2303
2307
  const patches = {};
2304
2308
  const errors = {};
2305
2309
  const {
2306
2310
  errors: allErrors,
2307
2311
  patches: allPatches
2308
- } = await this.readPatches();
2312
+ } = await this.readPatches(filters.patchIds);
2309
2313
  for (const [patchIdS, patch] of Object.entries(allPatches)) {
2310
2314
  const patchId = patchIdS;
2311
2315
  if (filters.authors && !(patch.authorId === null || filters.authors.includes(patch.authorId))) {
2312
2316
  continue;
2313
2317
  }
2318
+ if (filters.moduleFilePaths && !filters.moduleFilePaths.includes(patch.path)) {
2319
+ continue;
2320
+ }
2314
2321
  patches[patchId] = {
2322
+ patch: filters.omitPatch ? undefined : patch.patch,
2315
2323
  path: patch.path,
2316
2324
  createdAt: patch.createdAt,
2317
2325
  authorId: patch.authorId,
@@ -2767,16 +2775,13 @@ const BasePatchResponse = z.z.object({
2767
2775
  });
2768
2776
  const GetPatches = z.z.object({
2769
2777
  patches: z.z.array(z.z.intersection(z.z.object({
2770
- patch: Patch
2778
+ patch: Patch.optional()
2771
2779
  }), BasePatchResponse)),
2772
2780
  errors: z.z.array(z.z.object({
2773
2781
  patchId: PatchId.optional(),
2774
2782
  message: z.z.string()
2775
2783
  })).optional()
2776
2784
  });
2777
- const SearchPatches = z.z.object({
2778
- patches: z.z.array(BasePatchResponse)
2779
- });
2780
2785
  const FilesResponse = z.z.object({
2781
2786
  files: z.z.array(z.z.union([z.z.object({
2782
2787
  filePath: z.z.string(),
@@ -2835,13 +2840,29 @@ class ValOpsHttp extends ValOps {
2835
2840
  async onInit() {
2836
2841
  // TODO: unused for now. Implement or remove
2837
2842
  }
2838
- async getPatchOpsById(patchIds) {
2839
- const params = new URLSearchParams();
2840
- params.set("branch", this.branch);
2841
- if (patchIds.length > 0) {
2842
- params.set("patch_ids", encodeURIComponent(patchIds.join(",")));
2843
+ async fetchPatches(filters) {
2844
+ const params = [];
2845
+ params.push(["branch", this.branch]);
2846
+ if (filters.patchIds) {
2847
+ for (const patchId of filters.patchIds) {
2848
+ params.push(["patch_id", patchId]);
2849
+ }
2850
+ }
2851
+ if (filters.authors) {
2852
+ for (const author of filters.authors) {
2853
+ params.push(["author_id", author]);
2854
+ }
2855
+ }
2856
+ if (filters.omitPatch) {
2857
+ params.push(["omit_patch", "true"]);
2843
2858
  }
2844
- return fetch(`${this.hostUrl}/v1/${this.project}/patches${params.size > 0 ? "?" + params : ""}`, {
2859
+ if (filters.moduleFilePaths) {
2860
+ for (const moduleFilePath of filters.moduleFilePaths) {
2861
+ params.push(["module_file_path", moduleFilePath]);
2862
+ }
2863
+ }
2864
+ const searchParams = new URLSearchParams(params);
2865
+ return fetch(`${this.hostUrl}/v1/${this.project}/patches${searchParams.size > 0 ? `?${searchParams.toString()}` : ""}`, {
2845
2866
  headers: {
2846
2867
  ...this.authHeaders,
2847
2868
  "Content-Type": "application/json"
@@ -2889,55 +2910,6 @@ class ValOpsHttp extends ValOps {
2889
2910
  };
2890
2911
  });
2891
2912
  }
2892
- async findPatches(filters) {
2893
- const params = new URLSearchParams();
2894
- params.set("branch", this.branch);
2895
- if (filters.authors && filters.authors.length > 0) {
2896
- params.set("author_ids", encodeURIComponent(filters.authors.join(",")));
2897
- }
2898
- return fetch(`${this.hostUrl}/v1/${this.project}/search/patches${params.size > 0 ? "?" + params : ""}`, {
2899
- headers: {
2900
- ...this.authHeaders,
2901
- "Content-Type": "application/json"
2902
- }
2903
- }).then(async res => {
2904
- const patches = {};
2905
- if (res.ok) {
2906
- const parsed = SearchPatches.safeParse(await res.json());
2907
- if (parsed.success) {
2908
- for (const patchesRes of parsed.data.patches) {
2909
- patches[patchesRes.patchId] = {
2910
- path: patchesRes.path,
2911
- authorId: patchesRes.authorId,
2912
- createdAt: patchesRes.createdAt,
2913
- appliedAt: patchesRes.applied && {
2914
- baseSha: patchesRes.applied.baseSha,
2915
- timestamp: patchesRes.applied.appliedAt,
2916
- git: {
2917
- commitSha: patchesRes.applied.commitSha
2918
- }
2919
- }
2920
- };
2921
- }
2922
- return {
2923
- patches
2924
- };
2925
- }
2926
- return {
2927
- patches,
2928
- error: {
2929
- message: `Could not parse search patches response. Error: ${zodValidationError.fromError(parsed.error)}`
2930
- }
2931
- };
2932
- }
2933
- return {
2934
- patches,
2935
- error: {
2936
- message: "Could not find patches. HTTP error: " + res.status + " " + res.statusText
2937
- }
2938
- };
2939
- });
2940
- }
2941
2913
  async saveSourceFilePatch(path, patch, authorId) {
2942
2914
  return fetch(`${this.hostUrl}/v1/${this.project}/patches`, {
2943
2915
  method: "POST",
@@ -3276,251 +3248,59 @@ class ValOpsHttp extends ValOps {
3276
3248
  }
3277
3249
 
3278
3250
  /* eslint-disable @typescript-eslint/no-unused-vars */
3279
- class ValServer {
3280
- constructor(valModules, options, callbacks) {
3281
- this.valModules = valModules;
3282
- this.options = options;
3283
- this.callbacks = callbacks;
3284
- if (options.mode === "fs") {
3285
- this.serverOps = new ValOpsFS(options.cwd, valModules, {
3286
- formatter: options.formatter
3287
- });
3288
- } else if (options.mode === "http") {
3289
- this.serverOps = new ValOpsHttp(options.valContentUrl, options.project, options.commit, options.branch, options.apiKey, valModules, {
3290
- formatter: options.formatter,
3291
- root: options.root
3292
- });
3293
- } else {
3294
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3295
- throw new Error("Invalid mode: " + (options === null || options === void 0 ? void 0 : options.mode));
3296
- }
3297
- }
3298
-
3299
- //#region auth
3300
- async enable(query) {
3301
- const redirectToRes = getRedirectUrl(query, this.options.valEnableRedirectUrl);
3302
- if (typeof redirectToRes !== "string") {
3303
- return redirectToRes;
3304
- }
3305
- await this.callbacks.onEnable(true);
3306
- return {
3307
- cookies: {
3308
- [internal.VAL_ENABLE_COOKIE_NAME]: ENABLE_COOKIE_VALUE
3309
- },
3310
- status: 302,
3311
- redirectTo: redirectToRes
3312
- };
3313
- }
3314
- async disable(query) {
3315
- const redirectToRes = getRedirectUrl(query, this.options.valDisableRedirectUrl);
3316
- if (typeof redirectToRes !== "string") {
3317
- return redirectToRes;
3318
- }
3319
- await this.callbacks.onDisable(true);
3320
- return {
3321
- cookies: {
3322
- [internal.VAL_ENABLE_COOKIE_NAME]: {
3323
- value: "false"
3324
- }
3325
- },
3326
- status: 302,
3327
- redirectTo: redirectToRes
3328
- };
3329
- }
3330
- async authorize(query) {
3331
- if (typeof query.redirect_to !== "string") {
3332
- return {
3333
- status: 400,
3334
- json: {
3335
- message: "Missing redirect_to query param"
3336
- }
3337
- };
3338
- }
3339
- const token = crypto.randomUUID();
3340
- const redirectUrl = new URL(query.redirect_to);
3341
- const appAuthorizeUrl = this.getAuthorizeUrl(`${redirectUrl.origin}/${this.options.route}`, token);
3342
- await this.callbacks.onEnable(true);
3343
- return {
3344
- cookies: {
3345
- [internal.VAL_ENABLE_COOKIE_NAME]: ENABLE_COOKIE_VALUE,
3346
- [internal.VAL_STATE_COOKIE]: {
3347
- value: createStateCookie({
3348
- redirect_to: query.redirect_to,
3349
- token
3350
- }),
3351
- options: {
3352
- httpOnly: true,
3353
- sameSite: "lax",
3354
- expires: new Date(Date.now() + 1000 * 60 * 60) // 1 hour
3355
- }
3356
- }
3357
- },
3358
- status: 302,
3359
- redirectTo: appAuthorizeUrl
3360
- };
3251
+ const ValServer = (valModules, options, callbacks) => {
3252
+ let serverOps;
3253
+ if (options.mode === "fs") {
3254
+ serverOps = new ValOpsFS(options.cwd, valModules, {
3255
+ formatter: options.formatter
3256
+ });
3257
+ } else if (options.mode === "http") {
3258
+ serverOps = new ValOpsHttp(options.valContentUrl, options.project, options.commit, options.branch, options.apiKey, valModules, {
3259
+ formatter: options.formatter,
3260
+ root: options.root
3261
+ });
3262
+ } else {
3263
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3264
+ throw new Error("Invalid mode: " + (options === null || options === void 0 ? void 0 : options.mode));
3361
3265
  }
3362
- async callback(query, cookies) {
3363
- if (!this.options.project) {
3364
- return {
3365
- status: 302,
3366
- cookies: {
3367
- [internal.VAL_STATE_COOKIE]: {
3368
- value: null
3369
- }
3370
- },
3371
- redirectTo: this.getAppErrorUrl("Project is not set")
3372
- };
3373
- }
3374
- if (!this.options.valSecret) {
3375
- return {
3376
- status: 302,
3377
- cookies: {
3378
- [internal.VAL_STATE_COOKIE]: {
3379
- value: null
3380
- }
3381
- },
3382
- redirectTo: this.getAppErrorUrl("Secret is not set")
3383
- };
3384
- }
3385
- const {
3386
- success: callbackReqSuccess,
3387
- error: callbackReqError
3388
- } = verifyCallbackReq(cookies[internal.VAL_STATE_COOKIE], query);
3389
- if (callbackReqError !== null) {
3390
- return {
3391
- status: 302,
3392
- cookies: {
3393
- [internal.VAL_STATE_COOKIE]: {
3394
- value: null
3395
- }
3396
- },
3397
- redirectTo: this.getAppErrorUrl(`Authorization callback failed. Details: ${callbackReqError}`)
3398
- };
3399
- }
3400
- const data = await this.consumeCode(callbackReqSuccess.code);
3401
- if (data === null) {
3402
- return {
3403
- status: 302,
3404
- cookies: {
3405
- [internal.VAL_STATE_COOKIE]: {
3406
- value: null
3407
- }
3408
- },
3409
- redirectTo: this.getAppErrorUrl("Failed to exchange code for user")
3410
- };
3411
- }
3412
- const exp = getExpire();
3413
- const valSecret = this.options.valSecret;
3414
- if (!valSecret) {
3415
- return {
3416
- status: 302,
3417
- cookies: {
3418
- [internal.VAL_STATE_COOKIE]: {
3419
- value: null
3420
- }
3421
- },
3422
- redirectTo: this.getAppErrorUrl("Setup is not correct: secret is missing")
3423
- };
3266
+ const getAuthorizeUrl = (publicValApiRe, token) => {
3267
+ if (!options.project) {
3268
+ throw new Error("Project is not set");
3424
3269
  }
3425
- const cookie = encodeJwt({
3426
- ...data,
3427
- exp // this is the client side exp
3428
- }, valSecret);
3429
- return {
3430
- status: 302,
3431
- cookies: {
3432
- [internal.VAL_STATE_COOKIE]: {
3433
- value: null
3434
- },
3435
- [internal.VAL_ENABLE_COOKIE_NAME]: ENABLE_COOKIE_VALUE,
3436
- [internal.VAL_SESSION_COOKIE]: {
3437
- value: cookie,
3438
- options: {
3439
- httpOnly: true,
3440
- sameSite: "strict",
3441
- path: "/",
3442
- secure: true,
3443
- expires: new Date(exp * 1000) // NOTE: this is not used for authorization, only for authentication
3444
- }
3445
- }
3446
- },
3447
- redirectTo: callbackReqSuccess.redirect_uri || "/"
3448
- };
3449
- }
3450
- async session(cookies) {
3451
- if (this.serverOps instanceof ValOpsFS) {
3452
- return {
3453
- status: 200,
3454
- json: {
3455
- mode: "local",
3456
- enabled: await this.callbacks.isEnabled()
3457
- }
3458
- };
3270
+ if (!options.valBuildUrl) {
3271
+ throw new Error("Val build url is not set");
3459
3272
  }
3460
- if (!this.options.project) {
3461
- return {
3462
- status: 500,
3463
- json: {
3464
- message: "Project is not set"
3465
- }
3466
- };
3273
+ const url = new URL(`/auth/${options.project}/authorize`, options.valBuildUrl);
3274
+ url.searchParams.set("redirect_uri", encodeURIComponent(`${publicValApiRe}/callback`));
3275
+ url.searchParams.set("state", token);
3276
+ return url.toString();
3277
+ };
3278
+ const getAppErrorUrl = error => {
3279
+ if (!options.project) {
3280
+ throw new Error("Project is not set");
3467
3281
  }
3468
- if (!this.options.valSecret) {
3469
- return {
3470
- status: 500,
3471
- json: {
3472
- message: "Secret is not set"
3473
- }
3474
- };
3282
+ if (!options.valBuildUrl) {
3283
+ throw new Error("Val build url is not set");
3475
3284
  }
3476
- return withAuth(this.options.valSecret, cookies, "session", async data => {
3477
- if (!this.options.valBuildUrl) {
3478
- return {
3479
- status: 500,
3480
- json: {
3481
- message: "Val is not correctly setup. Build url is missing"
3482
- }
3483
- };
3484
- }
3485
- const url = new URL(`/api/val/${this.options.project}/auth/session`, this.options.valBuildUrl);
3486
- const fetchRes = await fetch(url, {
3487
- headers: getAuthHeaders(data.token, "application/json")
3488
- });
3489
- if (fetchRes.status === 200) {
3490
- return {
3491
- status: fetchRes.status,
3492
- json: {
3493
- mode: "proxy",
3494
- enabled: await this.callbacks.isEnabled(),
3495
- ...(await fetchRes.json())
3496
- }
3497
- };
3498
- } else {
3499
- return {
3500
- status: fetchRes.status,
3501
- json: {
3502
- message: "Failed to authorize",
3503
- ...(await fetchRes.json())
3504
- }
3505
- };
3506
- }
3507
- });
3508
- }
3509
- async consumeCode(code) {
3510
- if (!this.options.project) {
3285
+ const url = new URL(`/auth/${options.project}/authorize`, options.valBuildUrl);
3286
+ url.searchParams.set("error", encodeURIComponent(error));
3287
+ return url.toString();
3288
+ };
3289
+ const consumeCode = async code => {
3290
+ if (!options.project) {
3511
3291
  throw new Error("Project is not set");
3512
3292
  }
3513
- if (!this.options.valBuildUrl) {
3293
+ if (!options.valBuildUrl) {
3514
3294
  throw new Error("Val build url is not set");
3515
3295
  }
3516
- const url = new URL(`/api/val/${this.options.project}/auth/token`, this.options.valBuildUrl);
3296
+ const url = new URL(`/api/val/${options.project}/auth/token`, options.valBuildUrl);
3517
3297
  url.searchParams.set("code", encodeURIComponent(code));
3518
- if (!this.options.apiKey) {
3298
+ if (!options.apiKey) {
3519
3299
  return null;
3520
3300
  }
3521
3301
  return fetch(url, {
3522
3302
  method: "POST",
3523
- headers: getAuthHeaders(this.options.apiKey, "application/json") // NOTE: we use apiKey as auth on this endpoint (we do not have a token yet)
3303
+ headers: getAuthHeaders(options.apiKey, "application/json") // NOTE: we use apiKey as auth on this endpoint (we do not have a token yet)
3524
3304
  }).then(async res => {
3525
3305
  if (res.status === 200) {
3526
3306
  const token = await res.text();
@@ -3540,34 +3320,11 @@ class ValServer {
3540
3320
  console.debug("Failed to get user from code: ", err);
3541
3321
  return null;
3542
3322
  });
3543
- }
3544
- getAuthorizeUrl(publicValApiRe, token) {
3545
- if (!this.options.project) {
3546
- throw new Error("Project is not set");
3547
- }
3548
- if (!this.options.valBuildUrl) {
3549
- throw new Error("Val build url is not set");
3550
- }
3551
- const url = new URL(`/auth/${this.options.project}/authorize`, this.options.valBuildUrl);
3552
- url.searchParams.set("redirect_uri", encodeURIComponent(`${publicValApiRe}/callback`));
3553
- url.searchParams.set("state", token);
3554
- return url.toString();
3555
- }
3556
- getAppErrorUrl(error) {
3557
- if (!this.options.project) {
3558
- throw new Error("Project is not set");
3559
- }
3560
- if (!this.options.valBuildUrl) {
3561
- throw new Error("Val build url is not set");
3562
- }
3563
- const url = new URL(`/auth/${this.options.project}/authorize`, this.options.valBuildUrl);
3564
- url.searchParams.set("error", encodeURIComponent(error));
3565
- return url.toString();
3566
- }
3567
- getAuth(cookies) {
3323
+ };
3324
+ const getAuth = cookies => {
3568
3325
  const cookie = cookies[internal.VAL_SESSION_COOKIE];
3569
- if (!this.options.valSecret) {
3570
- if (this.serverOps instanceof ValOpsFS) {
3326
+ if (!options.valSecret) {
3327
+ if (serverOps instanceof ValOpsFS) {
3571
3328
  return {
3572
3329
  error: null,
3573
3330
  id: null
@@ -3579,9 +3336,9 @@ class ValServer {
3579
3336
  }
3580
3337
  }
3581
3338
  if (typeof cookie === "string") {
3582
- const decodedToken = decodeJwt(cookie, this.options.valSecret);
3339
+ const decodedToken = decodeJwt(cookie, options.valSecret);
3583
3340
  if (!decodedToken) {
3584
- if (this.serverOps instanceof ValOpsFS) {
3341
+ if (serverOps instanceof ValOpsFS) {
3585
3342
  return {
3586
3343
  error: null,
3587
3344
  id: null
@@ -3593,7 +3350,7 @@ class ValServer {
3593
3350
  }
3594
3351
  const verification = IntegratedServerJwtPayload.safeParse(decodedToken);
3595
3352
  if (!verification.success) {
3596
- if (this.serverOps instanceof ValOpsFS) {
3353
+ if (serverOps instanceof ValOpsFS) {
3597
3354
  return {
3598
3355
  error: null,
3599
3356
  id: null
@@ -3607,7 +3364,7 @@ class ValServer {
3607
3364
  id: verification.data.sub
3608
3365
  };
3609
3366
  } else {
3610
- if (this.serverOps instanceof ValOpsFS) {
3367
+ if (serverOps instanceof ValOpsFS) {
3611
3368
  return {
3612
3369
  error: null,
3613
3370
  id: null
@@ -3617,420 +3374,687 @@ class ValServer {
3617
3374
  error: "Login required: cookie not found"
3618
3375
  };
3619
3376
  }
3620
- }
3621
- async logout() {
3622
- return {
3623
- status: 200,
3624
- cookies: {
3625
- [internal.VAL_SESSION_COOKIE]: {
3626
- value: null
3627
- },
3628
- [internal.VAL_STATE_COOKIE]: {
3629
- value: null
3377
+ };
3378
+ return {
3379
+ //#region auth
3380
+ "/enable": {
3381
+ GET: async req => {
3382
+ const query = req.query;
3383
+ const redirectToRes = getRedirectUrl(query, options.valEnableRedirectUrl);
3384
+ if (typeof redirectToRes !== "string") {
3385
+ return redirectToRes;
3630
3386
  }
3387
+ await callbacks.onEnable(true);
3388
+ return {
3389
+ cookies: {
3390
+ [internal.VAL_ENABLE_COOKIE_NAME]: ENABLE_COOKIE_VALUE
3391
+ },
3392
+ status: 302,
3393
+ redirectTo: redirectToRes
3394
+ };
3631
3395
  }
3632
- };
3633
- }
3634
-
3635
- //#region patches
3636
- async getPatches(query, cookies) {
3637
- const auth = this.getAuth(cookies);
3638
- if (auth.error) {
3639
- return {
3640
- status: 401,
3641
- json: {
3642
- message: auth.error
3396
+ },
3397
+ "/disable": {
3398
+ GET: async req => {
3399
+ const query = req.query;
3400
+ const redirectToRes = getRedirectUrl(query, options.valDisableRedirectUrl);
3401
+ if (typeof redirectToRes !== "string") {
3402
+ return redirectToRes;
3643
3403
  }
3644
- };
3645
- }
3646
- if (this.serverOps instanceof ValOpsHttp && !("id" in auth)) {
3647
- return {
3648
- status: 401,
3649
- json: {
3650
- message: "Unauthorized"
3404
+ await callbacks.onDisable(true);
3405
+ return {
3406
+ cookies: {
3407
+ [internal.VAL_ENABLE_COOKIE_NAME]: {
3408
+ value: "false"
3409
+ }
3410
+ },
3411
+ status: 302,
3412
+ redirectTo: redirectToRes
3413
+ };
3414
+ }
3415
+ },
3416
+ "/authorize": {
3417
+ GET: async req => {
3418
+ const query = req.query;
3419
+ if (typeof query.redirect_to !== "string") {
3420
+ return {
3421
+ status: 400,
3422
+ json: {
3423
+ message: "Missing redirect_to query param"
3424
+ }
3425
+ };
3651
3426
  }
3652
- };
3653
- }
3654
- const authors = query.authors;
3655
- const patches = await this.serverOps.findPatches({
3656
- authors
3657
- });
3658
- if (patches.errors && Object.keys(patches.errors).length > 0) {
3659
- console.error("Val: Failed to get patches", patches.errors);
3660
- return {
3661
- status: 500,
3662
- json: {
3663
- message: "Failed to get patches",
3664
- details: patches.errors
3427
+ const token = crypto.randomUUID();
3428
+ const redirectUrl = new URL(query.redirect_to);
3429
+ const appAuthorizeUrl = getAuthorizeUrl(`${redirectUrl.origin}/${options.route}`, token);
3430
+ await callbacks.onEnable(true);
3431
+ return {
3432
+ cookies: {
3433
+ [internal.VAL_ENABLE_COOKIE_NAME]: ENABLE_COOKIE_VALUE,
3434
+ [internal.VAL_STATE_COOKIE]: {
3435
+ value: createStateCookie({
3436
+ redirect_to: query.redirect_to,
3437
+ token
3438
+ }),
3439
+ options: {
3440
+ httpOnly: true,
3441
+ sameSite: "lax",
3442
+ expires: new Date(Date.now() + 1000 * 60 * 60) // 1 hour
3443
+ }
3444
+ }
3445
+ },
3446
+ status: 302,
3447
+ redirectTo: appAuthorizeUrl
3448
+ };
3449
+ }
3450
+ },
3451
+ "/callback": {
3452
+ GET: async req => {
3453
+ const cookies = req.cookies;
3454
+ const query = req.query;
3455
+ if (!options.project) {
3456
+ return {
3457
+ status: 302,
3458
+ cookies: {
3459
+ [internal.VAL_STATE_COOKIE]: {
3460
+ value: null
3461
+ }
3462
+ },
3463
+ redirectTo: getAppErrorUrl("Project is not set")
3464
+ };
3665
3465
  }
3666
- };
3667
- }
3668
- const res = {};
3669
- for (const [patchIdS, patchData] of Object.entries(patches.patches)) {
3670
- var _patchData$appliedAt;
3671
- const patchId = patchIdS;
3672
- if (!res[patchData.path]) {
3673
- res[patchData.path] = [];
3674
- }
3675
- res[patchData.path].push({
3676
- patch_id: patchId,
3677
- created_at: patchData.createdAt,
3678
- applied_at_base_sha: ((_patchData$appliedAt = patchData.appliedAt) === null || _patchData$appliedAt === void 0 ? void 0 : _patchData$appliedAt.baseSha) || null,
3679
- author: patchData.authorId ?? undefined
3680
- });
3681
- }
3682
- return {
3683
- status: 200,
3684
- json: res
3685
- };
3686
- }
3687
- async deletePatches(query, cookies) {
3688
- const auth = this.getAuth(cookies);
3689
- if (auth.error) {
3690
- return {
3691
- status: 401,
3692
- json: {
3693
- message: auth.error
3466
+ if (!options.valSecret) {
3467
+ return {
3468
+ status: 302,
3469
+ cookies: {
3470
+ [internal.VAL_STATE_COOKIE]: {
3471
+ value: null
3472
+ }
3473
+ },
3474
+ redirectTo: getAppErrorUrl("Secret is not set")
3475
+ };
3694
3476
  }
3695
- };
3696
- }
3697
- if (this.serverOps instanceof ValOpsHttp && !("id" in auth)) {
3698
- return {
3699
- status: 401,
3700
- json: {
3701
- message: "Unauthorized"
3477
+ const {
3478
+ success: callbackReqSuccess,
3479
+ error: callbackReqError
3480
+ } = verifyCallbackReq(cookies[internal.VAL_STATE_COOKIE], query);
3481
+ if (callbackReqError !== null) {
3482
+ return {
3483
+ status: 302,
3484
+ cookies: {
3485
+ [internal.VAL_STATE_COOKIE]: {
3486
+ value: null
3487
+ }
3488
+ },
3489
+ redirectTo: getAppErrorUrl(`Authorization callback failed. Details: ${callbackReqError}`)
3490
+ };
3702
3491
  }
3703
- };
3704
- }
3705
- const ids = query.id;
3706
- const deleteRes = await this.serverOps.deletePatches(ids);
3707
- if (deleteRes.errors && Object.keys(deleteRes.errors).length > 0) {
3708
- console.error("Val: Failed to delete patches", deleteRes.errors);
3709
- return {
3710
- status: 500,
3711
- json: {
3712
- message: "Failed to delete patches",
3713
- details: deleteRes.errors
3492
+ const data = await consumeCode(callbackReqSuccess.code);
3493
+ if (data === null) {
3494
+ return {
3495
+ status: 302,
3496
+ cookies: {
3497
+ [internal.VAL_STATE_COOKIE]: {
3498
+ value: null
3499
+ }
3500
+ },
3501
+ redirectTo: getAppErrorUrl("Failed to exchange code for user")
3502
+ };
3714
3503
  }
3715
- };
3716
- }
3717
- return {
3718
- status: 200,
3719
- json: ids
3720
- };
3721
- }
3722
-
3723
- //#region tree ops
3724
- async getSchema(cookies) {
3725
- const auth = this.getAuth(cookies);
3726
- if (auth.error) {
3727
- return {
3728
- status: 401,
3729
- json: {
3730
- message: auth.error
3504
+ const exp = getExpire();
3505
+ const valSecret = options.valSecret;
3506
+ if (!valSecret) {
3507
+ return {
3508
+ status: 302,
3509
+ cookies: {
3510
+ [internal.VAL_STATE_COOKIE]: {
3511
+ value: null
3512
+ }
3513
+ },
3514
+ redirectTo: getAppErrorUrl("Setup is not correct: secret is missing")
3515
+ };
3731
3516
  }
3732
- };
3733
- }
3734
- if (this.serverOps instanceof ValOpsHttp && !("id" in auth)) {
3735
- return {
3736
- status: 401,
3737
- json: {
3738
- message: "Unauthorized"
3517
+ const cookie = encodeJwt({
3518
+ ...data,
3519
+ exp // this is the client side exp
3520
+ }, valSecret);
3521
+ return {
3522
+ status: 302,
3523
+ cookies: {
3524
+ [internal.VAL_STATE_COOKIE]: {
3525
+ value: null
3526
+ },
3527
+ [internal.VAL_ENABLE_COOKIE_NAME]: ENABLE_COOKIE_VALUE,
3528
+ [internal.VAL_SESSION_COOKIE]: {
3529
+ value: cookie,
3530
+ options: {
3531
+ httpOnly: true,
3532
+ sameSite: "strict",
3533
+ path: "/",
3534
+ secure: true,
3535
+ expires: new Date(exp * 1000) // NOTE: this is not used for authorization, only for authentication
3536
+ }
3537
+ }
3538
+ },
3539
+ redirectTo: callbackReqSuccess.redirect_uri || "/"
3540
+ };
3541
+ }
3542
+ },
3543
+ "/session": {
3544
+ GET: async req => {
3545
+ const cookies = req.cookies;
3546
+ if (serverOps instanceof ValOpsFS) {
3547
+ return {
3548
+ status: 200,
3549
+ json: {
3550
+ mode: "local",
3551
+ enabled: await callbacks.isEnabled()
3552
+ }
3553
+ };
3739
3554
  }
3740
- };
3741
- }
3742
- const moduleErrors = await this.serverOps.getModuleErrors();
3743
- if ((moduleErrors === null || moduleErrors === void 0 ? void 0 : moduleErrors.length) > 0) {
3744
- console.error("Val: Module errors", moduleErrors);
3745
- return {
3746
- status: 500,
3747
- json: {
3748
- message: "Val is not correctly setup. Check the val.modules file",
3749
- details: moduleErrors
3555
+ if (!options.project) {
3556
+ return {
3557
+ status: 500,
3558
+ json: {
3559
+ message: "Project is not set"
3560
+ }
3561
+ };
3750
3562
  }
3751
- };
3752
- }
3753
- const schemaSha = await this.serverOps.getSchemaSha();
3754
- const schemas = await this.serverOps.getSchemas();
3755
- const serializedSchemas = {};
3756
- for (const [moduleFilePathS, schema] of Object.entries(schemas)) {
3757
- const moduleFilePath = moduleFilePathS;
3758
- serializedSchemas[moduleFilePath] = schema.serialize();
3759
- }
3760
- return {
3761
- status: 200,
3762
- json: {
3763
- schemaSha,
3764
- schemas: serializedSchemas
3765
- }
3766
- };
3767
- }
3768
- async putTree(body, treePath, query, cookies) {
3769
- var _bodyRes$data, _bodyRes$data2, _bodyRes$data3;
3770
- const auth = this.getAuth(cookies);
3771
- if (auth.error) {
3772
- return {
3773
- status: 401,
3774
- json: {
3775
- message: auth.error
3563
+ if (!options.valSecret) {
3564
+ return {
3565
+ status: 500,
3566
+ json: {
3567
+ message: "Secret is not set"
3568
+ }
3569
+ };
3776
3570
  }
3777
- };
3778
- }
3779
- if (this.serverOps instanceof ValOpsHttp && !("id" in auth)) {
3780
- return {
3781
- status: 401,
3782
- json: {
3783
- message: "Unauthorized"
3571
+ return withAuth(options.valSecret, cookies, "session", async data => {
3572
+ if (!options.valBuildUrl) {
3573
+ return {
3574
+ status: 500,
3575
+ json: {
3576
+ message: "Val is not correctly setup. Build url is missing"
3577
+ }
3578
+ };
3579
+ }
3580
+ const url = new URL(`/api/val/${options.project}/auth/session`, options.valBuildUrl);
3581
+ const fetchRes = await fetch(url, {
3582
+ headers: getAuthHeaders(data.token, "application/json")
3583
+ });
3584
+ if (fetchRes.status === 200) {
3585
+ return {
3586
+ status: fetchRes.status,
3587
+ json: {
3588
+ mode: "proxy",
3589
+ enabled: await callbacks.isEnabled(),
3590
+ ...(await fetchRes.json())
3591
+ }
3592
+ };
3593
+ } else {
3594
+ return {
3595
+ status: fetchRes.status,
3596
+ json: {
3597
+ message: "Failed to authorize",
3598
+ ...(await fetchRes.json())
3599
+ }
3600
+ };
3601
+ }
3602
+ });
3603
+ }
3604
+ },
3605
+ "/logout": {
3606
+ GET: async () => {
3607
+ return {
3608
+ status: 200,
3609
+ cookies: {
3610
+ [internal.VAL_SESSION_COOKIE]: {
3611
+ value: null
3612
+ },
3613
+ [internal.VAL_STATE_COOKIE]: {
3614
+ value: null
3615
+ }
3616
+ }
3617
+ };
3618
+ }
3619
+ },
3620
+ //#region patches
3621
+ "/patches/~": {
3622
+ GET: async req => {
3623
+ const query = req.query;
3624
+ const cookies = req.cookies;
3625
+ const auth = getAuth(cookies);
3626
+ if (auth.error) {
3627
+ return {
3628
+ status: 401,
3629
+ json: {
3630
+ message: auth.error
3631
+ }
3632
+ };
3784
3633
  }
3785
- };
3786
- }
3787
- // TODO: move
3788
- const PutTreeBody = z.z.object({
3789
- patchIds: z.z.array(z.z.string().refine(id => true // TODO:
3790
- )).optional(),
3791
- addPatch: z.z.object({
3792
- path: z.z.string().refine(path => true // TODO:
3793
- ),
3794
- patch: Patch
3795
- }).optional()
3796
- }).optional();
3797
- const moduleErrors = await this.serverOps.getModuleErrors();
3798
- if ((moduleErrors === null || moduleErrors === void 0 ? void 0 : moduleErrors.length) > 0) {
3799
- console.error("Val: Module errors", moduleErrors);
3800
- return {
3801
- status: 500,
3802
- json: {
3803
- message: "Val is not correctly setup. Check the val.modules file",
3804
- details: moduleErrors
3634
+ if (serverOps instanceof ValOpsHttp && !("id" in auth)) {
3635
+ return {
3636
+ status: 401,
3637
+ json: {
3638
+ message: "Unauthorized"
3639
+ }
3640
+ };
3805
3641
  }
3806
- };
3807
- }
3808
- const bodyRes = PutTreeBody.safeParse(body);
3809
- if (!bodyRes.success) {
3810
- return {
3811
- status: 400,
3812
- json: {
3813
- message: "Invalid body: " + zodValidationError.fromError(bodyRes.error).toString(),
3814
- details: bodyRes.error.errors
3642
+ const authors = query.author;
3643
+ const patches = await serverOps.fetchPatches({
3644
+ authors,
3645
+ patchIds: query.patch_id,
3646
+ omitPatch: query.omit_patch === true,
3647
+ moduleFilePaths: query.module_file_path
3648
+ });
3649
+ if (patches.error) {
3650
+ // Error is singular
3651
+ console.error("Val: Failed to get patches", patches.errors);
3652
+ return {
3653
+ status: 500,
3654
+ json: {
3655
+ message: patches.error.message,
3656
+ details: patches.error
3657
+ }
3658
+ };
3815
3659
  }
3816
- };
3817
- }
3818
- let tree;
3819
- let patchAnalysis = null;
3820
- if ((_bodyRes$data = bodyRes.data) !== null && _bodyRes$data !== void 0 && _bodyRes$data.patchIds && ((_bodyRes$data2 = bodyRes.data) === null || _bodyRes$data2 === void 0 || (_bodyRes$data2 = _bodyRes$data2.patchIds) === null || _bodyRes$data2 === void 0 ? void 0 : _bodyRes$data2.length) > 0 || (_bodyRes$data3 = bodyRes.data) !== null && _bodyRes$data3 !== void 0 && _bodyRes$data3.addPatch) {
3821
- var _bodyRes$data4, _bodyRes$data5;
3822
- // TODO: validate patches_sha
3823
- const patchIds = (_bodyRes$data4 = bodyRes.data) === null || _bodyRes$data4 === void 0 ? void 0 : _bodyRes$data4.patchIds;
3824
- const patchOps = patchIds && patchIds.length > 0 ? await this.serverOps.getPatchOpsById(patchIds) : {
3825
- patches: {}
3826
- };
3827
- let patchErrors = undefined;
3828
- for (const [patchIdS, error] of Object.entries(patchOps.errors || {})) {
3829
- const patchId = patchIdS;
3830
- if (!patchErrors) {
3831
- patchErrors = {};
3660
+ if (patches.errors && Object.keys(patches.errors).length > 0) {
3661
+ // Errors is plural. Different property than above.
3662
+ console.error("Val: Failed to get patches", patches.errors);
3663
+ return {
3664
+ status: 500,
3665
+ json: {
3666
+ message: "Failed to get patches",
3667
+ details: patches.errors
3668
+ }
3669
+ };
3832
3670
  }
3833
- patchErrors[patchId] = {
3834
- message: error.message
3671
+ return {
3672
+ status: 200,
3673
+ json: patches
3835
3674
  };
3836
- }
3837
- if ((_bodyRes$data5 = bodyRes.data) !== null && _bodyRes$data5 !== void 0 && _bodyRes$data5.addPatch) {
3838
- const newPatchModuleFilePath = bodyRes.data.addPatch.path;
3839
- const newPatchOps = bodyRes.data.addPatch.patch;
3840
- const authorId = "id" in auth ? auth.id : null;
3841
- const createPatchRes = await this.serverOps.createPatch(newPatchModuleFilePath, newPatchOps, authorId);
3842
- if (createPatchRes.error) {
3675
+ },
3676
+ DELETE: async req => {
3677
+ const query = req.query;
3678
+ const cookies = req.cookies;
3679
+ const auth = getAuth(cookies);
3680
+ if (auth.error) {
3681
+ return {
3682
+ status: 401,
3683
+ json: {
3684
+ message: auth.error
3685
+ }
3686
+ };
3687
+ }
3688
+ if (serverOps instanceof ValOpsHttp && !("id" in auth)) {
3689
+ return {
3690
+ status: 401,
3691
+ json: {
3692
+ message: "Unauthorized"
3693
+ }
3694
+ };
3695
+ }
3696
+ const ids = query.id;
3697
+ const deleteRes = await serverOps.deletePatches(ids);
3698
+ if (deleteRes.errors && Object.keys(deleteRes.errors).length > 0) {
3699
+ console.error("Val: Failed to delete patches", deleteRes.errors);
3843
3700
  return {
3844
3701
  status: 500,
3845
3702
  json: {
3846
- message: "Failed to create patch: " + createPatchRes.error.message,
3847
- details: createPatchRes.error
3703
+ message: "Failed to delete patches",
3704
+ details: deleteRes.errors
3848
3705
  }
3849
3706
  };
3850
3707
  }
3851
- // TODO: evaluate if we need this: seems wrong to delete patches that are not applied
3852
- // for (const fileRes of createPatchRes.files) {
3853
- // if (fileRes.error) {
3854
- // // clean up broken patch:
3855
- // await this.serverOps.deletePatches([createPatchRes.patchId]);
3856
- // return {
3857
- // status: 500,
3858
- // json: {
3859
- // message: "Failed to create patch",
3860
- // details: fileRes.error,
3861
- // },
3862
- // };
3863
- // }
3864
- // }
3865
- patchOps.patches[createPatchRes.patchId] = {
3866
- path: newPatchModuleFilePath,
3867
- patch: newPatchOps,
3868
- authorId,
3869
- createdAt: createPatchRes.createdAt,
3870
- appliedAt: null
3708
+ return {
3709
+ status: 200,
3710
+ json: ids
3871
3711
  };
3872
3712
  }
3873
- // TODO: errors
3874
- patchAnalysis = this.serverOps.analyzePatches(patchOps.patches);
3875
- tree = {
3876
- ...(await this.serverOps.getTree({
3877
- ...patchAnalysis,
3878
- ...patchOps
3879
- }))
3880
- };
3881
- if (query.validate_all === "true") {
3882
- const allTree = await this.serverOps.getTree();
3883
- tree = {
3884
- sources: {
3885
- ...allTree.sources,
3886
- ...tree.sources
3887
- },
3888
- errors: {
3889
- ...allTree.errors,
3890
- ...tree.errors
3713
+ },
3714
+ //#region tree ops
3715
+ "/schema": {
3716
+ GET: async req => {
3717
+ const cookies = req.cookies;
3718
+ const auth = getAuth(cookies);
3719
+ if (auth.error) {
3720
+ return {
3721
+ status: 401,
3722
+ json: {
3723
+ message: auth.error
3724
+ }
3725
+ };
3726
+ }
3727
+ if (serverOps instanceof ValOpsHttp && !("id" in auth)) {
3728
+ return {
3729
+ status: 401,
3730
+ json: {
3731
+ message: "Unauthorized"
3732
+ }
3733
+ };
3734
+ }
3735
+ const moduleErrors = await serverOps.getModuleErrors();
3736
+ if ((moduleErrors === null || moduleErrors === void 0 ? void 0 : moduleErrors.length) > 0) {
3737
+ console.error("Val: Module errors", moduleErrors);
3738
+ return {
3739
+ status: 500,
3740
+ json: {
3741
+ message: "Val is not correctly setup. Check the val.modules file",
3742
+ details: moduleErrors
3743
+ }
3744
+ };
3745
+ }
3746
+ const schemaSha = await serverOps.getSchemaSha();
3747
+ const schemas = await serverOps.getSchemas();
3748
+ const serializedSchemas = {};
3749
+ for (const [moduleFilePathS, schema] of Object.entries(schemas)) {
3750
+ const moduleFilePath = moduleFilePathS;
3751
+ serializedSchemas[moduleFilePath] = schema.serialize();
3752
+ }
3753
+ return {
3754
+ status: 200,
3755
+ json: {
3756
+ schemaSha,
3757
+ schemas: serializedSchemas
3891
3758
  }
3892
3759
  };
3893
3760
  }
3894
- } else {
3895
- tree = await this.serverOps.getTree();
3896
- }
3897
- if (tree.errors && Object.keys(tree.errors).length > 0) {
3898
- console.error("Val: Failed to get tree", JSON.stringify(tree.errors));
3899
- }
3900
- if (query.validate_sources === "true" || query.validate_binary_files === "true") {
3901
- const schemas = await this.serverOps.getSchemas();
3902
- const sourcesValidation = await this.serverOps.validateSources(schemas, tree.sources);
3761
+ },
3762
+ "/tree/~": {
3763
+ PUT: async req => {
3764
+ var _body$patchIds;
3765
+ const query = req.query;
3766
+ const cookies = req.cookies;
3767
+ const body = req.body;
3768
+ const treePath = req.path || "";
3769
+ const auth = getAuth(cookies);
3770
+ if (auth.error) {
3771
+ return {
3772
+ status: 401,
3773
+ json: {
3774
+ message: auth.error
3775
+ }
3776
+ };
3777
+ }
3778
+ if (serverOps instanceof ValOpsHttp && !("id" in auth)) {
3779
+ return {
3780
+ status: 401,
3781
+ json: {
3782
+ message: "Unauthorized"
3783
+ }
3784
+ };
3785
+ }
3786
+ const moduleErrors = await serverOps.getModuleErrors();
3787
+ if ((moduleErrors === null || moduleErrors === void 0 ? void 0 : moduleErrors.length) > 0) {
3788
+ console.error("Val: Module errors", moduleErrors);
3789
+ return {
3790
+ status: 500,
3791
+ json: {
3792
+ message: "Val is not correctly setup. Check the val.modules file",
3793
+ details: moduleErrors
3794
+ }
3795
+ };
3796
+ }
3797
+ let tree;
3798
+ let patchAnalysis = null;
3799
+ let newPatchId = undefined;
3800
+ if (body !== null && body !== void 0 && body.patchIds && (body === null || body === void 0 || (_body$patchIds = body.patchIds) === null || _body$patchIds === void 0 ? void 0 : _body$patchIds.length) > 0 || body !== null && body !== void 0 && body.addPatch) {
3801
+ // TODO: validate patches_sha
3802
+ const patchIds = body === null || body === void 0 ? void 0 : body.patchIds;
3803
+ const patchOps = patchIds && patchIds.length > 0 ? await serverOps.fetchPatches({
3804
+ patchIds,
3805
+ omitPatch: false
3806
+ }) : {
3807
+ patches: {}
3808
+ };
3809
+ let patchErrors = undefined;
3810
+ for (const [patchIdS, error] of Object.entries(patchOps.errors || {})) {
3811
+ const patchId = patchIdS;
3812
+ if (!patchErrors) {
3813
+ patchErrors = {};
3814
+ }
3815
+ patchErrors[patchId] = {
3816
+ message: error.message
3817
+ };
3818
+ }
3819
+ if (body !== null && body !== void 0 && body.addPatch) {
3820
+ const newPatchModuleFilePath = body.addPatch.path;
3821
+ const newPatchOps = body.addPatch.patch;
3822
+ const authorId = "id" in auth ? auth.id : null;
3823
+ const createPatchRes = await serverOps.createPatch(newPatchModuleFilePath, newPatchOps, authorId);
3824
+ if (createPatchRes.error) {
3825
+ return {
3826
+ status: 500,
3827
+ json: {
3828
+ message: "Failed to create patch: " + createPatchRes.error.message,
3829
+ details: createPatchRes.error
3830
+ }
3831
+ };
3832
+ }
3833
+ // TODO: evaluate if we need this: seems wrong to delete patches that are not applied
3834
+ // for (const fileRes of createPatchRes.files) {
3835
+ // if (fileRes.error) {
3836
+ // // clean up broken patch:
3837
+ // await this.serverOps.deletePatches([createPatchRes.patchId]);
3838
+ // return {
3839
+ // status: 500,
3840
+ // json: {
3841
+ // message: "Failed to create patch",
3842
+ // details: fileRes.error,
3843
+ // },
3844
+ // };
3845
+ // }
3846
+ // }
3847
+ newPatchId = createPatchRes.patchId;
3848
+ patchOps.patches[createPatchRes.patchId] = {
3849
+ path: newPatchModuleFilePath,
3850
+ patch: newPatchOps,
3851
+ authorId,
3852
+ createdAt: createPatchRes.createdAt,
3853
+ appliedAt: null
3854
+ };
3855
+ }
3856
+ // TODO: errors
3857
+ patchAnalysis = serverOps.analyzePatches(patchOps.patches);
3858
+ tree = {
3859
+ ...(await serverOps.getTree({
3860
+ ...patchAnalysis,
3861
+ ...patchOps
3862
+ }))
3863
+ };
3864
+ if (query.validate_all) {
3865
+ const allTree = await serverOps.getTree();
3866
+ tree = {
3867
+ sources: {
3868
+ ...allTree.sources,
3869
+ ...tree.sources
3870
+ },
3871
+ errors: {
3872
+ ...allTree.errors,
3873
+ ...tree.errors
3874
+ }
3875
+ };
3876
+ }
3877
+ } else {
3878
+ tree = await serverOps.getTree();
3879
+ }
3880
+ if (tree.errors && Object.keys(tree.errors).length > 0) {
3881
+ console.error("Val: Failed to get tree", JSON.stringify(tree.errors));
3882
+ const res = {
3883
+ status: 400,
3884
+ json: {
3885
+ type: "patch-error",
3886
+ errors: Object.fromEntries(Object.entries(tree.errors).map(([key, value]) => [key, value.map(error => ({
3887
+ patchId: error.patchId,
3888
+ skipped: error.skipped,
3889
+ error: {
3890
+ message: error.error.message
3891
+ }
3892
+ }))])),
3893
+ message: "One or more patches failed to be applied"
3894
+ }
3895
+ };
3896
+ return res;
3897
+ }
3898
+ if (query.validate_sources || query.validate_binary_files) {
3899
+ const schemas = await serverOps.getSchemas();
3900
+ const sourcesValidation = await serverOps.validateSources(schemas, tree.sources);
3903
3901
 
3904
- // TODO: send validation errors
3905
- if (query.validate_binary_files === "true") {
3906
- await this.serverOps.validateFiles(schemas, tree.sources, sourcesValidation.files);
3907
- }
3908
- }
3909
- const schemaSha = await this.serverOps.getSchemaSha();
3910
- const modules = {};
3911
- for (const [moduleFilePathS, module] of Object.entries(tree.sources)) {
3912
- const moduleFilePath = moduleFilePathS;
3913
- if (moduleFilePath.startsWith(treePath)) {
3914
- modules[moduleFilePath] = {
3915
- source: module,
3916
- patches: patchAnalysis ? {
3917
- applied: patchAnalysis.patchesByModule[moduleFilePath].map(p => p.patchId)
3918
- } : undefined
3902
+ // TODO: send validation errors
3903
+ if (query.validate_binary_files) {
3904
+ await serverOps.validateFiles(schemas, tree.sources, sourcesValidation.files);
3905
+ }
3906
+ }
3907
+ const schemaSha = await serverOps.getSchemaSha();
3908
+ const modules = {};
3909
+ for (const [moduleFilePathS, module] of Object.entries(tree.sources)) {
3910
+ const moduleFilePath = moduleFilePathS;
3911
+ if (moduleFilePath.startsWith(treePath)) {
3912
+ modules[moduleFilePath] = {
3913
+ source: module,
3914
+ patches: patchAnalysis && patchAnalysis.patchesByModule[moduleFilePath] ? {
3915
+ applied: patchAnalysis.patchesByModule[moduleFilePath].map(p => p.patchId)
3916
+ } : undefined
3917
+ };
3918
+ }
3919
+ }
3920
+ const res = {
3921
+ status: 200,
3922
+ json: {
3923
+ schemaSha,
3924
+ modules,
3925
+ newPatchId
3926
+ }
3919
3927
  };
3928
+ return res;
3920
3929
  }
3921
- }
3922
- return {
3923
- status: 200,
3924
- json: {
3925
- schemaSha,
3926
- modules
3927
- }
3928
- };
3929
- }
3930
- async postSave(body, cookies) {
3931
- const auth = this.getAuth(cookies);
3932
- if (auth.error) {
3933
- return {
3934
- status: 401,
3935
- json: {
3936
- message: auth.error
3930
+ },
3931
+ "/save": {
3932
+ POST: async req => {
3933
+ const cookies = req.cookies;
3934
+ const body = req.body;
3935
+ const auth = getAuth(cookies);
3936
+ if (auth.error) {
3937
+ return {
3938
+ status: 401,
3939
+ json: {
3940
+ message: auth.error
3941
+ }
3942
+ };
3937
3943
  }
3938
- };
3939
- }
3940
- const PostSaveBody = z.z.object({
3941
- patchIds: z.z.array(z.z.string().refine(id => true // TODO:
3942
- ))
3943
- });
3944
- const bodyRes = PostSaveBody.safeParse(body);
3945
- if (!bodyRes.success) {
3946
- return {
3947
- status: 400,
3948
- json: {
3949
- message: "Invalid body: " + zodValidationError.fromError(bodyRes.error).toString(),
3950
- details: bodyRes.error.errors
3944
+ const PostSaveBody = z.z.object({
3945
+ patchIds: z.z.array(z.z.string().refine(id => true // TODO:
3946
+ ))
3947
+ });
3948
+ const bodyRes = PostSaveBody.safeParse(body);
3949
+ if (!bodyRes.success) {
3950
+ return {
3951
+ status: 400,
3952
+ json: {
3953
+ message: "Invalid body: " + zodValidationError.fromError(bodyRes.error).toString(),
3954
+ details: bodyRes.error.errors
3955
+ }
3956
+ };
3951
3957
  }
3952
- };
3953
- }
3954
- const {
3955
- patchIds
3956
- } = bodyRes.data;
3957
- const patches = await this.serverOps.getPatchOpsById(patchIds);
3958
- const analysis = this.serverOps.analyzePatches(patches.patches);
3959
- const preparedCommit = await this.serverOps.prepare({
3960
- ...analysis,
3961
- ...patches
3962
- });
3963
- if (preparedCommit.hasErrors) {
3964
- console.error("Failed to create commit", {
3965
- sourceFilePatchErrors: preparedCommit.sourceFilePatchErrors,
3966
- binaryFilePatchErrors: preparedCommit.binaryFilePatchErrors
3967
- });
3968
- return {
3969
- status: 400,
3970
- json: {
3971
- message: "Failed to create commit",
3972
- details: {
3958
+ const {
3959
+ patchIds
3960
+ } = bodyRes.data;
3961
+ const patches = await serverOps.fetchPatches({
3962
+ patchIds,
3963
+ omitPatch: false
3964
+ });
3965
+ const analysis = serverOps.analyzePatches(patches.patches);
3966
+ const preparedCommit = await serverOps.prepare({
3967
+ ...analysis,
3968
+ ...patches
3969
+ });
3970
+ if (preparedCommit.hasErrors) {
3971
+ console.error("Failed to create commit", {
3973
3972
  sourceFilePatchErrors: preparedCommit.sourceFilePatchErrors,
3974
3973
  binaryFilePatchErrors: preparedCommit.binaryFilePatchErrors
3974
+ });
3975
+ return {
3976
+ status: 400,
3977
+ json: {
3978
+ message: "Failed to create commit",
3979
+ details: {
3980
+ sourceFilePatchErrors: Object.fromEntries(Object.entries(preparedCommit.sourceFilePatchErrors).map(([key, errors]) => [key, errors.map(e => ({
3981
+ message: formatPatchSourceError(e)
3982
+ }))])),
3983
+ binaryFilePatchErrors: preparedCommit.binaryFilePatchErrors
3984
+ }
3985
+ }
3986
+ };
3987
+ }
3988
+ if (serverOps instanceof ValOpsFS) {
3989
+ await serverOps.saveFiles(preparedCommit);
3990
+ return {
3991
+ status: 200,
3992
+ json: {} // TODO:
3993
+ };
3994
+ } else if (serverOps instanceof ValOpsHttp) {
3995
+ if (auth.error === undefined && auth.id) {
3996
+ await serverOps.commit(preparedCommit, "Update content: " + Object.keys(analysis.patchesByModule) + " modules changed", auth.id);
3997
+ return {
3998
+ status: 200,
3999
+ json: {} // TODO:
4000
+ };
3975
4001
  }
4002
+ return {
4003
+ status: 401,
4004
+ json: {
4005
+ message: "Unauthorized"
4006
+ }
4007
+ };
4008
+ } else {
4009
+ throw new Error("Invalid server ops");
3976
4010
  }
3977
- };
3978
- }
3979
- if (this.serverOps instanceof ValOpsFS) {
3980
- await this.serverOps.saveFiles(preparedCommit);
3981
- return {
3982
- status: 200,
3983
- json: {} // TODO:
3984
- };
3985
- } else if (this.serverOps instanceof ValOpsHttp) {
3986
- if (auth.error === undefined && auth.id) {
3987
- await this.serverOps.commit(preparedCommit, "Update content: " + Object.keys(analysis.patchesByModule) + " modules changed", auth.id);
3988
- return {
3989
- status: 200,
3990
- json: {} // TODO:
3991
- };
3992
4011
  }
3993
- return {
3994
- status: 401,
3995
- json: {
3996
- message: "Unauthorized"
4012
+ },
4013
+ //#region files
4014
+ "/files": {
4015
+ GET: async req => {
4016
+ const query = req.query;
4017
+ const filePath = req.path;
4018
+ // NOTE: no auth here since you would need the patch_id to get something that is not published.
4019
+ // For everything that is published, well they are already public so no auth required there...
4020
+ // We could imagine adding auth just to be a 200% certain,
4021
+ // However that won't work since images are requested by the nextjs backend as a part of image optimization (again: as an example) which is a backend-to-backend op (no cookies, ...).
4022
+ // So: 1) patch ids are not possible to guess (but possible to brute force)
4023
+ // 2) the process of shimming a patch into the frontend would be quite challenging (so just trying out this attack would require a lot of effort)
4024
+ // 3) the benefit an attacker would get is an image that is not yet published (i.e. most cases: not very interesting)
4025
+ // Thus: attack surface + ease of attack + benefit = low probability of attack
4026
+ // If we couldn't argue that patch ids are secret enough, then this would be a problem.
4027
+ let fileBuffer;
4028
+ if (query.patch_id) {
4029
+ fileBuffer = await serverOps.getBase64EncodedBinaryFileFromPatch(filePath, query.patch_id);
4030
+ } else {
4031
+ fileBuffer = await serverOps.getBinaryFile(filePath);
3997
4032
  }
3998
- };
3999
- } else {
4000
- throw new Error("Invalid server ops");
4001
- }
4002
- }
4003
-
4004
- //#region files
4005
- async getFiles(filePath, query) {
4006
- // NOTE: no auth here since you would need the patch_id to get something that is not published.
4007
- // For everything that is published, well they are already public so no auth required there...
4008
- // We could imagine adding auth just to be a 200% certain,
4009
- // However that won't work since images are requested by the nextjs backend as a part of image optimization (again: as an example) which is a backend-to-backend op (no cookies, ...).
4010
- // So: 1) patch ids are not possible to guess (but possible to brute force)
4011
- // 2) the process of shimming a patch into the frontend would be quite challenging (so just trying out this attack would require a lot of effort)
4012
- // 3) the benefit an attacker would get is an image that is not yet published (i.e. most cases: not very interesting)
4013
- // Thus: attack surface + ease of attack + benefit = low probability of attack
4014
- // If we couldn't argue that patch ids are secret enough, then this would be a problem.
4015
- let fileBuffer;
4016
- if (query.patch_id) {
4017
- fileBuffer = await this.serverOps.getBase64EncodedBinaryFileFromPatch(filePath, query.patch_id);
4018
- } else {
4019
- fileBuffer = await this.serverOps.getBinaryFile(filePath);
4020
- }
4021
- if (fileBuffer) {
4022
- return {
4023
- status: 200,
4024
- body: bufferToReadableStream(fileBuffer)
4025
- };
4026
- } else {
4027
- return {
4028
- status: 404,
4029
- json: {
4030
- message: "File not found"
4033
+ if (fileBuffer) {
4034
+ return {
4035
+ status: 200,
4036
+ body: bufferToReadableStream(fileBuffer)
4037
+ };
4038
+ } else {
4039
+ return {
4040
+ status: 404,
4041
+ json: {
4042
+ message: "File not found"
4043
+ }
4044
+ };
4031
4045
  }
4032
- };
4046
+ }
4033
4047
  }
4048
+ };
4049
+ };
4050
+ function formatPatchSourceError(error) {
4051
+ if ("message" in error) {
4052
+ return error.message;
4053
+ } else if (Array.isArray(error)) {
4054
+ return error.map(formatPatchSourceError).join("\n");
4055
+ } else {
4056
+ const _exhaustiveCheck = error;
4057
+ return "Unknown patch source error: " + JSON.stringify(_exhaustiveCheck);
4034
4058
  }
4035
4059
  }
4036
4060
  function verifyCallbackReq(stateCookie, queryParams) {
@@ -4168,7 +4192,7 @@ async function withAuth(secret, cookies, errorMessageType, handler) {
4168
4192
  status: 401,
4169
4193
  json: {
4170
4194
  message: "Session invalid or, most likely, expired. You will need to login again.",
4171
- details: verification.error
4195
+ details: zodValidationError.fromError(verification.error).toString()
4172
4196
  }
4173
4197
  };
4174
4198
  }
@@ -4330,7 +4354,7 @@ function guessMimeTypeFromPath(filePath) {
4330
4354
 
4331
4355
  async function createValServer(valModules, route, opts, callbacks, formatter) {
4332
4356
  const valServerConfig = await initHandlerOptions(route, opts);
4333
- return new ValServer(valModules, {
4357
+ return ValServer(valModules, {
4334
4358
  formatter,
4335
4359
  ...valServerConfig
4336
4360
  }, callbacks);
@@ -4455,17 +4479,9 @@ async function readCommit(gitDir, branchName) {
4455
4479
  return undefined;
4456
4480
  }
4457
4481
  }
4458
- const {
4459
- VAL_SESSION_COOKIE,
4460
- VAL_STATE_COOKIE
4461
- } = core.Internal;
4462
- const TREE_PATH_PREFIX = "/tree/~";
4463
- const PATCHES_PATH_PREFIX = "/patches/~";
4464
- const FILES_PATH_PREFIX = "/files";
4465
4482
  function createValApiRouter(route, valServerPromise, convert) {
4466
4483
  const uiRequestHandler = server.createUIRequestHandler();
4467
4484
  return async req => {
4468
- var _req$method;
4469
4485
  const valServer = await valServerPromise;
4470
4486
  const url = new URL(req.url);
4471
4487
  if (!url.pathname.startsWith(route)) {
@@ -4479,113 +4495,213 @@ function createValApiRouter(route, valServerPromise, convert) {
4479
4495
  json: error
4480
4496
  });
4481
4497
  }
4482
- const method = (_req$method = req.method) === null || _req$method === void 0 ? void 0 : _req$method.toUpperCase();
4483
- function withTreePath(path, prefix) {
4484
- return async useTreePath => {
4485
- const pathIndex = path.indexOf("~");
4486
- if (path.startsWith(prefix) && pathIndex !== -1) {
4487
- return useTreePath(path.slice(pathIndex + 1));
4488
- } else {
4489
- if (prefix.indexOf("/~") === -1) {
4490
- return convert({
4491
- status: 500,
4492
- json: {
4493
- message: `Route is incorrectly formed: ${prefix}!`
4498
+ const path = url.pathname.slice(route.length);
4499
+ const groupQueryParams = arr => {
4500
+ const map = {};
4501
+ for (const [key, value] of arr) {
4502
+ const list = map[key] || [];
4503
+ list.push(value);
4504
+ map[key] = list;
4505
+ }
4506
+ return map;
4507
+ };
4508
+ async function getValServerResponse(reqApiRoutePath, req) {
4509
+ var _req$method, _anyApi$route, _anyValServer$route;
4510
+ const anyApi =
4511
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4512
+ internal.Api;
4513
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4514
+ const anyValServer = valServer;
4515
+ const method = (_req$method = req.method) === null || _req$method === void 0 ? void 0 : _req$method.toUpperCase();
4516
+ let route = null;
4517
+ let path = undefined;
4518
+ for (const routeDef of Object.keys(internal.Api)) {
4519
+ if (routeDef === reqApiRoutePath) {
4520
+ route = routeDef;
4521
+ break;
4522
+ }
4523
+ if (reqApiRoutePath.startsWith(routeDef)) {
4524
+ var _anyApi$routeDef;
4525
+ const reqDefinition = anyApi === null || anyApi === void 0 || (_anyApi$routeDef = anyApi[routeDef]) === null || _anyApi$routeDef === void 0 || (_anyApi$routeDef = _anyApi$routeDef[method]) === null || _anyApi$routeDef === void 0 ? void 0 : _anyApi$routeDef.req;
4526
+ if (reqDefinition) {
4527
+ route = routeDef;
4528
+ if (reqDefinition.path) {
4529
+ const subPath = reqApiRoutePath.slice(routeDef.length);
4530
+ const pathRes = reqDefinition.path.safeParse(subPath);
4531
+ if (!pathRes.success) {
4532
+ return zodErrorResult(pathRes.error, `invalid path: '${subPath}' endpoint: '${routeDef}'`);
4533
+ } else {
4534
+ path = pathRes.data;
4494
4535
  }
4495
- });
4536
+ }
4537
+ break;
4496
4538
  }
4497
- return convert({
4498
- status: 404,
4539
+ }
4540
+ }
4541
+ if (!route) {
4542
+ return {
4543
+ status: 404,
4544
+ json: {
4545
+ message: "Route not found. Valid routes are: " + Object.keys(internal.Api),
4546
+ details: {
4547
+ route,
4548
+ method
4549
+ }
4550
+ }
4551
+ };
4552
+ }
4553
+ const apiEndpoint = anyApi === null || anyApi === void 0 || (_anyApi$route = anyApi[route]) === null || _anyApi$route === void 0 ? void 0 : _anyApi$route[method];
4554
+ const reqDefinition = apiEndpoint === null || apiEndpoint === void 0 ? void 0 : apiEndpoint.req;
4555
+ if (!reqDefinition) {
4556
+ return {
4557
+ status: 404,
4558
+ json: {
4559
+ message: `Requested method ${method} on route ${route} is not valid. Valid methods are: ${Object.keys(anyApi[route]).join(", ")}`,
4560
+ details: {
4561
+ route,
4562
+ method
4563
+ }
4564
+ }
4565
+ };
4566
+ }
4567
+ const endpointImpl = anyValServer === null || anyValServer === void 0 || (_anyValServer$route = anyValServer[route]) === null || _anyValServer$route === void 0 ? void 0 : _anyValServer$route[method];
4568
+ if (!endpointImpl) {
4569
+ return {
4570
+ status: 500,
4571
+ json: {
4572
+ message: "Missing server implementation of route with method. This might be caused by a mismatch between Val package versions.",
4573
+ details: {
4574
+ valid: {
4575
+ route: {
4576
+ server: Object.keys(anyValServer || {}),
4577
+ api: Object.keys(anyApi || {})
4578
+ },
4579
+ method: {
4580
+ server: Object.keys((anyValServer === null || anyValServer === void 0 ? void 0 : anyValServer[route]) || {}),
4581
+ api: Object.keys((anyApi === null || anyApi === void 0 ? void 0 : anyApi[route]) || {})
4582
+ }
4583
+ },
4584
+ route,
4585
+ method
4586
+ }
4587
+ }
4588
+ };
4589
+ }
4590
+ const bodyRes = reqDefinition.body ? reqDefinition.body.safeParse(await req.json()) : {
4591
+ success: true,
4592
+ data: {}
4593
+ };
4594
+ if (!bodyRes.success) {
4595
+ return zodErrorResult(bodyRes.error, "invalid body data");
4596
+ }
4597
+ const cookiesRes = reqDefinition.cookies ? getCookies(req, reqDefinition.cookies) : {
4598
+ success: true,
4599
+ data: {}
4600
+ };
4601
+ if (!cookiesRes.success) {
4602
+ return zodErrorResult(cookiesRes.error, "invalid cookies");
4603
+ }
4604
+ const actualQueryParams = groupQueryParams(Array.from(url.searchParams.entries()));
4605
+ let query = {};
4606
+ if (reqDefinition.query) {
4607
+ // This is code is particularly heavy, however
4608
+ // @see ValidQueryParamTypes in ApiRouter.ts where we explain what we want to support
4609
+ // We prioritized a declarative ApiRouter, so this code is what we ended up with for better of worse
4610
+ const queryRules = {};
4611
+ for (const [key, zodRule] of Object.entries(reqDefinition.query)) {
4612
+ let innerType = zodRule;
4613
+ let isOptional = false;
4614
+ let isArray = false;
4615
+ // extract inner types:
4616
+ if (innerType instanceof z.z.ZodOptional) {
4617
+ isOptional = true;
4618
+ innerType = innerType.unwrap();
4619
+ }
4620
+ if (innerType instanceof z.z.ZodArray) {
4621
+ isArray = true;
4622
+ innerType = innerType.element;
4623
+ }
4624
+ // convert boolean to union of literals true and false so we can parse it as a string
4625
+ if (innerType instanceof z.z.ZodBoolean) {
4626
+ innerType = z.z.union([z.z.literal("true"), z.z.literal("false")]).transform(arg => Boolean(arg));
4627
+ }
4628
+ // re-build rules:
4629
+ let arrayCompatibleRule = innerType;
4630
+ arrayCompatibleRule = z.z.array(innerType); // we always want to parse an array because we group the query params by into an array
4631
+ if (isOptional) {
4632
+ arrayCompatibleRule = arrayCompatibleRule.optional();
4633
+ }
4634
+ if (!isArray) {
4635
+ arrayCompatibleRule = arrayCompatibleRule.transform(arg => arg && arg[0]);
4636
+ }
4637
+ queryRules[key] = arrayCompatibleRule;
4638
+ }
4639
+ const queryRes = z.z.object(queryRules).safeParse(actualQueryParams);
4640
+ if (!queryRes.success) {
4641
+ return zodErrorResult(queryRes.error, `invalid query params: (${JSON.stringify(actualQueryParams)})`);
4642
+ }
4643
+ query = queryRes.data;
4644
+ }
4645
+ const res = await endpointImpl({
4646
+ body: bodyRes.data,
4647
+ cookies: cookiesRes.data,
4648
+ query,
4649
+ path
4650
+ });
4651
+ const resDef = apiEndpoint.res;
4652
+ if (resDef) {
4653
+ const responseResult = resDef.safeParse(res);
4654
+ if (!responseResult.success) {
4655
+ return {
4656
+ status: 500,
4499
4657
  json: {
4500
- message: `Malformed ${prefix} path! Expected: '${prefix}'`
4658
+ message: "Could not validate response. This is likely a bug in Val server.",
4659
+ details: {
4660
+ response: res,
4661
+ errors: formatZodErrorString(responseResult.error)
4662
+ }
4501
4663
  }
4502
- });
4664
+ };
4503
4665
  }
4504
- };
4666
+ }
4667
+ return res;
4505
4668
  }
4506
- const path = url.pathname.slice(route.length);
4507
4669
  if (path.startsWith("/static")) {
4508
4670
  return convert(await uiRequestHandler(path.slice("/static".length), url.href));
4509
- } else if (path === "/session") {
4510
- return convert(await valServer.session(getCookies(req, [VAL_SESSION_COOKIE])));
4511
- } else if (path === "/authorize") {
4512
- return convert(await valServer.authorize({
4513
- redirect_to: url.searchParams.get("redirect_to") || undefined
4514
- }));
4515
- } else if (path === "/callback") {
4516
- return convert(await valServer.callback({
4517
- code: url.searchParams.get("code") || undefined,
4518
- state: url.searchParams.get("state") || undefined
4519
- }, getCookies(req, [VAL_STATE_COOKIE])));
4520
- } else if (path === "/logout") {
4521
- return convert(await valServer.logout());
4522
- } else if (path === "/enable") {
4523
- return convert(await valServer.enable({
4524
- redirect_to: url.searchParams.get("redirect_to") || undefined
4525
- }));
4526
- } else if (path === "/disable") {
4527
- return convert(await valServer.disable({
4528
- redirect_to: url.searchParams.get("redirect_to") || undefined
4529
- }));
4530
- } else if (method === "POST" && path === "/save") {
4531
- const body = await req.json();
4532
- return convert(await valServer.postSave(body, getCookies(req, [VAL_SESSION_COOKIE])));
4533
- // } else if (method === "POST" && path === "/validate") {
4534
- // const body = (await req.json()) as unknown;
4535
- // return convert(
4536
- // await valServer.postValidate(
4537
- // body,
4538
- // getCookies(req, [VAL_SESSION_COOKIE]),
4539
- // requestHeaders
4540
- // )
4541
- // );
4542
- } else if (method === "GET" && path === "/schema") {
4543
- return convert(await valServer.getSchema(getCookies(req, [VAL_SESSION_COOKIE])));
4544
- } else if (method === "PUT" && path.startsWith(TREE_PATH_PREFIX)) {
4545
- return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.putTree(await req.json(), treePath, {
4546
- patches_sha: url.searchParams.get("patches_sha") || undefined,
4547
- validate_all: url.searchParams.get("validate_all") || undefined,
4548
- validate_binary_files: url.searchParams.get("validate_binary_files") || undefined,
4549
- validate_sources: url.searchParams.get("validate_sources") || undefined
4550
- }, getCookies(req, [VAL_SESSION_COOKIE]))));
4551
- } else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
4552
- return withTreePath(path, PATCHES_PATH_PREFIX)(async () => convert(await valServer.getPatches({
4553
- authors: url.searchParams.getAll("author")
4554
- }, getCookies(req, [VAL_SESSION_COOKIE]))));
4555
- } else if (method === "DELETE" && path.startsWith(PATCHES_PATH_PREFIX)) {
4556
- return withTreePath(path, PATCHES_PATH_PREFIX)(async () => convert(await valServer.deletePatches({
4557
- id: url.searchParams.getAll("id")
4558
- }, getCookies(req, [VAL_SESSION_COOKIE]))));
4559
- } else if (method === "GET" && path.startsWith(FILES_PATH_PREFIX)) {
4560
- const treePath = path.slice(FILES_PATH_PREFIX.length);
4561
- return convert(await valServer.getFiles(treePath, {
4562
- patch_id: url.searchParams.get("patch_id") || undefined
4563
- }));
4564
4671
  } else {
4565
- return convert({
4566
- status: 404,
4567
- json: {
4568
- message: "Not Found",
4569
- details: {
4570
- method,
4571
- path
4572
- }
4573
- }
4574
- });
4672
+ return convert(await getValServerResponse(path, req));
4673
+ }
4674
+ };
4675
+ }
4676
+ function formatZodErrorString(error) {
4677
+ const errors = zodValidationError.fromZodError(error).toString();
4678
+ return errors.length > 640 ? `${errors.slice(0, 640)}...` : errors;
4679
+ }
4680
+ function zodErrorResult(error, message) {
4681
+ return {
4682
+ status: 400,
4683
+ json: {
4684
+ message: "Bad Request: " + message,
4685
+ details: {
4686
+ errors: formatZodErrorString(error)
4687
+ }
4575
4688
  }
4576
4689
  };
4577
4690
  }
4578
4691
 
4579
4692
  // TODO: is this naive implementation is too naive?
4580
- function getCookies(req, names) {
4581
- var _req$headers$get;
4582
- return ((_req$headers$get = req.headers.get("Cookie")) === null || _req$headers$get === void 0 ? void 0 : _req$headers$get.split("; ").reduce((acc, cookie) => {
4583
- const [name, value] = cookie.split("=");
4584
- if (names.includes(name.trim())) {
4585
- acc[name.trim()] = decodeURIComponent(value.trim());
4586
- }
4587
- return acc;
4588
- }, {})) || {};
4693
+ function getCookies(req, cookiesDef) {
4694
+ var _req$headers;
4695
+ const input = {};
4696
+ const cookieParts = (_req$headers = req.headers) === null || _req$headers === void 0 || (_req$headers = _req$headers.get("Cookie")) === null || _req$headers === void 0 ? void 0 : _req$headers.split("; ");
4697
+ for (const name of Object.keys(cookiesDef)) {
4698
+ const cookie = cookieParts === null || cookieParts === void 0 ? void 0 : cookieParts.find(cookie => cookie.startsWith(`${name}=`));
4699
+ const value = cookie ? decodeURIComponent(cookie === null || cookie === void 0 ? void 0 : cookie.split("=")[1]) : undefined;
4700
+ if (value) {
4701
+ input[name.trim()] = value;
4702
+ }
4703
+ }
4704
+ return z.z.object(cookiesDef).safeParse(input);
4589
4705
  }
4590
4706
 
4591
4707
  /**