@valbuild/server 0.93.0 → 0.95.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/dist/declarations/src/ValFS.d.ts +2 -1
- package/dist/declarations/src/ValFSHost.d.ts +4 -1
- package/dist/declarations/src/ValServer.d.ts +1 -1
- package/dist/valbuild-server.cjs.dev.js +701 -83
- package/dist/valbuild-server.cjs.prod.js +701 -83
- package/dist/valbuild-server.esm.js +701 -83
- package/package.json +4 -4
|
@@ -628,7 +628,7 @@ function removeFromNode(document, node, key) {
|
|
|
628
628
|
} else if (ts.isObjectLiteralExpression(node)) {
|
|
629
629
|
return pipe(findObjectPropertyAssignment(node, key), result.flatMap(assignment => {
|
|
630
630
|
if (!assignment) {
|
|
631
|
-
return result.err(new PatchError("Cannot
|
|
631
|
+
return result.err(new PatchError("Cannot remove object element which does not exist"));
|
|
632
632
|
}
|
|
633
633
|
return result.ok(assignment);
|
|
634
634
|
}), result.map(assignment => [removeAt(document, node.properties, node.properties.indexOf(assignment))[0], assignment.initializer]));
|
|
@@ -995,7 +995,14 @@ class ValSourceFileHandler {
|
|
|
995
995
|
});
|
|
996
996
|
fs.writeFileSync(fileName, typeof data === "string" ? data : new Uint8Array(data), encoding);
|
|
997
997
|
},
|
|
998
|
-
rmFile: fs.rmSync
|
|
998
|
+
rmFile: fs.rmSync,
|
|
999
|
+
readBuffer: fileName => {
|
|
1000
|
+
try {
|
|
1001
|
+
return fs.readFileSync(fileName);
|
|
1002
|
+
} catch {
|
|
1003
|
+
return undefined;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
999
1006
|
}) {
|
|
1000
1007
|
this.projectRoot = projectRoot;
|
|
1001
1008
|
this.compilerOptions = compilerOptions;
|
|
@@ -1043,7 +1050,14 @@ class ValModuleLoader {
|
|
|
1043
1050
|
});
|
|
1044
1051
|
fs.writeFileSync(fileName, typeof data === "string" ? data : new Uint8Array(data), encoding);
|
|
1045
1052
|
},
|
|
1046
|
-
rmFile: fs.rmSync
|
|
1053
|
+
rmFile: fs.rmSync,
|
|
1054
|
+
readBuffer: fileName => {
|
|
1055
|
+
try {
|
|
1056
|
+
return fs.readFileSync(fileName);
|
|
1057
|
+
} catch {
|
|
1058
|
+
return undefined;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1047
1061
|
}, disableCache = false) {
|
|
1048
1062
|
this.projectRoot = projectRoot;
|
|
1049
1063
|
this.compilerOptions = compilerOptions;
|
|
@@ -1336,7 +1350,14 @@ async function createService(projectRoot, opts, host = {
|
|
|
1336
1350
|
});
|
|
1337
1351
|
fs.writeFileSync(fileName, typeof data === "string" ? data : new Uint8Array(data), encoding);
|
|
1338
1352
|
},
|
|
1339
|
-
rmFile: fs.rmSync
|
|
1353
|
+
rmFile: fs.rmSync,
|
|
1354
|
+
readBuffer: fileName => {
|
|
1355
|
+
try {
|
|
1356
|
+
return fs.readFileSync(fileName);
|
|
1357
|
+
} catch {
|
|
1358
|
+
return undefined;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1340
1361
|
}, loader) {
|
|
1341
1362
|
const compilerOptions = getCompilerOptions(projectRoot, host);
|
|
1342
1363
|
const sourceFileHandler = new ValSourceFileHandler(projectRoot, compilerOptions, host);
|
|
@@ -1669,8 +1690,10 @@ class ValOps {
|
|
|
1669
1690
|
const filePath = op.filePath;
|
|
1670
1691
|
fileLastUpdatedByPatchId[filePath] = {
|
|
1671
1692
|
patchId: patch.patchId,
|
|
1672
|
-
remote: op.remote
|
|
1693
|
+
remote: op.remote,
|
|
1694
|
+
isDelete: op.value === null
|
|
1673
1695
|
};
|
|
1696
|
+
continue;
|
|
1674
1697
|
}
|
|
1675
1698
|
const path = patch.path;
|
|
1676
1699
|
if (!patchesByModule[path]) {
|
|
@@ -1745,15 +1768,19 @@ class ValOps {
|
|
|
1745
1768
|
const fileFixOps = {};
|
|
1746
1769
|
for (const op of patchData.patch) {
|
|
1747
1770
|
if (op.op === "file") {
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
op
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1771
|
+
if (op.value !== null) {
|
|
1772
|
+
// NOTE: We insert the last patch_id that modify a file
|
|
1773
|
+
// when constructing the url we use the patch id (and the file path)
|
|
1774
|
+
// to fetch the right file
|
|
1775
|
+
// NOTE: overwrite and use last patch_id if multiple patches modify the same file
|
|
1776
|
+
fileFixOps[op.path.join("/")] = [{
|
|
1777
|
+
op: "add",
|
|
1778
|
+
path: op.path.concat(...(op.nestedFilePath || [])).concat("patch_id"),
|
|
1779
|
+
value: patchId
|
|
1780
|
+
}];
|
|
1781
|
+
}
|
|
1782
|
+
// null value = delete: no patch_id to inject; the "remove" op in
|
|
1783
|
+
// the patch already removes the metadata entry from the source
|
|
1757
1784
|
} else {
|
|
1758
1785
|
applicableOps.push(op);
|
|
1759
1786
|
}
|
|
@@ -1866,6 +1893,21 @@ class ValOps {
|
|
|
1866
1893
|
const files = {};
|
|
1867
1894
|
const remoteFiles = {};
|
|
1868
1895
|
const entries = Object.entries(schemas);
|
|
1896
|
+
// Build a map of gallery directory → [ModuleFilePath, ...] across ALL modules
|
|
1897
|
+
// (must include all modules, not just those being validated, since conflicts can come from any module)
|
|
1898
|
+
const galleryDirectoryToModules = new Map();
|
|
1899
|
+
for (const [moduleFilePathS, schema] of entries) {
|
|
1900
|
+
const serialized = schema["executeSerialize"]();
|
|
1901
|
+
if (serialized.type === "record" && serialized.mediaType && serialized.directory) {
|
|
1902
|
+
const dir = serialized.directory;
|
|
1903
|
+
const existing = galleryDirectoryToModules.get(dir);
|
|
1904
|
+
if (existing) {
|
|
1905
|
+
existing.push(moduleFilePathS);
|
|
1906
|
+
} else {
|
|
1907
|
+
galleryDirectoryToModules.set(dir, [moduleFilePathS]);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1869
1911
|
const modulePathsToValidate = patchesByModule && Object.keys(patchesByModule);
|
|
1870
1912
|
for (const [pathS, schema] of entries) {
|
|
1871
1913
|
if (modulePathsToValidate && !modulePathsToValidate.includes(pathS)) {
|
|
@@ -1906,7 +1948,7 @@ class ValOps {
|
|
|
1906
1948
|
};
|
|
1907
1949
|
if (validationErrors) {
|
|
1908
1950
|
for (const validationError of validationErrors) {
|
|
1909
|
-
var _validationError$fixe, _validationError$fixe2, _validationError$fixe3, _validationError$fixe4;
|
|
1951
|
+
var _validationError$fixe, _validationError$fixe2, _validationError$fixe3, _validationError$fixe4, _validationError$fixe5, _validationError$fixe6, _validationError$fixe7, _validationError$fixe8;
|
|
1910
1952
|
if (isOnlyFileCheckValidationError(validationError)) {
|
|
1911
1953
|
if (files[sourcePath]) {
|
|
1912
1954
|
throw new Error("Cannot have multiple files with same path. Path: " + sourcePath + "; Module: " + path);
|
|
@@ -1989,7 +2031,32 @@ class ValOps {
|
|
|
1989
2031
|
}
|
|
1990
2032
|
}
|
|
1991
2033
|
}
|
|
1992
|
-
} else {
|
|
2034
|
+
} else if ((_validationError$fixe5 = validationError.fixes) !== null && _validationError$fixe5 !== void 0 && _validationError$fixe5.includes("images:check-unique-folder") || (_validationError$fixe6 = validationError.fixes) !== null && _validationError$fixe6 !== void 0 && _validationError$fixe6.includes("files:check-unique-folder")) {
|
|
2035
|
+
const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
|
|
2036
|
+
if (!validationError.value || typeof validationError.value !== "object") {
|
|
2037
|
+
addError({
|
|
2038
|
+
message: `Could not find a directory value for gallery at ${sourcePath}. ${TYPE_ERROR_MESSAGE}`,
|
|
2039
|
+
typeError: true
|
|
2040
|
+
});
|
|
2041
|
+
} else {
|
|
2042
|
+
const directory = "directory" in validationError.value && validationError.value.directory;
|
|
2043
|
+
if (typeof directory !== "string") {
|
|
2044
|
+
addError({
|
|
2045
|
+
message: `Expected gallery validation error 'value' to have property 'directory' of type 'string'. Found: ${typeof directory}. ${TYPE_ERROR_MESSAGE}`,
|
|
2046
|
+
typeError: true
|
|
2047
|
+
});
|
|
2048
|
+
} else {
|
|
2049
|
+
const modulesUsingDir = galleryDirectoryToModules.get(directory) ?? [];
|
|
2050
|
+
const conflictingModules = modulesUsingDir.filter(m => m !== path);
|
|
2051
|
+
if (conflictingModules.length > 0) {
|
|
2052
|
+
addError({
|
|
2053
|
+
message: `Gallery directory '${directory}' in ${path} conflicts with: ${conflictingModules.join(", ")}. Each gallery must use a unique directory.`
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
// If conflictingModules is empty, directory is unique — silently drop the error.
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
} else if ((_validationError$fixe7 = validationError.fixes) !== null && _validationError$fixe7 !== void 0 && _validationError$fixe7.includes("images:check-all-files") || (_validationError$fixe8 = validationError.fixes) !== null && _validationError$fixe8 !== void 0 && _validationError$fixe8.includes("files:check-all-files")) ; else {
|
|
1993
2060
|
addError(validationError);
|
|
1994
2061
|
}
|
|
1995
2062
|
}
|
|
@@ -2269,16 +2336,22 @@ class ValOps {
|
|
|
2269
2336
|
await Promise.all(Object.entries(fileLastUpdatedByPatchId).map(async ([filePath, patchData]) => {
|
|
2270
2337
|
const {
|
|
2271
2338
|
patchId,
|
|
2272
|
-
remote
|
|
2339
|
+
remote,
|
|
2340
|
+
isDelete
|
|
2273
2341
|
} = patchData;
|
|
2274
2342
|
if (globalAppliedPatches.includes(patchId)) {
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2343
|
+
if (isDelete) {
|
|
2344
|
+
// Signal file deletion via patchedSourceFiles null entry
|
|
2345
|
+
patchedSourceFiles[filePath] = null;
|
|
2346
|
+
} else {
|
|
2347
|
+
// TODO: do we want to make sure the file is there? Then again, it should be rare that it happens (unless there's a Val bug) so it might be enough to fail later (at commit)
|
|
2348
|
+
// TODO: include sha256? This way we can make sure we pick the right file since theoretically there could be multiple files with the same path in the same patch
|
|
2349
|
+
// or is that the case? We are picking the latest file by path so, that should be enough?
|
|
2350
|
+
patchedBinaryFilesDescriptors[filePath] = {
|
|
2351
|
+
patchId,
|
|
2352
|
+
remote
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2282
2355
|
} else {
|
|
2283
2356
|
hasErrors = true;
|
|
2284
2357
|
binaryFilePatchErrors[filePath] = {
|
|
@@ -2301,8 +2374,8 @@ class ValOps {
|
|
|
2301
2374
|
}
|
|
2302
2375
|
|
|
2303
2376
|
// #region createPatch
|
|
2304
|
-
async createPatch(path, patch, patchId, parentRef, authorId) {
|
|
2305
|
-
const saveRes = await this.saveSourceFilePatch(path, patch, patchId, parentRef, authorId);
|
|
2377
|
+
async createPatch(path, patch, patchId, parentRef, sessionId, authorId) {
|
|
2378
|
+
const saveRes = await this.saveSourceFilePatch(path, patch, patchId, parentRef, authorId, sessionId);
|
|
2306
2379
|
if (result.isErr(saveRes)) {
|
|
2307
2380
|
console.error(`Could not save source patch at path: '${path}'. Error: ${saveRes.error.errorType === "other" ? saveRes.error.message : saveRes.error.errorType}`);
|
|
2308
2381
|
if (saveRes.error.errorType === "patch-head-conflict") {
|
|
@@ -2324,8 +2397,8 @@ class ValOps {
|
|
|
2324
2397
|
// #region abstract ops
|
|
2325
2398
|
}
|
|
2326
2399
|
function isOnlyFileCheckValidationError(validationError) {
|
|
2327
|
-
var _validationError$
|
|
2328
|
-
if ((_validationError$
|
|
2400
|
+
var _validationError$fixe9;
|
|
2401
|
+
if ((_validationError$fixe9 = validationError.fixes) !== null && _validationError$fixe9 !== void 0 && _validationError$fixe9.every(f => f === "file:check-metadata" || f === "image:check-metadata")) {
|
|
2329
2402
|
return true;
|
|
2330
2403
|
}
|
|
2331
2404
|
return false;
|
|
@@ -2694,11 +2767,17 @@ class ValOpsFS extends ValOps {
|
|
|
2694
2767
|
parentPatchId: dir
|
|
2695
2768
|
});
|
|
2696
2769
|
} else {
|
|
2697
|
-
|
|
2770
|
+
const patchId = parsedPatch.data.patchId;
|
|
2771
|
+
if (includes && includes.length > 0 && !includes.includes(patchId)) {
|
|
2698
2772
|
return;
|
|
2699
2773
|
}
|
|
2700
|
-
patches[
|
|
2701
|
-
|
|
2774
|
+
patches[patchId] = {
|
|
2775
|
+
path: parsedPatch.data.path,
|
|
2776
|
+
patch: parsedPatch.data.patch,
|
|
2777
|
+
parentRef: parsedPatch.data.parentRef,
|
|
2778
|
+
baseSha: parsedPatch.data.baseSha,
|
|
2779
|
+
createdAt: parsedPatch.data.createdAt,
|
|
2780
|
+
authorId: parsedPatch.data.authorId,
|
|
2702
2781
|
appliedAt: null
|
|
2703
2782
|
};
|
|
2704
2783
|
}
|
|
@@ -2869,7 +2948,7 @@ class ValOpsFS extends ValOps {
|
|
|
2869
2948
|
};
|
|
2870
2949
|
}
|
|
2871
2950
|
}
|
|
2872
|
-
async saveSourceFilePatch(path, patch, patchId, parentRef, authorId) {
|
|
2951
|
+
async saveSourceFilePatch(path, patch, patchId, parentRef, authorId, sessionId) {
|
|
2873
2952
|
const patchDir = this.getParentPatchIdFromParentRef(parentRef);
|
|
2874
2953
|
try {
|
|
2875
2954
|
const baseSha = await this.getBaseSha();
|
|
@@ -2879,6 +2958,7 @@ class ValOpsFS extends ValOps {
|
|
|
2879
2958
|
parentRef,
|
|
2880
2959
|
path,
|
|
2881
2960
|
authorId,
|
|
2961
|
+
sessionId,
|
|
2882
2962
|
baseSha,
|
|
2883
2963
|
coreVersion: Internal.VERSION.core,
|
|
2884
2964
|
createdAt: new Date().toISOString()
|
|
@@ -2956,6 +3036,14 @@ class ValOpsFS extends ValOps {
|
|
|
2956
3036
|
const patchFilePath = this.getBinaryFilePath(filePath, patchDir);
|
|
2957
3037
|
const metadataFilePath = this.getBinaryFileMetadataPath(filePath, patchDir);
|
|
2958
3038
|
try {
|
|
3039
|
+
if (data === null) {
|
|
3040
|
+
this.host.deleteFile(patchFilePath);
|
|
3041
|
+
this.host.deleteFile(metadataFilePath);
|
|
3042
|
+
return {
|
|
3043
|
+
patchId,
|
|
3044
|
+
filePath
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
2959
3047
|
const buffer = bufferFromDataUrl(data);
|
|
2960
3048
|
if (!buffer) {
|
|
2961
3049
|
return {
|
|
@@ -3216,7 +3304,11 @@ class ValOpsFS extends ValOps {
|
|
|
3216
3304
|
for (const [filePath, data] of Object.entries(preparedCommit.patchedSourceFiles)) {
|
|
3217
3305
|
const absPath = path__default.join(this.rootDir, ...filePath.split("/"));
|
|
3218
3306
|
try {
|
|
3219
|
-
|
|
3307
|
+
if (data === null) {
|
|
3308
|
+
this.host.deleteFile(absPath);
|
|
3309
|
+
} else {
|
|
3310
|
+
this.host.writeUf8File(absPath, data);
|
|
3311
|
+
}
|
|
3220
3312
|
updatedFiles.push(absPath);
|
|
3221
3313
|
} catch (err) {
|
|
3222
3314
|
errors[absPath] = {
|
|
@@ -3305,12 +3397,6 @@ class ValOpsFS extends ValOps {
|
|
|
3305
3397
|
return result.ok(Object.fromEntries(Object.entries(patches.patches).map(([patchId, value]) => [patchId, this.getParentPatchIdFromParentRef(value.parentRef)])));
|
|
3306
3398
|
}
|
|
3307
3399
|
|
|
3308
|
-
// #region profiles
|
|
3309
|
-
async getProfiles() {
|
|
3310
|
-
// We do not have profiles in FS mode
|
|
3311
|
-
return [];
|
|
3312
|
-
}
|
|
3313
|
-
|
|
3314
3400
|
// #region fs file path helpers
|
|
3315
3401
|
getPatchesDir() {
|
|
3316
3402
|
return path__default.join(this.rootDir, ValOpsFS.VAL_DIR, "patches");
|
|
@@ -3342,6 +3428,11 @@ class FSOpsHost {
|
|
|
3342
3428
|
});
|
|
3343
3429
|
}
|
|
3344
3430
|
}
|
|
3431
|
+
deleteFile(path) {
|
|
3432
|
+
if (this.fileExists(path)) {
|
|
3433
|
+
fs.rmSync(path);
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3345
3436
|
moveDir(from, to) {
|
|
3346
3437
|
fs.renameSync(from, to);
|
|
3347
3438
|
}
|
|
@@ -3414,7 +3505,9 @@ const FSPatch = z.object({
|
|
|
3414
3505
|
parentRef: ParentRef,
|
|
3415
3506
|
authorId: z.string().refine(p => true).nullable(),
|
|
3416
3507
|
createdAt: z.string().datetime(),
|
|
3417
|
-
coreVersion: z.string().nullable()
|
|
3508
|
+
coreVersion: z.string().nullable(),
|
|
3509
|
+
// TODO: use this to check if patch is compatible with current core version?
|
|
3510
|
+
sessionId: z.string().nullable()
|
|
3418
3511
|
});
|
|
3419
3512
|
const FSPatchBase = z.object({
|
|
3420
3513
|
baseSha: z.string().refine(p => true),
|
|
@@ -3516,17 +3609,6 @@ const CommitResponse = z.object({
|
|
|
3516
3609
|
commit: CommitSha,
|
|
3517
3610
|
branch: z.string()
|
|
3518
3611
|
});
|
|
3519
|
-
const ProfilesResponse = z.object({
|
|
3520
|
-
profiles: z.array(z.object({
|
|
3521
|
-
profileId: z.string(),
|
|
3522
|
-
fullName: z.string(),
|
|
3523
|
-
email: z.string().optional(),
|
|
3524
|
-
// TODO: make this required once this can be guaranteed
|
|
3525
|
-
avatar: z.object({
|
|
3526
|
-
url: z.string()
|
|
3527
|
-
}).nullable()
|
|
3528
|
-
}))
|
|
3529
|
-
});
|
|
3530
3612
|
const NonceResponse = z.object({
|
|
3531
3613
|
nonce: z.string(),
|
|
3532
3614
|
url: z.string()
|
|
@@ -3977,7 +4059,7 @@ class ValOpsHttp extends ValOps {
|
|
|
3977
4059
|
};
|
|
3978
4060
|
}
|
|
3979
4061
|
}
|
|
3980
|
-
async saveSourceFilePatch(path, patch, patchId, parentRef, authorId) {
|
|
4062
|
+
async saveSourceFilePatch(path, patch, patchId, parentRef, authorId, sessionId) {
|
|
3981
4063
|
const baseSha = await this.getBaseSha();
|
|
3982
4064
|
return fetch(`${this.contentUrl}/v1/${this.project}/patches`, {
|
|
3983
4065
|
method: "POST",
|
|
@@ -3989,6 +4071,7 @@ class ValOpsHttp extends ValOps {
|
|
|
3989
4071
|
path,
|
|
3990
4072
|
patch,
|
|
3991
4073
|
authorId,
|
|
4074
|
+
sessionId,
|
|
3992
4075
|
patchId,
|
|
3993
4076
|
parentPatchId: parentRef.type === "patch" ? parentRef.patchId : null,
|
|
3994
4077
|
baseSha,
|
|
@@ -4412,31 +4495,6 @@ class ValOpsHttp extends ValOps {
|
|
|
4412
4495
|
};
|
|
4413
4496
|
}
|
|
4414
4497
|
}
|
|
4415
|
-
|
|
4416
|
-
// #region profiles
|
|
4417
|
-
async getProfiles() {
|
|
4418
|
-
var _res$headers$get6;
|
|
4419
|
-
const res = await fetch(`${this.contentUrl}/v1/${this.project}/profiles`, {
|
|
4420
|
-
headers: {
|
|
4421
|
-
...this.authHeaders,
|
|
4422
|
-
"Content-Type": "application/json"
|
|
4423
|
-
}
|
|
4424
|
-
});
|
|
4425
|
-
if (res.ok) {
|
|
4426
|
-
const parsed = ProfilesResponse.safeParse(await res.json());
|
|
4427
|
-
if (parsed.error) {
|
|
4428
|
-
console.error("Could not parse profiles response", parsed.error);
|
|
4429
|
-
throw Error(`Could not get profiles from remote server: wrong format. You might need to upgrade Val.`);
|
|
4430
|
-
}
|
|
4431
|
-
return parsed.data.profiles;
|
|
4432
|
-
}
|
|
4433
|
-
if ((_res$headers$get6 = res.headers.get("Content-Type")) !== null && _res$headers$get6 !== void 0 && _res$headers$get6.includes("application/json")) {
|
|
4434
|
-
const json = await res.json();
|
|
4435
|
-
const message = getErrorMessageFromUnknownJson(json, "Unknown error");
|
|
4436
|
-
throw Error(`Could not get profiles (status: ${res.status}): ${message}`);
|
|
4437
|
-
}
|
|
4438
|
-
throw Error(`Could not get profiles. Got status: ${res.status}`);
|
|
4439
|
-
}
|
|
4440
4498
|
}
|
|
4441
4499
|
|
|
4442
4500
|
const host = process.env.VAL_CONTENT_URL || DEFAULT_CONTENT_HOST;
|
|
@@ -4589,6 +4647,16 @@ function hasRemoteFileSchema(schema) {
|
|
|
4589
4647
|
|
|
4590
4648
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
4591
4649
|
const ValServer = (valModules, options, callbacks) => {
|
|
4650
|
+
const ProfilesResponse = z.object({
|
|
4651
|
+
profiles: z.array(z.object({
|
|
4652
|
+
profileId: z.string(),
|
|
4653
|
+
fullName: z.string(),
|
|
4654
|
+
email: z.string().optional(),
|
|
4655
|
+
avatar: z.object({
|
|
4656
|
+
url: z.string()
|
|
4657
|
+
}).nullable()
|
|
4658
|
+
}))
|
|
4659
|
+
});
|
|
4592
4660
|
let serverOps;
|
|
4593
4661
|
if (options.mode === "fs") {
|
|
4594
4662
|
serverOps = new ValOpsFS(options.valContentUrl, options.cwd, valModules, {
|
|
@@ -5438,10 +5506,11 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
5438
5506
|
}
|
|
5439
5507
|
const patches = req.body.patches;
|
|
5440
5508
|
let parentRef = req.body.parentRef;
|
|
5509
|
+
const sessionId = req.body.sessionId ?? null;
|
|
5441
5510
|
const authorId = "id" in auth ? auth.id : null;
|
|
5442
5511
|
const newPatchIds = [];
|
|
5443
5512
|
for (const patch of patches) {
|
|
5444
|
-
const createPatchRes = await serverOps.createPatch(patch.path, patch.patch, patch.patchId, parentRef, authorId);
|
|
5513
|
+
const createPatchRes = await serverOps.createPatch(patch.path, patch.patch, patch.patchId, parentRef, sessionId, authorId);
|
|
5445
5514
|
if (result.isErr(createPatchRes)) {
|
|
5446
5515
|
if (createPatchRes.error.errorType === "patch-head-conflict") {
|
|
5447
5516
|
return {
|
|
@@ -5802,13 +5871,87 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
5802
5871
|
}
|
|
5803
5872
|
};
|
|
5804
5873
|
}
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5874
|
+
if (!options.project) {
|
|
5875
|
+
return {
|
|
5876
|
+
status: 500,
|
|
5877
|
+
json: {
|
|
5878
|
+
message: "Project is not configured"
|
|
5879
|
+
}
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5882
|
+
const authDataRes = await getRemoteFileAuth();
|
|
5883
|
+
if (authDataRes.status !== 200) {
|
|
5884
|
+
if (serverOps instanceof ValOpsFS && authDataRes.json.errorCode === "pat-error") {
|
|
5885
|
+
return {
|
|
5886
|
+
status: 200,
|
|
5887
|
+
json: {
|
|
5888
|
+
profiles: []
|
|
5889
|
+
}
|
|
5890
|
+
};
|
|
5891
|
+
}
|
|
5892
|
+
return {
|
|
5893
|
+
status: 500,
|
|
5894
|
+
json: {
|
|
5895
|
+
message: authDataRes.json.message
|
|
5896
|
+
}
|
|
5897
|
+
};
|
|
5898
|
+
}
|
|
5899
|
+
const authData = authDataRes.json.remoteFileAuth;
|
|
5900
|
+
const execFetch = async headers => {
|
|
5901
|
+
try {
|
|
5902
|
+
const upstreamUrl = `${options.valContentUrl}/v1/${options.project}/profiles`;
|
|
5903
|
+
const upstreamRes = await fetch(upstreamUrl, {
|
|
5904
|
+
method: "GET",
|
|
5905
|
+
headers
|
|
5906
|
+
});
|
|
5907
|
+
if (!upstreamRes.ok) {
|
|
5908
|
+
const text = await upstreamRes.text();
|
|
5909
|
+
const isAuthError = upstreamRes.status === 401 || upstreamRes.status === 403;
|
|
5910
|
+
return {
|
|
5911
|
+
status: isAuthError ? 401 : 500,
|
|
5912
|
+
json: {
|
|
5913
|
+
message: isAuthError ? `Profile authentication failed: ${upstreamRes.status} ${text}` : `Profiles failed: ${upstreamRes.status} ${text}`
|
|
5914
|
+
}
|
|
5915
|
+
};
|
|
5916
|
+
}
|
|
5917
|
+
const parseRes = ProfilesResponse.safeParse(await upstreamRes.json());
|
|
5918
|
+
if (!parseRes.success) {
|
|
5919
|
+
return {
|
|
5920
|
+
status: 500,
|
|
5921
|
+
json: {
|
|
5922
|
+
message: "Could not parse profiles response: " + fromError(parseRes.error).toString()
|
|
5923
|
+
}
|
|
5924
|
+
};
|
|
5925
|
+
}
|
|
5926
|
+
return {
|
|
5927
|
+
status: 200,
|
|
5928
|
+
json: {
|
|
5929
|
+
profiles: parseRes.data.profiles
|
|
5930
|
+
}
|
|
5931
|
+
};
|
|
5932
|
+
} catch (err) {
|
|
5933
|
+
return {
|
|
5934
|
+
status: 500,
|
|
5935
|
+
json: {
|
|
5936
|
+
message: err instanceof Error ? err.message : "Profiles request failed"
|
|
5937
|
+
}
|
|
5938
|
+
};
|
|
5810
5939
|
}
|
|
5811
5940
|
};
|
|
5941
|
+
if (serverOps instanceof ValOpsFS) {
|
|
5942
|
+
return execFetch(getProfileAuthHeaders(authData, null, "application/json"));
|
|
5943
|
+
}
|
|
5944
|
+
if (!options.valSecret) {
|
|
5945
|
+
return {
|
|
5946
|
+
status: 500,
|
|
5947
|
+
json: {
|
|
5948
|
+
message: "Secret is not configured"
|
|
5949
|
+
}
|
|
5950
|
+
};
|
|
5951
|
+
}
|
|
5952
|
+
return withAuth(options.valSecret, cookies, "profiles", data => {
|
|
5953
|
+
return execFetch(getProfileAuthHeaders(authData, data, "application/json"));
|
|
5954
|
+
});
|
|
5812
5955
|
}
|
|
5813
5956
|
},
|
|
5814
5957
|
"/commit-summary": {
|
|
@@ -5990,6 +6133,381 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
5990
6133
|
}
|
|
5991
6134
|
}
|
|
5992
6135
|
},
|
|
6136
|
+
//#region ai proxy
|
|
6137
|
+
"/ai/initialize": {
|
|
6138
|
+
POST: async req => {
|
|
6139
|
+
const cookies = req.cookies;
|
|
6140
|
+
const auth = getAuth(cookies);
|
|
6141
|
+
if (auth.error) {
|
|
6142
|
+
return {
|
|
6143
|
+
status: 401,
|
|
6144
|
+
json: {
|
|
6145
|
+
message: auth.error
|
|
6146
|
+
}
|
|
6147
|
+
};
|
|
6148
|
+
}
|
|
6149
|
+
if (!options.project) {
|
|
6150
|
+
return {
|
|
6151
|
+
status: 500,
|
|
6152
|
+
json: {
|
|
6153
|
+
message: "Project is not configured"
|
|
6154
|
+
}
|
|
6155
|
+
};
|
|
6156
|
+
}
|
|
6157
|
+
const authDataRes = await getRemoteFileAuth();
|
|
6158
|
+
if (authDataRes.status !== 200) {
|
|
6159
|
+
return {
|
|
6160
|
+
status: 500,
|
|
6161
|
+
json: {
|
|
6162
|
+
message: authDataRes.json.message
|
|
6163
|
+
}
|
|
6164
|
+
};
|
|
6165
|
+
}
|
|
6166
|
+
const authData = authDataRes.json.remoteFileAuth;
|
|
6167
|
+
const execFetch = async headers => {
|
|
6168
|
+
try {
|
|
6169
|
+
const upstreamUrl = `${options.valContentUrl}/v1/${options.project}/ai/initialize`;
|
|
6170
|
+
const upstreamRes = await fetch(upstreamUrl, {
|
|
6171
|
+
method: "POST",
|
|
6172
|
+
headers,
|
|
6173
|
+
body: JSON.stringify({})
|
|
6174
|
+
});
|
|
6175
|
+
if (!upstreamRes.ok) {
|
|
6176
|
+
const text = await upstreamRes.text();
|
|
6177
|
+
return {
|
|
6178
|
+
status: 500,
|
|
6179
|
+
json: {
|
|
6180
|
+
message: `AI initialize failed: ${upstreamRes.status} ${text}`
|
|
6181
|
+
}
|
|
6182
|
+
};
|
|
6183
|
+
}
|
|
6184
|
+
const json = await upstreamRes.json();
|
|
6185
|
+
const wsUrl = options.valContentUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:") + `/v1/${options.project}/ai/connect`;
|
|
6186
|
+
return {
|
|
6187
|
+
status: 200,
|
|
6188
|
+
json: {
|
|
6189
|
+
nonce: json.nonce,
|
|
6190
|
+
wsUrl
|
|
6191
|
+
}
|
|
6192
|
+
};
|
|
6193
|
+
} catch (err) {
|
|
6194
|
+
return {
|
|
6195
|
+
status: 500,
|
|
6196
|
+
json: {
|
|
6197
|
+
message: err instanceof Error ? err.message : "AI initialize error"
|
|
6198
|
+
}
|
|
6199
|
+
};
|
|
6200
|
+
}
|
|
6201
|
+
};
|
|
6202
|
+
if (serverOps instanceof ValOpsFS) {
|
|
6203
|
+
return execFetch(getProfileAuthHeaders(authData, null, "application/json"));
|
|
6204
|
+
}
|
|
6205
|
+
if (!options.valSecret) {
|
|
6206
|
+
return {
|
|
6207
|
+
status: 500,
|
|
6208
|
+
json: {
|
|
6209
|
+
message: "Secret is not configured"
|
|
6210
|
+
}
|
|
6211
|
+
};
|
|
6212
|
+
}
|
|
6213
|
+
return withAuth(options.valSecret, cookies, "ai/initialize", data => {
|
|
6214
|
+
return execFetch(getProfileAuthHeaders(authData, data, "application/json"));
|
|
6215
|
+
});
|
|
6216
|
+
}
|
|
6217
|
+
},
|
|
6218
|
+
"/ai/sessions": {
|
|
6219
|
+
GET: async req => {
|
|
6220
|
+
const cookies = req.cookies;
|
|
6221
|
+
const auth = getAuth(cookies);
|
|
6222
|
+
if (auth.error) {
|
|
6223
|
+
return {
|
|
6224
|
+
status: 401,
|
|
6225
|
+
json: {
|
|
6226
|
+
message: auth.error
|
|
6227
|
+
}
|
|
6228
|
+
};
|
|
6229
|
+
}
|
|
6230
|
+
if (!options.project) {
|
|
6231
|
+
return {
|
|
6232
|
+
status: 500,
|
|
6233
|
+
json: {
|
|
6234
|
+
message: "Project is not configured"
|
|
6235
|
+
}
|
|
6236
|
+
};
|
|
6237
|
+
}
|
|
6238
|
+
const authDataRes = await getRemoteFileAuth();
|
|
6239
|
+
if (authDataRes.status !== 200) {
|
|
6240
|
+
return {
|
|
6241
|
+
status: 500,
|
|
6242
|
+
json: {
|
|
6243
|
+
message: authDataRes.json.message
|
|
6244
|
+
}
|
|
6245
|
+
};
|
|
6246
|
+
}
|
|
6247
|
+
const authData = authDataRes.json.remoteFileAuth;
|
|
6248
|
+
const execFetch = async headers => {
|
|
6249
|
+
try {
|
|
6250
|
+
const SessionsResponse = z.object({
|
|
6251
|
+
sessions: z.array(z.object({
|
|
6252
|
+
id: z.string(),
|
|
6253
|
+
name: z.string().nullable(),
|
|
6254
|
+
createdAt: z.string(),
|
|
6255
|
+
updatedAt: z.string()
|
|
6256
|
+
})),
|
|
6257
|
+
nextCursor: z.object({
|
|
6258
|
+
updatedAt: z.string(),
|
|
6259
|
+
id: z.string()
|
|
6260
|
+
}).nullable().optional()
|
|
6261
|
+
});
|
|
6262
|
+
const params = new URLSearchParams();
|
|
6263
|
+
if (req.query.limit) params.set("limit", req.query.limit);
|
|
6264
|
+
if (req.query.cursor_updatedAt) {
|
|
6265
|
+
params.set("cursor_updatedAt", req.query.cursor_updatedAt);
|
|
6266
|
+
}
|
|
6267
|
+
if (req.query.cursor_id) {
|
|
6268
|
+
params.set("cursor_id", req.query.cursor_id);
|
|
6269
|
+
}
|
|
6270
|
+
const qs = params.toString();
|
|
6271
|
+
const upstreamUrl = `${options.valContentUrl}/v1/${options.project}/ai/sessions${qs ? `?${qs}` : ""}`;
|
|
6272
|
+
const upstreamRes = await fetch(upstreamUrl, {
|
|
6273
|
+
headers
|
|
6274
|
+
});
|
|
6275
|
+
if (!upstreamRes.ok) {
|
|
6276
|
+
const text = await upstreamRes.text();
|
|
6277
|
+
return {
|
|
6278
|
+
status: 500,
|
|
6279
|
+
json: {
|
|
6280
|
+
message: `AI sessions failed: ${upstreamRes.status} ${text}`
|
|
6281
|
+
}
|
|
6282
|
+
};
|
|
6283
|
+
}
|
|
6284
|
+
const json = SessionsResponse.safeParse(await upstreamRes.json());
|
|
6285
|
+
if (!json.success) {
|
|
6286
|
+
return {
|
|
6287
|
+
status: 500,
|
|
6288
|
+
json: {
|
|
6289
|
+
message: "Could not parse AI sessions response: " + fromError(json.error).toString()
|
|
6290
|
+
}
|
|
6291
|
+
};
|
|
6292
|
+
}
|
|
6293
|
+
return {
|
|
6294
|
+
status: 200,
|
|
6295
|
+
json: json.data
|
|
6296
|
+
};
|
|
6297
|
+
} catch (err) {
|
|
6298
|
+
return {
|
|
6299
|
+
status: 500,
|
|
6300
|
+
json: {
|
|
6301
|
+
message: err instanceof Error ? err.message : "AI sessions error"
|
|
6302
|
+
}
|
|
6303
|
+
};
|
|
6304
|
+
}
|
|
6305
|
+
};
|
|
6306
|
+
if (serverOps instanceof ValOpsFS) {
|
|
6307
|
+
return execFetch(getProfileAuthHeaders(authData, null, "application/json"));
|
|
6308
|
+
}
|
|
6309
|
+
if (!options.valSecret) {
|
|
6310
|
+
return {
|
|
6311
|
+
status: 500,
|
|
6312
|
+
json: {
|
|
6313
|
+
message: "Secret is not configured"
|
|
6314
|
+
}
|
|
6315
|
+
};
|
|
6316
|
+
}
|
|
6317
|
+
return withAuth(options.valSecret, cookies, "ai/sessions", data => {
|
|
6318
|
+
return execFetch(getProfileAuthHeaders(authData, data, "application/json"));
|
|
6319
|
+
});
|
|
6320
|
+
},
|
|
6321
|
+
PATCH: async req => {
|
|
6322
|
+
const cookies = req.cookies;
|
|
6323
|
+
const auth = getAuth(cookies);
|
|
6324
|
+
if (auth.error) {
|
|
6325
|
+
return {
|
|
6326
|
+
status: 401,
|
|
6327
|
+
json: {
|
|
6328
|
+
message: auth.error
|
|
6329
|
+
}
|
|
6330
|
+
};
|
|
6331
|
+
}
|
|
6332
|
+
if (!options.project) {
|
|
6333
|
+
return {
|
|
6334
|
+
status: 500,
|
|
6335
|
+
json: {
|
|
6336
|
+
message: "Project is not configured"
|
|
6337
|
+
}
|
|
6338
|
+
};
|
|
6339
|
+
}
|
|
6340
|
+
const pathParts = (req.path || "").split("/").filter(Boolean);
|
|
6341
|
+
const sessionId = pathParts[0];
|
|
6342
|
+
if (!sessionId) {
|
|
6343
|
+
return {
|
|
6344
|
+
status: 500,
|
|
6345
|
+
json: {
|
|
6346
|
+
message: "Missing sessionId in path"
|
|
6347
|
+
}
|
|
6348
|
+
};
|
|
6349
|
+
}
|
|
6350
|
+
const authDataRes = await getRemoteFileAuth();
|
|
6351
|
+
if (authDataRes.status !== 200) {
|
|
6352
|
+
return {
|
|
6353
|
+
status: 500,
|
|
6354
|
+
json: {
|
|
6355
|
+
message: authDataRes.json.message
|
|
6356
|
+
}
|
|
6357
|
+
};
|
|
6358
|
+
}
|
|
6359
|
+
const authData = authDataRes.json.remoteFileAuth;
|
|
6360
|
+
const execFetch = async headers => {
|
|
6361
|
+
try {
|
|
6362
|
+
const upstreamUrl = `${options.valContentUrl}/v1/${options.project}/ai/sessions/${encodeURIComponent(sessionId)}`;
|
|
6363
|
+
const upstreamRes = await fetch(upstreamUrl, {
|
|
6364
|
+
method: "PATCH",
|
|
6365
|
+
headers,
|
|
6366
|
+
body: JSON.stringify({
|
|
6367
|
+
name: req.body.name
|
|
6368
|
+
})
|
|
6369
|
+
});
|
|
6370
|
+
if (!upstreamRes.ok) {
|
|
6371
|
+
const text = await upstreamRes.text();
|
|
6372
|
+
return {
|
|
6373
|
+
status: 500,
|
|
6374
|
+
json: {
|
|
6375
|
+
message: `AI session rename failed: ${upstreamRes.status} ${text}`
|
|
6376
|
+
}
|
|
6377
|
+
};
|
|
6378
|
+
}
|
|
6379
|
+
return {
|
|
6380
|
+
status: 200,
|
|
6381
|
+
json: {}
|
|
6382
|
+
};
|
|
6383
|
+
} catch (err) {
|
|
6384
|
+
return {
|
|
6385
|
+
status: 500,
|
|
6386
|
+
json: {
|
|
6387
|
+
message: err instanceof Error ? err.message : "AI session rename error"
|
|
6388
|
+
}
|
|
6389
|
+
};
|
|
6390
|
+
}
|
|
6391
|
+
};
|
|
6392
|
+
if (serverOps instanceof ValOpsFS) {
|
|
6393
|
+
return execFetch(getProfileAuthHeaders(authData, null, "application/json"));
|
|
6394
|
+
}
|
|
6395
|
+
if (!options.valSecret) {
|
|
6396
|
+
return {
|
|
6397
|
+
status: 500,
|
|
6398
|
+
json: {
|
|
6399
|
+
message: "Secret is not configured"
|
|
6400
|
+
}
|
|
6401
|
+
};
|
|
6402
|
+
}
|
|
6403
|
+
return withAuth(options.valSecret, cookies, "ai/sessions/rename", data => {
|
|
6404
|
+
return execFetch(getProfileAuthHeaders(authData, data, "application/json"));
|
|
6405
|
+
});
|
|
6406
|
+
}
|
|
6407
|
+
},
|
|
6408
|
+
"/ai/messages": {
|
|
6409
|
+
GET: async req => {
|
|
6410
|
+
const cookies = req.cookies;
|
|
6411
|
+
const auth = getAuth(cookies);
|
|
6412
|
+
if (auth.error) {
|
|
6413
|
+
return {
|
|
6414
|
+
status: 401,
|
|
6415
|
+
json: {
|
|
6416
|
+
message: auth.error
|
|
6417
|
+
}
|
|
6418
|
+
};
|
|
6419
|
+
}
|
|
6420
|
+
if (!options.project) {
|
|
6421
|
+
return {
|
|
6422
|
+
status: 500,
|
|
6423
|
+
json: {
|
|
6424
|
+
message: "Project is not configured"
|
|
6425
|
+
}
|
|
6426
|
+
};
|
|
6427
|
+
}
|
|
6428
|
+
const pathParts = (req.path || "").split("/").filter(Boolean);
|
|
6429
|
+
const sessionId = pathParts[0];
|
|
6430
|
+
if (!sessionId) {
|
|
6431
|
+
return {
|
|
6432
|
+
status: 500,
|
|
6433
|
+
json: {
|
|
6434
|
+
message: "Missing sessionId in path"
|
|
6435
|
+
}
|
|
6436
|
+
};
|
|
6437
|
+
}
|
|
6438
|
+
const authDataRes = await getRemoteFileAuth();
|
|
6439
|
+
if (authDataRes.status !== 200) {
|
|
6440
|
+
return {
|
|
6441
|
+
status: 500,
|
|
6442
|
+
json: {
|
|
6443
|
+
message: authDataRes.json.message
|
|
6444
|
+
}
|
|
6445
|
+
};
|
|
6446
|
+
}
|
|
6447
|
+
const authData = authDataRes.json.remoteFileAuth;
|
|
6448
|
+
const execFetch = async headers => {
|
|
6449
|
+
try {
|
|
6450
|
+
const SessionMessagesResponse = z.object({
|
|
6451
|
+
messages: z.array(z.object({
|
|
6452
|
+
role: z.string(),
|
|
6453
|
+
content: z.string()
|
|
6454
|
+
})),
|
|
6455
|
+
nextCursor: z.object({
|
|
6456
|
+
updatedAt: z.string(),
|
|
6457
|
+
id: z.string()
|
|
6458
|
+
}).nullable().optional()
|
|
6459
|
+
});
|
|
6460
|
+
const upstreamUrl = `${options.valContentUrl}/v1/${options.project}/ai/sessions/${encodeURIComponent(sessionId)}/messages`;
|
|
6461
|
+
const upstreamRes = await fetch(upstreamUrl, {
|
|
6462
|
+
headers
|
|
6463
|
+
});
|
|
6464
|
+
if (!upstreamRes.ok) {
|
|
6465
|
+
const text = await upstreamRes.text();
|
|
6466
|
+
return {
|
|
6467
|
+
status: 500,
|
|
6468
|
+
json: {
|
|
6469
|
+
message: `AI session messages failed: ${upstreamRes.status} ${text}`
|
|
6470
|
+
}
|
|
6471
|
+
};
|
|
6472
|
+
}
|
|
6473
|
+
const json = SessionMessagesResponse.safeParse(await upstreamRes.json());
|
|
6474
|
+
if (!json.success) {
|
|
6475
|
+
return {
|
|
6476
|
+
status: 500,
|
|
6477
|
+
json: {
|
|
6478
|
+
message: "Could not parse AI session messages response: " + fromError(json.error).toString()
|
|
6479
|
+
}
|
|
6480
|
+
};
|
|
6481
|
+
}
|
|
6482
|
+
return {
|
|
6483
|
+
status: 200,
|
|
6484
|
+
json: json.data
|
|
6485
|
+
};
|
|
6486
|
+
} catch (err) {
|
|
6487
|
+
return {
|
|
6488
|
+
status: 500,
|
|
6489
|
+
json: {
|
|
6490
|
+
message: err instanceof Error ? err.message : "AI session messages error"
|
|
6491
|
+
}
|
|
6492
|
+
};
|
|
6493
|
+
}
|
|
6494
|
+
};
|
|
6495
|
+
if (serverOps instanceof ValOpsFS) {
|
|
6496
|
+
return execFetch(getProfileAuthHeaders(authData, null, "application/json"));
|
|
6497
|
+
}
|
|
6498
|
+
if (!options.valSecret) {
|
|
6499
|
+
return {
|
|
6500
|
+
status: 500,
|
|
6501
|
+
json: {
|
|
6502
|
+
message: "Secret is not configured"
|
|
6503
|
+
}
|
|
6504
|
+
};
|
|
6505
|
+
}
|
|
6506
|
+
return withAuth(options.valSecret, cookies, "ai/sessions/messages", data => {
|
|
6507
|
+
return execFetch(getProfileAuthHeaders(authData, data, "application/json"));
|
|
6508
|
+
});
|
|
6509
|
+
}
|
|
6510
|
+
},
|
|
5993
6511
|
//#region files
|
|
5994
6512
|
"/files": {
|
|
5995
6513
|
GET: async req => {
|
|
@@ -6211,6 +6729,21 @@ async function withAuth(secret, cookies, errorMessageType, handler) {
|
|
|
6211
6729
|
};
|
|
6212
6730
|
}
|
|
6213
6731
|
}
|
|
6732
|
+
function getProfileAuthHeaders(auth, data, type) {
|
|
6733
|
+
if ("pat" in auth) {
|
|
6734
|
+
return {
|
|
6735
|
+
"x-val-pat": auth.pat,
|
|
6736
|
+
"Content-Type": type
|
|
6737
|
+
};
|
|
6738
|
+
}
|
|
6739
|
+
if ("apiKey" in auth && data) {
|
|
6740
|
+
return {
|
|
6741
|
+
...getAuthHeaders(auth.apiKey, type),
|
|
6742
|
+
"x-val-profile-id": data.sub
|
|
6743
|
+
};
|
|
6744
|
+
}
|
|
6745
|
+
throw new Error("Invalid auth");
|
|
6746
|
+
}
|
|
6214
6747
|
function getAuthHeaders(token, type) {
|
|
6215
6748
|
if (!type) {
|
|
6216
6749
|
return {
|
|
@@ -6738,6 +7271,9 @@ class ValFSHost {
|
|
|
6738
7271
|
readFile(fileName) {
|
|
6739
7272
|
return this.valFS.readFile(fileName);
|
|
6740
7273
|
}
|
|
7274
|
+
readBuffer(fileName) {
|
|
7275
|
+
return this.valFS.readBuffer(fileName);
|
|
7276
|
+
}
|
|
6741
7277
|
realpath(path) {
|
|
6742
7278
|
return this.valFS.realpath(path);
|
|
6743
7279
|
}
|
|
@@ -7212,6 +7748,88 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
|
|
|
7212
7748
|
metadata: v.metadata
|
|
7213
7749
|
} : value
|
|
7214
7750
|
});
|
|
7751
|
+
} else if (fix === "images:check-all-files" || fix === "files:check-all-files") {
|
|
7752
|
+
if (!moduleSource || typeof moduleSource !== "object") {
|
|
7753
|
+
remainingErrors.push({
|
|
7754
|
+
...validationError,
|
|
7755
|
+
message: "Unexpected error while checking gallery metadata (no moduleSource)",
|
|
7756
|
+
fixes: undefined
|
|
7757
|
+
});
|
|
7758
|
+
continue;
|
|
7759
|
+
}
|
|
7760
|
+
const gallerySource = moduleSource;
|
|
7761
|
+
for (const [entryKey, storedEntry] of Object.entries(gallerySource)) {
|
|
7762
|
+
const filename = path__default.join(config.projectRoot, entryKey);
|
|
7763
|
+
let buffer;
|
|
7764
|
+
try {
|
|
7765
|
+
buffer = fs.readFileSync(filename);
|
|
7766
|
+
} catch {
|
|
7767
|
+
if (apply) {
|
|
7768
|
+
const removePath = sourceToPatchPath(sourcePath).concat([entryKey]);
|
|
7769
|
+
if (isNotRoot(removePath)) {
|
|
7770
|
+
patch.push({
|
|
7771
|
+
op: "remove",
|
|
7772
|
+
path: removePath
|
|
7773
|
+
});
|
|
7774
|
+
}
|
|
7775
|
+
} else {
|
|
7776
|
+
remainingErrors.push({
|
|
7777
|
+
...validationError,
|
|
7778
|
+
message: `Could not read file: ${filename} - file might not exist or can not be accessed`,
|
|
7779
|
+
fixes: undefined
|
|
7780
|
+
});
|
|
7781
|
+
}
|
|
7782
|
+
continue;
|
|
7783
|
+
}
|
|
7784
|
+
if (fix === "images:check-all-files") {
|
|
7785
|
+
const actualMetadata = await extractImageMetadata(filename, buffer);
|
|
7786
|
+
const stored = storedEntry;
|
|
7787
|
+
const metadataIsCorrect = stored.width === actualMetadata.width && stored.height === actualMetadata.height && stored.mimeType === actualMetadata.mimeType;
|
|
7788
|
+
if (!metadataIsCorrect) {
|
|
7789
|
+
if (apply) {
|
|
7790
|
+
patch.push({
|
|
7791
|
+
op: "replace",
|
|
7792
|
+
path: sourceToPatchPath(sourcePath).concat([entryKey]),
|
|
7793
|
+
value: {
|
|
7794
|
+
...stored,
|
|
7795
|
+
width: actualMetadata.width ?? 0,
|
|
7796
|
+
height: actualMetadata.height ?? 0,
|
|
7797
|
+
mimeType: actualMetadata.mimeType ?? "application/octet-stream"
|
|
7798
|
+
}
|
|
7799
|
+
});
|
|
7800
|
+
} else {
|
|
7801
|
+
remainingErrors.push({
|
|
7802
|
+
...validationError,
|
|
7803
|
+
message: `Image metadata for '${entryKey}' is incorrect (width: ${stored.width ?? "<empty>"} vs ${actualMetadata.width}, height: ${stored.height ?? "<empty>"} vs ${actualMetadata.height}, mimeType: ${stored.mimeType ?? "<empty>"} vs ${actualMetadata.mimeType}). Use --fix to update.`
|
|
7804
|
+
});
|
|
7805
|
+
}
|
|
7806
|
+
}
|
|
7807
|
+
} else if (fix === "files:check-all-files") {
|
|
7808
|
+
const actualMetadata = await extractFileMetadata(filename);
|
|
7809
|
+
const stored = storedEntry;
|
|
7810
|
+
const metadataIsCorrect = stored.mimeType === actualMetadata.mimeType;
|
|
7811
|
+
if (!metadataIsCorrect) {
|
|
7812
|
+
if (apply) {
|
|
7813
|
+
patch.push({
|
|
7814
|
+
op: "replace",
|
|
7815
|
+
path: sourceToPatchPath(sourcePath).concat([entryKey]),
|
|
7816
|
+
value: {
|
|
7817
|
+
...stored,
|
|
7818
|
+
mimeType: actualMetadata.mimeType ?? "application/octet-stream"
|
|
7819
|
+
}
|
|
7820
|
+
});
|
|
7821
|
+
} else {
|
|
7822
|
+
remainingErrors.push({
|
|
7823
|
+
...validationError,
|
|
7824
|
+
message: `File metadata for '${entryKey}' has incorrect mimeType: '${stored.mimeType ?? "<empty>"}' vs '${actualMetadata.mimeType}'. Use --fix to update.`
|
|
7825
|
+
});
|
|
7826
|
+
}
|
|
7827
|
+
}
|
|
7828
|
+
} else {
|
|
7829
|
+
const exhaustiveCheck = fix;
|
|
7830
|
+
throw new Error(`Internal error: unhandled fix type ${exhaustiveCheck}`);
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7215
7833
|
} else if (fix === "file:check-remote" || fix === "image:check-remote") {
|
|
7216
7834
|
const v = getRemoteValueFromValidationError(validationError);
|
|
7217
7835
|
if (!v.success) {
|