@valbuild/cli 0.108.3 → 0.108.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/__fixtures__/basic/app/jobb/[slug]/page/jobb/student.val.json +6 -0
- package/src/__fixtures__/basic/app/jobb/[slug]/page.val.ts +20 -0
- package/src/__fixtures__/basic/content/basic-json-values-image.val.ts +16 -0
- package/src/__fixtures__/basic/content/json-entries/with-image.val.json +6 -0
- package/src/__fixtures__/basic/val.config.ts +6 -2
- package/src/__fixtures__/basic/val.modules.ts +2 -0
- package/src/runValidation.test.ts +124 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valbuild/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.108.
|
|
4
|
+
"version": "0.108.5",
|
|
5
5
|
"description": "Val CLI tools",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"jszip": "^3.10.1",
|
|
25
25
|
"meow": "^9.0.0",
|
|
26
26
|
"picocolors": "^1.1.1",
|
|
27
|
-
"@valbuild/
|
|
27
|
+
"@valbuild/eslint-plugin": "0.108.0",
|
|
28
|
+
"@valbuild/server": "0.108.5",
|
|
28
29
|
"@valbuild/core": "0.106.0",
|
|
29
|
-
"@valbuild/server": "0.108.
|
|
30
|
-
"@valbuild/shared": "0.108.0"
|
|
31
|
-
"@valbuild/eslint-plugin": "0.108.0"
|
|
30
|
+
"@valbuild/language-server": "0.108.5",
|
|
31
|
+
"@valbuild/shared": "0.108.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"prettier": "*",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { c, nextAppRouter, s } from "../../../val.config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The shape the `.jsonValues()` path bug was reported on: a ROUTER whose entry
|
|
5
|
+
* content — including an `s.image()` — lives in a `*.val.json` next to the
|
|
6
|
+
* module. A router serializes as a record, so it takes the same jsonValues code
|
|
7
|
+
* path, but nothing pinned that until this fixture.
|
|
8
|
+
*/
|
|
9
|
+
export default c.define(
|
|
10
|
+
"/app/jobb/[slug]/page.val.ts",
|
|
11
|
+
s
|
|
12
|
+
.router(
|
|
13
|
+
nextAppRouter,
|
|
14
|
+
s.object({ header: s.string(), pageImage: s.image() }),
|
|
15
|
+
)
|
|
16
|
+
.jsonValues(),
|
|
17
|
+
{
|
|
18
|
+
"/jobb/student": c.json(() => import("./page/jobb/student.val.json")),
|
|
19
|
+
},
|
|
20
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { c, s } from "../val.config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A `.jsonValues()` record whose entry content contains an `s.image()`. Image
|
|
5
|
+
* metadata can only be checked by reading the bytes, so validating an image
|
|
6
|
+
* ALWAYS produces a fix — which means every fix handler has to cope with a
|
|
7
|
+
* source path that points inside an entry whose content is not in the
|
|
8
|
+
* `.val.ts`.
|
|
9
|
+
*/
|
|
10
|
+
export default c.define(
|
|
11
|
+
"/content/basic-json-values-image.val.ts",
|
|
12
|
+
s.record(s.object({ title: s.string(), image: s.image() })).jsonValues(),
|
|
13
|
+
{
|
|
14
|
+
"/with-image": c.json(() => import("./json-entries/with-image.val.json")),
|
|
15
|
+
},
|
|
16
|
+
);
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { initVal } from "@valbuild/core";
|
|
1
|
+
import { initVal, Internal } from "@valbuild/core";
|
|
2
2
|
|
|
3
3
|
// NOTE: initVal() must be called with an options object for `config` to be
|
|
4
4
|
// defined - the CLI requires val.config to export a config object.
|
|
5
5
|
const { s, c, config } = initVal({});
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// `@valbuild/next` is what a real project takes its routers from; this fixture
|
|
8
|
+
// only depends on core, so it reads the same router off `Internal`.
|
|
9
|
+
const { nextAppRouter } = Internal;
|
|
10
|
+
|
|
11
|
+
export { s, c, config, nextAppRouter };
|
|
@@ -16,6 +16,8 @@ export default modules(config, [
|
|
|
16
16
|
{ def: () => import("./content/basic-gallery-remote.val") },
|
|
17
17
|
{ def: () => import("./content/basic-gallery-remote-existing.val") },
|
|
18
18
|
{ def: () => import("./content/basic-json-values.val") },
|
|
19
|
+
{ def: () => import("./content/basic-json-values-image.val") },
|
|
20
|
+
{ def: () => import("./app/jobb/[slug]/page.val") },
|
|
19
21
|
{ def: () => import("./content/basic-nested-json-values.val") },
|
|
20
22
|
{ def: () => import("./content/basic-inline-json-values.val") },
|
|
21
23
|
{ def: () => import("./content/basic-inline-json-values-invalid.val") },
|
|
@@ -735,6 +735,130 @@ describe("runValidation", () => {
|
|
|
735
735
|
expect(events.at(-1)).toEqual({ type: "summary-errors", count: 2 });
|
|
736
736
|
});
|
|
737
737
|
|
|
738
|
+
test("reports a fixable error INSIDE an entry instead of throwing", async () => {
|
|
739
|
+
// Regression: every image validation ends in a fix (whether the stored
|
|
740
|
+
// dimensions match the bytes can only be answered by reading the file), and
|
|
741
|
+
// the fix handlers resolved the reported path against the module source —
|
|
742
|
+
// which for a jsonValues module holds a `c.json(...)` marker where the
|
|
743
|
+
// entry content should be. `Internal.resolvePath` refuses to walk into a
|
|
744
|
+
// marker, so a single `s.image()` inside an entry aborted the whole run
|
|
745
|
+
// with "Cannot resolve path into a jsonValues entry until its content is
|
|
746
|
+
// loaded" — no report, no exit code, nothing fixable.
|
|
747
|
+
const events = await runOn(["content/basic-json-values-image.val.ts"]);
|
|
748
|
+
|
|
749
|
+
expect(events).toEqual(
|
|
750
|
+
expect.arrayContaining([
|
|
751
|
+
expect.objectContaining({
|
|
752
|
+
type: "validation-fixable-error",
|
|
753
|
+
sourcePath:
|
|
754
|
+
'/content/basic-json-values-image.val.ts?p="/with-image"."image"',
|
|
755
|
+
fixable: true,
|
|
756
|
+
}),
|
|
757
|
+
]),
|
|
758
|
+
);
|
|
759
|
+
expect(events.at(-1)).toEqual({ type: "summary-errors", count: 1 });
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
test("--fix writes the entry's metadata into its *.val.json", async () => {
|
|
763
|
+
const events: ValidationEvent[] = [];
|
|
764
|
+
for await (const event of runValidation({
|
|
765
|
+
root: tmpDir,
|
|
766
|
+
fix: true,
|
|
767
|
+
valFiles: ["content/basic-json-values-image.val.ts"],
|
|
768
|
+
project: undefined,
|
|
769
|
+
remote: mockRemote,
|
|
770
|
+
fs: createDefaultValFSHost(),
|
|
771
|
+
})) {
|
|
772
|
+
events.push(event);
|
|
773
|
+
}
|
|
774
|
+
expect(events).toEqual(
|
|
775
|
+
expect.arrayContaining([
|
|
776
|
+
expect.objectContaining({
|
|
777
|
+
type: "fix-applied",
|
|
778
|
+
sourcePath:
|
|
779
|
+
'/content/basic-json-values-image.val.ts?p="/with-image"."image"',
|
|
780
|
+
}),
|
|
781
|
+
]),
|
|
782
|
+
);
|
|
783
|
+
|
|
784
|
+
// The metadata belongs in the entry's own file: the `.val.ts` has no place
|
|
785
|
+
// to put it, and a patch applied there would either fail or corrupt the
|
|
786
|
+
// `c.json(...)` reference.
|
|
787
|
+
const jsonPath = path.join(
|
|
788
|
+
tmpDir,
|
|
789
|
+
"content/json-entries/with-image.val.json",
|
|
790
|
+
);
|
|
791
|
+
expect(JSON.parse(fs.readFileSync(jsonPath, "utf8"))).toEqual({
|
|
792
|
+
title: "An entry with an image",
|
|
793
|
+
image: {
|
|
794
|
+
path: "/public/val/image.png",
|
|
795
|
+
width: 1,
|
|
796
|
+
height: 1,
|
|
797
|
+
mimeType: "image/png",
|
|
798
|
+
},
|
|
799
|
+
});
|
|
800
|
+
const valTs = fs.readFileSync(
|
|
801
|
+
path.join(tmpDir, "content/basic-json-values-image.val.ts"),
|
|
802
|
+
"utf8",
|
|
803
|
+
);
|
|
804
|
+
expect(valTs).toContain(
|
|
805
|
+
'c.json(() => import("./json-entries/with-image.val.json"))',
|
|
806
|
+
);
|
|
807
|
+
expect(valTs).not.toContain("mimeType");
|
|
808
|
+
|
|
809
|
+
// The bug the user hit was the SECOND run: fixing once and then failing
|
|
810
|
+
// forever is the same as never fixing at all.
|
|
811
|
+
const afterFix = await runOn(["content/basic-json-values-image.val.ts"]);
|
|
812
|
+
expect(afterFix.at(-1)).toEqual({ type: "summary-success" });
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
test("--fix reaches into a ROUTER's entry file too", async () => {
|
|
816
|
+
// The reported bug was on a router (`s.router(nextAppRouter, ...).jsonValues()`),
|
|
817
|
+
// not a plain record. A router serializes as a record so it takes the same
|
|
818
|
+
// path, but nothing covered it — and a router's entry keys are URL paths,
|
|
819
|
+
// which is what the failing report showed: `"/jobb/student"."pageImage"`.
|
|
820
|
+
const before = await runOn(["app/jobb/[slug]/page.val.ts"]);
|
|
821
|
+
expect(before).toEqual(
|
|
822
|
+
expect.arrayContaining([
|
|
823
|
+
expect.objectContaining({
|
|
824
|
+
type: "validation-fixable-error",
|
|
825
|
+
sourcePath:
|
|
826
|
+
'/app/jobb/[slug]/page.val.ts?p="/jobb/student"."pageImage"',
|
|
827
|
+
}),
|
|
828
|
+
]),
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
const events: ValidationEvent[] = [];
|
|
832
|
+
for await (const event of runValidation({
|
|
833
|
+
root: tmpDir,
|
|
834
|
+
fix: true,
|
|
835
|
+
valFiles: ["app/jobb/[slug]/page.val.ts"],
|
|
836
|
+
project: undefined,
|
|
837
|
+
remote: mockRemote,
|
|
838
|
+
fs: createDefaultValFSHost(),
|
|
839
|
+
})) {
|
|
840
|
+
events.push(event);
|
|
841
|
+
}
|
|
842
|
+
expect(events.at(-1)).toEqual({ type: "summary-success" });
|
|
843
|
+
|
|
844
|
+
const jsonPath = path.join(
|
|
845
|
+
tmpDir,
|
|
846
|
+
"app/jobb/[slug]/page/jobb/student.val.json",
|
|
847
|
+
);
|
|
848
|
+
expect(JSON.parse(fs.readFileSync(jsonPath, "utf8"))).toEqual({
|
|
849
|
+
header: "Student",
|
|
850
|
+
pageImage: {
|
|
851
|
+
path: "/public/val/image.png",
|
|
852
|
+
width: 1,
|
|
853
|
+
height: 1,
|
|
854
|
+
mimeType: "image/png",
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
expect((await runOn(["app/jobb/[slug]/page.val.ts"])).at(-1)).toEqual({
|
|
858
|
+
type: "summary-success",
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
|
|
738
862
|
test("rejects a nested .jsonValues() instead of reporting it valid", async () => {
|
|
739
863
|
// Root-only is a hard contract: a nested one would silently get NO content
|
|
740
864
|
// validation. The Studio refuses to load such a project, so the CLI saying
|