@valbuild/cli 0.102.0 → 0.103.1
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/cli/dist/valbuild-cli-cli.cjs.dev.js +26 -566
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +26 -566
- package/cli/dist/valbuild-cli-cli.esm.js +25 -565
- package/package.json +5 -5
- package/src/__fixtures__/basic/content/basic-image-from-galleries.val.ts +2 -2
- package/src/__fixtures__/basic/content/basic-image-from-gallery.val.ts +1 -1
- package/src/__fixtures__/basic/content/basic-image.val.ts +3 -5
- package/src/__fixtures__/debug-snapshot/.val/patches/11111111-1111-4111-8111-111111111111/patch.json +6 -9
- package/src/__fixtures__/debug-snapshot/.val/patches/22222222-2222-4222-8222-222222222222/patch.json +5 -10
- package/src/__fixtures__/debug-snapshot/.val/patches/33333333-3333-4333-8333-333333333333/patch.json +16 -0
- package/src/__fixtures__/debug-snapshot/.val/patches/patches.log +4 -0
- package/src/debug/snapshot.ts +23 -18
- package/src/debug.test.ts +38 -17
- package/src/listUnusedFiles.ts +15 -22
- package/src/runValidation.test.ts +5 -5
- package/src/runValidation.ts +25 -771
- package/src/utils/sourcePathToFileLocation.ts +1 -1
- package/src/__fixtures__/debug-snapshot/.val/patches/head/patch.json +0 -20
- package/src/utils/getFileExt.ts +0 -4
|
@@ -9,7 +9,7 @@ export default c.define(
|
|
|
9
9
|
image2: s.image(basicGallery2Val),
|
|
10
10
|
}),
|
|
11
11
|
{
|
|
12
|
-
image1:
|
|
13
|
-
image2:
|
|
12
|
+
image1: { path: "/public/val/images/image.png" },
|
|
13
|
+
image2: { path: "/public/val/images2/image.png" },
|
|
14
14
|
},
|
|
15
15
|
);
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { c, s } from "../val.config";
|
|
2
2
|
|
|
3
|
-
export default c.define(
|
|
4
|
-
"/
|
|
5
|
-
|
|
6
|
-
c.image("/public/val/image.png"),
|
|
7
|
-
);
|
|
3
|
+
export default c.define("/content/basic-image.val.ts", s.image(), {
|
|
4
|
+
path: "/public/val/image.png",
|
|
5
|
+
});
|
package/src/__fixtures__/debug-snapshot/.val/patches/11111111-1111-4111-8111-111111111111/patch.json
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"patch": [
|
|
3
3
|
{
|
|
4
|
-
"op": "
|
|
5
|
-
"path": ["BBL", "
|
|
4
|
+
"op": "replace",
|
|
5
|
+
"path": ["BBL", "title"],
|
|
6
|
+
"value": "BBL Housing"
|
|
6
7
|
}
|
|
7
8
|
],
|
|
8
|
-
"patchId": "
|
|
9
|
-
"parentRef": {
|
|
10
|
-
"type": "patch",
|
|
11
|
-
"patchId": "11111111-1111-4111-8111-111111111111"
|
|
12
|
-
},
|
|
9
|
+
"patchId": "11111111-1111-4111-8111-111111111111",
|
|
13
10
|
"path": "/content/projects.val.ts",
|
|
14
|
-
"authorId": "author-
|
|
11
|
+
"authorId": "author-a",
|
|
15
12
|
"sessionId": null,
|
|
16
13
|
"baseSha": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
17
14
|
"coreVersion": null,
|
|
18
|
-
"createdAt": "2026-08-
|
|
15
|
+
"createdAt": "2026-08-10T12:00:00.000Z"
|
|
19
16
|
}
|
package/src/__fixtures__/debug-snapshot/.val/patches/22222222-2222-4222-8222-222222222222/patch.json
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"patch": [
|
|
3
3
|
{
|
|
4
|
-
"op": "
|
|
5
|
-
"path": ["BBL", "summary", "services", "2"]
|
|
6
|
-
"value": "Development U"
|
|
4
|
+
"op": "remove",
|
|
5
|
+
"path": ["BBL", "summary", "services", "2"]
|
|
7
6
|
}
|
|
8
7
|
],
|
|
9
|
-
"patchId": "
|
|
10
|
-
"parentRef": {
|
|
11
|
-
"type": "patch",
|
|
12
|
-
"patchId": "22222222-2222-4222-8222-222222222222"
|
|
13
|
-
},
|
|
8
|
+
"patchId": "22222222-2222-4222-8222-222222222222",
|
|
14
9
|
"path": "/content/projects.val.ts",
|
|
15
|
-
"authorId": "author-
|
|
10
|
+
"authorId": "author-b",
|
|
16
11
|
"sessionId": null,
|
|
17
12
|
"baseSha": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
18
13
|
"coreVersion": null,
|
|
19
|
-
"createdAt": "2026-08-11T09:00:
|
|
14
|
+
"createdAt": "2026-08-11T09:00:00.000Z"
|
|
20
15
|
}
|
package/src/__fixtures__/debug-snapshot/.val/patches/33333333-3333-4333-8333-333333333333/patch.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"patch": [
|
|
3
|
+
{
|
|
4
|
+
"op": "replace",
|
|
5
|
+
"path": ["BBL", "summary", "services", "2"],
|
|
6
|
+
"value": "Development U"
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"patchId": "33333333-3333-4333-8333-333333333333",
|
|
10
|
+
"path": "/content/projects.val.ts",
|
|
11
|
+
"authorId": "author-a",
|
|
12
|
+
"sessionId": null,
|
|
13
|
+
"baseSha": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
14
|
+
"coreVersion": null,
|
|
15
|
+
"createdAt": "2026-08-11T09:00:05.000Z"
|
|
16
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
val-patch-log v1
|
|
2
|
+
11111111-1111-4111-8111-111111111111 2026-08-10T12:00:00.000Z /content/projects.val.ts
|
|
3
|
+
22222222-2222-4222-8222-222222222222 2026-08-11T09:00:00.000Z /content/projects.val.ts
|
|
4
|
+
33333333-3333-4333-8333-333333333333 2026-08-11T09:00:05.000Z /content/projects.val.ts
|
package/src/debug/snapshot.ts
CHANGED
|
@@ -22,8 +22,6 @@ export type SnapshotPatch = {
|
|
|
22
22
|
authorId: string | null;
|
|
23
23
|
baseSha: string;
|
|
24
24
|
appliedAt: { commitSha: string } | null;
|
|
25
|
-
/** Parent in the chain: null means it is the first ("head"). */
|
|
26
|
-
parentPatchId: PatchId | null;
|
|
27
25
|
patch: unknown;
|
|
28
26
|
};
|
|
29
27
|
|
|
@@ -199,9 +197,21 @@ export async function buildSnapshot(
|
|
|
199
197
|
for (const p of elided.elided) {
|
|
200
198
|
elidedPatchValues.push({ patchId: patch.patchId, path: p });
|
|
201
199
|
}
|
|
202
|
-
entries[`.val/patches/${patch.
|
|
203
|
-
|
|
200
|
+
entries[`.val/patches/${patch.patchId}/patch.json`] = JSON.stringify(
|
|
201
|
+
toFsPatch(patch),
|
|
202
|
+
null,
|
|
203
|
+
2,
|
|
204
|
+
);
|
|
204
205
|
}
|
|
206
|
+
// The order lives in the log, not in the records, so it has to be written too
|
|
207
|
+
// or the unzipped snapshot is a store with no order at all.
|
|
208
|
+
entries[".val/patches/patches.log"] = [
|
|
209
|
+
"val-patch-log v1",
|
|
210
|
+
...patches.map(
|
|
211
|
+
(patch) => `${patch.patchId} ${patch.createdAt} ${patch.path}`,
|
|
212
|
+
),
|
|
213
|
+
"",
|
|
214
|
+
].join("\n");
|
|
205
215
|
|
|
206
216
|
let includesBinaryFiles = false;
|
|
207
217
|
if (options.includeFiles) {
|
|
@@ -296,31 +306,29 @@ function toSnapshotPatches(
|
|
|
296
306
|
appliedAt: { commitSha: string } | null;
|
|
297
307
|
}[],
|
|
298
308
|
): SnapshotPatch[] {
|
|
299
|
-
return orderedPatches.map((patch
|
|
309
|
+
return orderedPatches.map((patch) => ({
|
|
300
310
|
patchId: patch.patchId,
|
|
301
311
|
path: patch.path,
|
|
302
312
|
createdAt: patch.createdAt,
|
|
303
313
|
authorId: patch.authorId,
|
|
304
314
|
baseSha: patch.baseSha,
|
|
305
315
|
appliedAt: patch.appliedAt,
|
|
306
|
-
parentPatchId: i === 0 ? null : orderedPatches[i - 1].patchId,
|
|
307
316
|
patch: patch.patch,
|
|
308
317
|
}));
|
|
309
318
|
}
|
|
310
319
|
|
|
311
320
|
/**
|
|
312
321
|
* The shape ValOpsFS writes per patch, so an unzipped snapshot is a patch store
|
|
313
|
-
* a plain ValOpsFS can read.
|
|
314
|
-
*
|
|
322
|
+
* a plain ValOpsFS can read.
|
|
323
|
+
*
|
|
324
|
+
* The directory is named after the patch itself and the record points at
|
|
325
|
+
* nothing: the order is in `patches.log` beside it. A record that named its
|
|
326
|
+
* parent is how a snapshot could carry a chain that had already lost a link.
|
|
315
327
|
*/
|
|
316
328
|
function toFsPatch(patch: SnapshotPatch) {
|
|
317
329
|
return {
|
|
318
330
|
patch: patch.patch,
|
|
319
331
|
patchId: patch.patchId,
|
|
320
|
-
parentRef:
|
|
321
|
-
patch.parentPatchId === null
|
|
322
|
-
? { type: "head", headBaseSha: patch.baseSha }
|
|
323
|
-
: { type: "patch", patchId: patch.parentPatchId },
|
|
324
332
|
path: patch.path,
|
|
325
333
|
authorId: patch.authorId,
|
|
326
334
|
sessionId: null,
|
|
@@ -339,16 +347,13 @@ async function writeBinaryFiles(
|
|
|
339
347
|
patches: SnapshotPatch[],
|
|
340
348
|
entries: Record<string, string>,
|
|
341
349
|
): Promise<boolean> {
|
|
342
|
-
const
|
|
343
|
-
patches.map((p) => [p.patchId, p.parentPatchId ?? "head"]),
|
|
344
|
-
);
|
|
350
|
+
const known = new Set(patches.map((p) => p.patchId));
|
|
345
351
|
let wrote = false;
|
|
346
352
|
for (const [filePath, data] of Object.entries(fileLastUpdatedByPatchId)) {
|
|
347
353
|
if (data.isDelete) {
|
|
348
354
|
continue;
|
|
349
355
|
}
|
|
350
|
-
|
|
351
|
-
if (parentPatchId === undefined) {
|
|
356
|
+
if (!known.has(data.patchId)) {
|
|
352
357
|
continue;
|
|
353
358
|
}
|
|
354
359
|
const buffer = await ctx.serverOps.getBase64EncodedBinaryFileFromPatch(
|
|
@@ -361,7 +366,7 @@ async function writeBinaryFiles(
|
|
|
361
366
|
}
|
|
362
367
|
// Base64 so the snapshot stays a text-only entry map; the replay decodes it.
|
|
363
368
|
entries[
|
|
364
|
-
`.val/patches/${
|
|
369
|
+
`.val/patches/${data.patchId}/files${filePath}/${path.posix.basename(filePath)}.base64`
|
|
365
370
|
] = buffer.toString("base64");
|
|
366
371
|
wrote = true;
|
|
367
372
|
}
|
package/src/debug.test.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
1
3
|
import path from "path";
|
|
2
4
|
import { ModuleFilePath } from "@valbuild/core";
|
|
5
|
+
import { readPatchStore } from "@valbuild/server";
|
|
3
6
|
import { createDebugContext } from "./debug/context";
|
|
4
7
|
import { buildSnapshot } from "./debug/snapshot";
|
|
5
8
|
|
|
@@ -71,25 +74,43 @@ describe("val debug snapshot", () => {
|
|
|
71
74
|
expect(entries["val.modules.original.ts"]).toContain("unrelated");
|
|
72
75
|
});
|
|
73
76
|
|
|
74
|
-
test("writes
|
|
77
|
+
test("writes a patch store ValOpsFS can actually read back", async () => {
|
|
75
78
|
const { entries } = await build();
|
|
76
79
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
80
|
+
// Materialise it and read it with the real store, rather than asserting on
|
|
81
|
+
// path strings: "the layout ValOpsFS reads back" is the property, and a
|
|
82
|
+
// snapshot that merely looks right is exactly what a debug tool must not
|
|
83
|
+
// produce.
|
|
84
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "val-snapshot-"));
|
|
85
|
+
for (const [name, content] of Object.entries(entries)) {
|
|
86
|
+
const file = path.join(dir, name);
|
|
87
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
88
|
+
fs.writeFileSync(file, content);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const read = readPatchStore(path.join(dir, ".val", "patches"));
|
|
92
|
+
if (read.status !== "ok") {
|
|
93
|
+
throw new Error(`expected ok, got ${read.status}`);
|
|
94
|
+
}
|
|
95
|
+
expect(read.problems).toEqual([]);
|
|
96
|
+
expect(read.entries.map((entry) => entry.patchId)).toEqual([
|
|
97
|
+
"11111111-1111-4111-8111-111111111111",
|
|
98
|
+
"22222222-2222-4222-8222-222222222222",
|
|
99
|
+
"33333333-3333-4333-8333-333333333333",
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
// Each record sits under its own id and points at nothing. A record that
|
|
103
|
+
// named its parent is how a snapshot could carry a chain that had already
|
|
104
|
+
// lost a link.
|
|
105
|
+
for (const entry of read.entries) {
|
|
106
|
+
const raw = JSON.parse(
|
|
107
|
+
entries[`.val/patches/${entry.patchId}/patch.json`],
|
|
108
|
+
);
|
|
109
|
+
expect(raw.patchId).toBe(entry.patchId);
|
|
110
|
+
expect(raw).not.toHaveProperty("parentRef");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
93
114
|
});
|
|
94
115
|
|
|
95
116
|
test("records why the snapshot may be incomplete rather than failing silently", async () => {
|
package/src/listUnusedFiles.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FILE_REF_PROP,
|
|
3
|
-
ModuleFilePath,
|
|
4
|
-
ModulePath,
|
|
5
|
-
SourcePath,
|
|
6
|
-
VAL_EXTENSION,
|
|
7
|
-
} from "@valbuild/core";
|
|
1
|
+
import { ModuleFilePath, ModulePath, SourcePath } from "@valbuild/core";
|
|
8
2
|
import { createService } from "@valbuild/server";
|
|
9
3
|
import { glob } from "fast-glob";
|
|
10
4
|
import path from "path";
|
|
@@ -49,7 +43,7 @@ export async function listUnusedFiles({ root }: { root?: string }) {
|
|
|
49
43
|
if (isFileRef(value)) {
|
|
50
44
|
const absoluteFilePathUsedByVal = path.join(
|
|
51
45
|
projectRoot,
|
|
52
|
-
...value
|
|
46
|
+
...value.path.split("/"),
|
|
53
47
|
);
|
|
54
48
|
filesUsedByVal.push(absoluteFilePathUsedByVal);
|
|
55
49
|
}
|
|
@@ -78,18 +72,17 @@ export async function listUnusedFiles({ root }: { root?: string }) {
|
|
|
78
72
|
return;
|
|
79
73
|
}
|
|
80
74
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return false;
|
|
75
|
+
/**
|
|
76
|
+
* Whether a flagged validation value names a file.
|
|
77
|
+
*
|
|
78
|
+
* A `ValidationError` carries no schema, so the shape is all there is to go on —
|
|
79
|
+
* the same heuristic this whole function documents as a TODO.
|
|
80
|
+
*/
|
|
81
|
+
function isFileRef(value: unknown): value is { path: string } {
|
|
82
|
+
return (
|
|
83
|
+
!!value &&
|
|
84
|
+
typeof value === "object" &&
|
|
85
|
+
"path" in value &&
|
|
86
|
+
typeof value.path === "string"
|
|
87
|
+
);
|
|
95
88
|
}
|
|
@@ -372,12 +372,12 @@ describe("runValidation", () => {
|
|
|
372
372
|
.flatMap((e) => e.fixes ?? []);
|
|
373
373
|
expect(allFixes).not.toContain("image:add-metadata");
|
|
374
374
|
}
|
|
375
|
+
// The fix writes the fields it read from the bytes next to `path`.
|
|
375
376
|
expect(result.source).toMatchObject({
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
},
|
|
377
|
+
path: "/public/val/image.png",
|
|
378
|
+
width: 1,
|
|
379
|
+
height: 1,
|
|
380
|
+
mimeType: "image/png",
|
|
381
381
|
});
|
|
382
382
|
} finally {
|
|
383
383
|
service.dispose();
|