@valbuild/cli 0.143.0 → 0.144.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/CHANGELOG.md +43 -0
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +12 -6
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +12 -6
- package/cli/dist/valbuild-cli-cli.esm.js +12 -6
- package/package.json +7 -7
- package/src/__fixtures__/basic/content/basic-files.val.ts +1 -4
- package/src/__fixtures__/basic/content/basic-fixable-and-unfixable.val.ts +12 -0
- package/src/__fixtures__/basic/content/basic-gallery-2.val.ts +1 -4
- package/src/__fixtures__/basic/content/basic-gallery-fail-on-non-unique-dir.val.ts +1 -4
- package/src/__fixtures__/basic/content/basic-gallery-missing-tracked.val.ts +1 -4
- package/src/__fixtures__/basic/content/basic-gallery-remote-existing.val.ts +1 -6
- package/src/__fixtures__/basic/content/basic-gallery-remote.val.ts +1 -6
- package/src/__fixtures__/basic/content/basic-gallery-wrong-metadata.val.ts +1 -4
- package/src/__fixtures__/basic/content/basic-gallery.val.ts +1 -4
- package/src/__fixtures__/basic/val.modules.ts +1 -0
- package/src/listUnusedFiles.ts +1 -1
- package/src/publish/buildArtifacts.ts +5 -0
- package/src/runValidation.imageset.test.ts +340 -0
- package/src/runValidation.test.ts +46 -0
- package/src/runValidation.ts +6 -4
- package/src/runValidation.videoset.test.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @valbuild/cli
|
|
2
2
|
|
|
3
|
+
## 0.144.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#788](https://github.com/valbuild/val/pull/788) [`a84271c`](https://github.com/valbuild/val/commit/a84271c534822071c1e428cf9990602459682d25) Thanks [@freekh](https://github.com/freekh)! - Fixes to `npx val validate` for `s.imagedir()` and `s.filedir()`, and the `s.image(galleryVal)` / `s.file(filesVal)` fields that use them:
|
|
8
|
+
|
|
9
|
+
- **`--fix` now updates every field that uses an uploaded image or file.** When `--fix` moves an entry of an `s.imagedir(dir).remote()` or `s.filedir(dir, options).remote()` to Val Remote, the entry's key changes from the local path to the remote URL. Fields that held the old path were left pointing at it, and reported "The gallery does not have an image at …" with nothing to fix it. Now `s.image(galleryVal)` and `s.file(filesVal)` fields, and images in rich text (`s.richtext({ img: s.image(galleryVal) })`), are updated in the same run. The VS Code extension's "upload to Val Remote" updates them in the same edit, including fields inside `.jsonValues()` entries, which live in their own `*.val.json` files.
|
|
10
|
+
- **A field that uses an image or file from a `.remote()` gallery is no longer reported as "Expected local image, but found remote".** The gallery decides whether its files are on Val Remote; the field only has to name a file the gallery has. Before, `--fix` would download the file and point the field at a path the gallery did not have.
|
|
11
|
+
- **Images uploaded through a field into a `.remote()` gallery.** When you upload an image through an `s.image(galleryVal)` field in Val Studio, the field stores the remote URL and the gallery stores the image under its local path. Validation now accepts this, and `useVal` / `fetchVal` now fill in the image's `width`, `height` and `mimeType` from the gallery for it, as they do for every other gallery image.
|
|
12
|
+
- **Errors in a gallery's keys are reported.** A remote URL in a gallery that is not `.remote()`, a remote URL that is not valid, or one outside the gallery's `dir` was silently ignored by `npx val validate`. It is now reported as an error. `--fix` does not change it: you decide whether the key or the gallery's options are wrong.
|
|
13
|
+
- **A file is no longer reported as valid when it still has an error.** When `--fix` fixed one error in a file and another error was left, the file could be printed as "✔ valid".
|
|
14
|
+
|
|
15
|
+
- [#827](https://github.com/valbuild/val/pull/827) [`1272b14`](https://github.com/valbuild/val/commit/1272b1408cf55fefccdce442352c208a7f69f44b) Thanks [@freekh](https://github.com/freekh)! - Publishing to Val's platform, through `val publish` or the Studio, now handles a stylesheet that imports other stylesheets, and fonts installed as packages:
|
|
16
|
+
|
|
17
|
+
```css
|
|
18
|
+
/* src/styles.css */
|
|
19
|
+
@import "tailwindcss";
|
|
20
|
+
@import "./theme/theme.css";
|
|
21
|
+
|
|
22
|
+
/* src/theme/theme.css */
|
|
23
|
+
@import "@fontsource-variable/inter";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Before this, the platform's build passed `@import "./theme/theme.css"` on to Tailwind without resolving it, and publishing stopped with `Tailwind asked for './theme/theme.css', which this build does not carry`. A font package such as `@fontsource-variable/inter` was built as code, failed, and was skipped.
|
|
27
|
+
|
|
28
|
+
- A stylesheet's own `@import`s are inlined where they were written, the same way `vite build` handles them. That covers relative files and stylesheets that dependencies ship. `layer()`, `supports()` and media conditions are kept, and a stylesheet imported twice is only included once.
|
|
29
|
+
- The files a dependency's stylesheet points at with `url(...)`, such as a font package's `.woff2` files, are published with the site and served from it.
|
|
30
|
+
|
|
31
|
+
- Updated dependencies [[`a84271c`](https://github.com/valbuild/val/commit/a84271c534822071c1e428cf9990602459682d25), [`860ab2b`](https://github.com/valbuild/val/commit/860ab2b87333daf95a48215b0e4a1b44f441c878), [`1272b14`](https://github.com/valbuild/val/commit/1272b1408cf55fefccdce442352c208a7f69f44b)]:
|
|
32
|
+
- @valbuild/core@0.144.0
|
|
33
|
+
- @valbuild/shared@0.144.0
|
|
34
|
+
- @valbuild/server@0.144.0
|
|
35
|
+
- @valbuild/language-server@0.144.0
|
|
36
|
+
- @valbuild/tanstack-build@0.144.0
|
|
37
|
+
|
|
38
|
+
## 0.143.1
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- Updated dependencies []:
|
|
43
|
+
- @valbuild/server@0.143.1
|
|
44
|
+
- @valbuild/language-server@0.143.1
|
|
45
|
+
|
|
3
46
|
## 0.143.0
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
|
@@ -243,8 +243,9 @@ async function* runValidation({
|
|
|
243
243
|
};
|
|
244
244
|
return;
|
|
245
245
|
} else {
|
|
246
|
+
// What is still wrong with this module once every handler has run: an
|
|
247
|
+
// error that was fixed is not counted, one that was not is.
|
|
246
248
|
let fileErrors = 0;
|
|
247
|
-
let fixedErrors = 0;
|
|
248
249
|
if (valModule.errors) {
|
|
249
250
|
if (valModule.errors.validation) {
|
|
250
251
|
// Resolve schema/source fixes (keyof:check-keys, router:check-route)
|
|
@@ -331,7 +332,6 @@ async function* runValidation({
|
|
|
331
332
|
continue;
|
|
332
333
|
}
|
|
333
334
|
if (result.appliedFix) {
|
|
334
|
-
fixedErrors += 1;
|
|
335
335
|
yield {
|
|
336
336
|
type: "fix-applied",
|
|
337
337
|
file,
|
|
@@ -371,7 +371,6 @@ async function* runValidation({
|
|
|
371
371
|
sourcePath: other.moduleFilePath
|
|
372
372
|
};
|
|
373
373
|
}
|
|
374
|
-
fixedErrors += 1;
|
|
375
374
|
yield {
|
|
376
375
|
type: "fix-applied",
|
|
377
376
|
file,
|
|
@@ -407,7 +406,10 @@ async function* runValidation({
|
|
|
407
406
|
}
|
|
408
407
|
}
|
|
409
408
|
}
|
|
410
|
-
|
|
409
|
+
// Valid means nothing is left, not that as many errors were fixed as
|
|
410
|
+
// are left: one fix and one error that no fix touches add up to the
|
|
411
|
+
// same count, and printed a module with an error in it as valid.
|
|
412
|
+
if (fileErrors === 0 && (!valModule.errors.fatal || valModule.errors.fatal.length == 0)) {
|
|
411
413
|
yield {
|
|
412
414
|
type: "file-valid",
|
|
413
415
|
file: moduleFilePath,
|
|
@@ -1029,7 +1031,7 @@ async function listUnusedFiles({
|
|
|
1029
1031
|
}
|
|
1030
1032
|
});
|
|
1031
1033
|
}
|
|
1032
|
-
// A media collection (`s.
|
|
1034
|
+
// A media collection (`s.imagedir()`, `s.filedir()`, `s.videodir()`) is
|
|
1033
1035
|
// keyed by its files, and an entry that is fine reports no error either —
|
|
1034
1036
|
// so, as for a video, they are found by walking the source. A video set's
|
|
1035
1037
|
// stream holds every playlist and segment its master names, and an entry
|
|
@@ -2616,7 +2618,10 @@ async function buildArtifacts(options) {
|
|
|
2616
2618
|
}
|
|
2617
2619
|
})();
|
|
2618
2620
|
const deps = node.layerDepsOf(manifest, sources);
|
|
2619
|
-
|
|
2621
|
+
// The files those stylesheets point at -- a font package's `.woff2`s --
|
|
2622
|
+
// published with the site under the names their `url()`s now use.
|
|
2623
|
+
const dependencyAssets = {};
|
|
2624
|
+
const css = await node.collectDependencyCss(root, node.cssSpecifiersOf(sources), dependencyAssets);
|
|
2620
2625
|
const layer = await node.buildVendorLayer(root, deps, css, {
|
|
2621
2626
|
base: target.base,
|
|
2622
2627
|
// A project on the platform layers its own copies; only the platform's
|
|
@@ -2644,6 +2649,7 @@ async function buildArtifacts(options) {
|
|
|
2644
2649
|
env: publicEnv(options.env ?? process.env),
|
|
2645
2650
|
publicFiles: readPublicFiles(root),
|
|
2646
2651
|
assets,
|
|
2652
|
+
dependencyAssets,
|
|
2647
2653
|
// The project's own files, without the generated tree or the
|
|
2648
2654
|
// stylesheets: what the next publish -- the Studio's included -- starts
|
|
2649
2655
|
// from. See `withoutStylesheets`.
|
|
@@ -243,8 +243,9 @@ async function* runValidation({
|
|
|
243
243
|
};
|
|
244
244
|
return;
|
|
245
245
|
} else {
|
|
246
|
+
// What is still wrong with this module once every handler has run: an
|
|
247
|
+
// error that was fixed is not counted, one that was not is.
|
|
246
248
|
let fileErrors = 0;
|
|
247
|
-
let fixedErrors = 0;
|
|
248
249
|
if (valModule.errors) {
|
|
249
250
|
if (valModule.errors.validation) {
|
|
250
251
|
// Resolve schema/source fixes (keyof:check-keys, router:check-route)
|
|
@@ -331,7 +332,6 @@ async function* runValidation({
|
|
|
331
332
|
continue;
|
|
332
333
|
}
|
|
333
334
|
if (result.appliedFix) {
|
|
334
|
-
fixedErrors += 1;
|
|
335
335
|
yield {
|
|
336
336
|
type: "fix-applied",
|
|
337
337
|
file,
|
|
@@ -371,7 +371,6 @@ async function* runValidation({
|
|
|
371
371
|
sourcePath: other.moduleFilePath
|
|
372
372
|
};
|
|
373
373
|
}
|
|
374
|
-
fixedErrors += 1;
|
|
375
374
|
yield {
|
|
376
375
|
type: "fix-applied",
|
|
377
376
|
file,
|
|
@@ -407,7 +406,10 @@ async function* runValidation({
|
|
|
407
406
|
}
|
|
408
407
|
}
|
|
409
408
|
}
|
|
410
|
-
|
|
409
|
+
// Valid means nothing is left, not that as many errors were fixed as
|
|
410
|
+
// are left: one fix and one error that no fix touches add up to the
|
|
411
|
+
// same count, and printed a module with an error in it as valid.
|
|
412
|
+
if (fileErrors === 0 && (!valModule.errors.fatal || valModule.errors.fatal.length == 0)) {
|
|
411
413
|
yield {
|
|
412
414
|
type: "file-valid",
|
|
413
415
|
file: moduleFilePath,
|
|
@@ -1029,7 +1031,7 @@ async function listUnusedFiles({
|
|
|
1029
1031
|
}
|
|
1030
1032
|
});
|
|
1031
1033
|
}
|
|
1032
|
-
// A media collection (`s.
|
|
1034
|
+
// A media collection (`s.imagedir()`, `s.filedir()`, `s.videodir()`) is
|
|
1033
1035
|
// keyed by its files, and an entry that is fine reports no error either —
|
|
1034
1036
|
// so, as for a video, they are found by walking the source. A video set's
|
|
1035
1037
|
// stream holds every playlist and segment its master names, and an entry
|
|
@@ -2616,7 +2618,10 @@ async function buildArtifacts(options) {
|
|
|
2616
2618
|
}
|
|
2617
2619
|
})();
|
|
2618
2620
|
const deps = node.layerDepsOf(manifest, sources);
|
|
2619
|
-
|
|
2621
|
+
// The files those stylesheets point at -- a font package's `.woff2`s --
|
|
2622
|
+
// published with the site under the names their `url()`s now use.
|
|
2623
|
+
const dependencyAssets = {};
|
|
2624
|
+
const css = await node.collectDependencyCss(root, node.cssSpecifiersOf(sources), dependencyAssets);
|
|
2620
2625
|
const layer = await node.buildVendorLayer(root, deps, css, {
|
|
2621
2626
|
base: target.base,
|
|
2622
2627
|
// A project on the platform layers its own copies; only the platform's
|
|
@@ -2644,6 +2649,7 @@ async function buildArtifacts(options) {
|
|
|
2644
2649
|
env: publicEnv(options.env ?? process.env),
|
|
2645
2650
|
publicFiles: readPublicFiles(root),
|
|
2646
2651
|
assets,
|
|
2652
|
+
dependencyAssets,
|
|
2647
2653
|
// The project's own files, without the generated tree or the
|
|
2648
2654
|
// stylesheets: what the next publish -- the Studio's included -- starts
|
|
2649
2655
|
// from. See `withoutStylesheets`.
|
|
@@ -208,8 +208,9 @@ async function* runValidation({
|
|
|
208
208
|
};
|
|
209
209
|
return;
|
|
210
210
|
} else {
|
|
211
|
+
// What is still wrong with this module once every handler has run: an
|
|
212
|
+
// error that was fixed is not counted, one that was not is.
|
|
211
213
|
let fileErrors = 0;
|
|
212
|
-
let fixedErrors = 0;
|
|
213
214
|
if (valModule.errors) {
|
|
214
215
|
if (valModule.errors.validation) {
|
|
215
216
|
// Resolve schema/source fixes (keyof:check-keys, router:check-route)
|
|
@@ -296,7 +297,6 @@ async function* runValidation({
|
|
|
296
297
|
continue;
|
|
297
298
|
}
|
|
298
299
|
if (result.appliedFix) {
|
|
299
|
-
fixedErrors += 1;
|
|
300
300
|
yield {
|
|
301
301
|
type: "fix-applied",
|
|
302
302
|
file,
|
|
@@ -336,7 +336,6 @@ async function* runValidation({
|
|
|
336
336
|
sourcePath: other.moduleFilePath
|
|
337
337
|
};
|
|
338
338
|
}
|
|
339
|
-
fixedErrors += 1;
|
|
340
339
|
yield {
|
|
341
340
|
type: "fix-applied",
|
|
342
341
|
file,
|
|
@@ -372,7 +371,10 @@ async function* runValidation({
|
|
|
372
371
|
}
|
|
373
372
|
}
|
|
374
373
|
}
|
|
375
|
-
|
|
374
|
+
// Valid means nothing is left, not that as many errors were fixed as
|
|
375
|
+
// are left: one fix and one error that no fix touches add up to the
|
|
376
|
+
// same count, and printed a module with an error in it as valid.
|
|
377
|
+
if (fileErrors === 0 && (!valModule.errors.fatal || valModule.errors.fatal.length == 0)) {
|
|
376
378
|
yield {
|
|
377
379
|
type: "file-valid",
|
|
378
380
|
file: moduleFilePath,
|
|
@@ -994,7 +996,7 @@ async function listUnusedFiles({
|
|
|
994
996
|
}
|
|
995
997
|
});
|
|
996
998
|
}
|
|
997
|
-
// A media collection (`s.
|
|
999
|
+
// A media collection (`s.imagedir()`, `s.filedir()`, `s.videodir()`) is
|
|
998
1000
|
// keyed by its files, and an entry that is fine reports no error either —
|
|
999
1001
|
// so, as for a video, they are found by walking the source. A video set's
|
|
1000
1002
|
// stream holds every playlist and segment its master names, and an entry
|
|
@@ -2581,7 +2583,10 @@ async function buildArtifacts(options) {
|
|
|
2581
2583
|
}
|
|
2582
2584
|
})();
|
|
2583
2585
|
const deps = node.layerDepsOf(manifest, sources);
|
|
2584
|
-
|
|
2586
|
+
// The files those stylesheets point at -- a font package's `.woff2`s --
|
|
2587
|
+
// published with the site under the names their `url()`s now use.
|
|
2588
|
+
const dependencyAssets = {};
|
|
2589
|
+
const css = await node.collectDependencyCss(root, node.cssSpecifiersOf(sources), dependencyAssets);
|
|
2585
2590
|
const layer = await node.buildVendorLayer(root, deps, css, {
|
|
2586
2591
|
base: target.base,
|
|
2587
2592
|
// A project on the platform layers its own copies; only the platform's
|
|
@@ -2609,6 +2614,7 @@ async function buildArtifacts(options) {
|
|
|
2609
2614
|
env: publicEnv(options.env ?? process.env),
|
|
2610
2615
|
publicFiles: readPublicFiles(root),
|
|
2611
2616
|
assets,
|
|
2617
|
+
dependencyAssets,
|
|
2612
2618
|
// The project's own files, without the generated tree or the
|
|
2613
2619
|
// stylesheets: what the next publish -- the Studio's included -- starts
|
|
2614
2620
|
// from. See `withoutStylesheets`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valbuild/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.144.0",
|
|
5
5
|
"description": "Val CLI tools",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"picocolors": "^1.1.1",
|
|
27
27
|
"zod": "^4.4.3",
|
|
28
28
|
"zod-validation-error": "^5.0.0",
|
|
29
|
-
"@valbuild/core": "0.143.0",
|
|
30
29
|
"@valbuild/eslint-plugin": "0.125.0",
|
|
31
|
-
"@valbuild/language-server": "0.
|
|
32
|
-
"@valbuild/
|
|
33
|
-
"@valbuild/
|
|
30
|
+
"@valbuild/language-server": "0.144.0",
|
|
31
|
+
"@valbuild/shared": "0.144.0",
|
|
32
|
+
"@valbuild/server": "0.144.0",
|
|
33
|
+
"@valbuild/core": "0.144.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"prettier": "*",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"rolldown": ">=1.2.5",
|
|
39
39
|
"@tanstack/router-generator": ">=1.167.0",
|
|
40
40
|
"@tanstack/router-plugin": ">=1.168.0",
|
|
41
|
-
"@valbuild/tanstack-build": "^0.
|
|
41
|
+
"@valbuild/tanstack-build": "^0.144.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@valbuild/tanstack-build": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"prettier": "~3.8.5",
|
|
71
71
|
"rolldown": "^1.2.5",
|
|
72
72
|
"tsx": "^4.23.12",
|
|
73
|
-
"@valbuild/tanstack-build": "0.
|
|
73
|
+
"@valbuild/tanstack-build": "0.144.0"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"start": "tsx src/cli.ts --",
|
|
@@ -2,10 +2,7 @@ import { c, s } from "../val.config";
|
|
|
2
2
|
|
|
3
3
|
export default c.define(
|
|
4
4
|
"/content/basic-files.val.ts",
|
|
5
|
-
s.
|
|
6
|
-
dir: "/public/val/files",
|
|
7
|
-
accept: "*/*",
|
|
8
|
-
}),
|
|
5
|
+
s.filedir("/public/val/files", { accept: "*/*" }),
|
|
9
6
|
{
|
|
10
7
|
"/public/val/files/tracked.txt": {
|
|
11
8
|
mimeType: "text/plain",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { c, s } from "../val.config";
|
|
2
|
+
|
|
3
|
+
// One error `--fix` repairs (the image's metadata) and one it cannot (the
|
|
4
|
+
// title is too short), in the same module.
|
|
5
|
+
export default c.define(
|
|
6
|
+
"/content/basic-fixable-and-unfixable.val.ts",
|
|
7
|
+
s.object({ image: s.image(), title: s.string().minLength(30) }),
|
|
8
|
+
{
|
|
9
|
+
image: { path: "/public/val/image.png" },
|
|
10
|
+
title: "Too short",
|
|
11
|
+
},
|
|
12
|
+
);
|
|
@@ -2,10 +2,7 @@ import { c, s } from "../val.config";
|
|
|
2
2
|
|
|
3
3
|
export default c.define(
|
|
4
4
|
"/content/basic-gallery-2.val.ts",
|
|
5
|
-
s.
|
|
6
|
-
dir: "/public/val/images2",
|
|
7
|
-
accept: "image/*",
|
|
8
|
-
}),
|
|
5
|
+
s.imagedir("/public/val/images2", { accept: "image/*" }),
|
|
9
6
|
{
|
|
10
7
|
"/public/val/images2/image.png": {
|
|
11
8
|
width: 1,
|
|
@@ -2,10 +2,7 @@ import { c, s } from "../val.config";
|
|
|
2
2
|
|
|
3
3
|
export default c.define(
|
|
4
4
|
"/content/basic-gallery-fail-on-non-unique-dir.val.ts",
|
|
5
|
-
s.
|
|
6
|
-
dir: "/public/val/images",
|
|
7
|
-
accept: "image/*",
|
|
8
|
-
}),
|
|
5
|
+
s.imagedir("/public/val/images", { accept: "image/*" }),
|
|
9
6
|
{
|
|
10
7
|
"/public/val/images/image.png": {
|
|
11
8
|
width: 1,
|
|
@@ -2,10 +2,7 @@ import { c, s } from "../val.config";
|
|
|
2
2
|
|
|
3
3
|
export default c.define(
|
|
4
4
|
"/content/basic-gallery-missing-tracked.val.ts",
|
|
5
|
-
s.
|
|
6
|
-
dir: "/public/val/images4",
|
|
7
|
-
accept: "image/*",
|
|
8
|
-
}),
|
|
5
|
+
s.imagedir("/public/val/images4", { accept: "image/*" }),
|
|
9
6
|
{
|
|
10
7
|
"/public/val/images4/missing.png": {
|
|
11
8
|
width: 1,
|
|
@@ -2,12 +2,7 @@ import { c, s } from "../val.config";
|
|
|
2
2
|
|
|
3
3
|
export default c.define(
|
|
4
4
|
"/content/basic-gallery-remote-existing.val.ts",
|
|
5
|
-
s
|
|
6
|
-
.imageset({
|
|
7
|
-
dir: "/public/val/images-remote2",
|
|
8
|
-
accept: "image/*",
|
|
9
|
-
})
|
|
10
|
-
.remote(),
|
|
5
|
+
s.imagedir("/public/val/images-remote2", { accept: "image/*" }).remote(),
|
|
11
6
|
|
|
12
7
|
{
|
|
13
8
|
"https://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc1/f/def4/p/public/val/images-remote2/image.png":
|
|
@@ -2,12 +2,7 @@ import { c, s } from "../val.config";
|
|
|
2
2
|
|
|
3
3
|
export default c.define(
|
|
4
4
|
"/content/basic-gallery-remote.val.ts",
|
|
5
|
-
s
|
|
6
|
-
.imageset({
|
|
7
|
-
dir: "/public/val/images-remote",
|
|
8
|
-
accept: "image/*",
|
|
9
|
-
})
|
|
10
|
-
.remote(),
|
|
5
|
+
s.imagedir("/public/val/images-remote", { accept: "image/*" }).remote(),
|
|
11
6
|
|
|
12
7
|
{
|
|
13
8
|
"/public/val/images-remote/image.png": {
|
|
@@ -2,10 +2,7 @@ import { c, s } from "../val.config";
|
|
|
2
2
|
|
|
3
3
|
export default c.define(
|
|
4
4
|
"/content/basic-gallery-wrong-metadata.val.ts",
|
|
5
|
-
s.
|
|
6
|
-
dir: "/public/val/images3",
|
|
7
|
-
accept: "image/*",
|
|
8
|
-
}),
|
|
5
|
+
s.imagedir("/public/val/images3", { accept: "image/*" }),
|
|
9
6
|
{
|
|
10
7
|
"/public/val/images3/image.png": {
|
|
11
8
|
width: 999,
|
|
@@ -2,10 +2,7 @@ import { c, s } from "../val.config";
|
|
|
2
2
|
|
|
3
3
|
export default c.define(
|
|
4
4
|
"/content/basic-gallery.val.ts",
|
|
5
|
-
s.
|
|
6
|
-
dir: "/public/val/images",
|
|
7
|
-
accept: "image/*",
|
|
8
|
-
}),
|
|
5
|
+
s.imagedir("/public/val/images", { accept: "image/*" }),
|
|
9
6
|
|
|
10
7
|
{
|
|
11
8
|
"/public/val/images/image.png": {
|
|
@@ -6,6 +6,7 @@ export default modules(config, [
|
|
|
6
6
|
{ def: () => import("./content/basic-errors.val") },
|
|
7
7
|
{ def: () => import("./content/basic-files.val") },
|
|
8
8
|
{ def: () => import("./content/basic-image.val") },
|
|
9
|
+
{ def: () => import("./content/basic-fixable-and-unfixable.val") },
|
|
9
10
|
{ def: () => import("./content/basic-image-from-gallery.val") },
|
|
10
11
|
{ def: () => import("./content/basic-image-from-galleries.val") },
|
|
11
12
|
{ def: () => import("./content/basic-gallery.val") },
|
package/src/listUnusedFiles.ts
CHANGED
|
@@ -56,7 +56,7 @@ export async function listUnusedFiles({ root }: { root?: string }) {
|
|
|
56
56
|
},
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
|
-
// A media collection (`s.
|
|
59
|
+
// A media collection (`s.imagedir()`, `s.filedir()`, `s.videodir()`) is
|
|
60
60
|
// keyed by its files, and an entry that is fine reports no error either —
|
|
61
61
|
// so, as for a video, they are found by walking the source. A video set's
|
|
62
62
|
// stream holds every playlist and segment its master names, and an entry
|
|
@@ -279,9 +279,13 @@ export async function buildArtifacts(options: {
|
|
|
279
279
|
}
|
|
280
280
|
})();
|
|
281
281
|
const deps = node.layerDepsOf(manifest, sources);
|
|
282
|
+
// The files those stylesheets point at -- a font package's `.woff2`s --
|
|
283
|
+
// published with the site under the names their `url()`s now use.
|
|
284
|
+
const dependencyAssets: Record<string, string> = {};
|
|
282
285
|
const css = await node.collectDependencyCss(
|
|
283
286
|
root,
|
|
284
287
|
node.cssSpecifiersOf(sources),
|
|
288
|
+
dependencyAssets,
|
|
285
289
|
);
|
|
286
290
|
const layer = await node.buildVendorLayer(root, deps, css, {
|
|
287
291
|
base: target.base,
|
|
@@ -319,6 +323,7 @@ export async function buildArtifacts(options: {
|
|
|
319
323
|
env: publicEnv(options.env ?? process.env),
|
|
320
324
|
publicFiles: readPublicFiles(root),
|
|
321
325
|
assets,
|
|
326
|
+
dependencyAssets,
|
|
322
327
|
// The project's own files, without the generated tree or the
|
|
323
328
|
// stylesheets: what the next publish -- the Studio's included -- starts
|
|
324
329
|
// from. See `withoutStylesheets`.
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "@jest/globals";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_VAL_REMOTE_HOST,
|
|
6
|
+
type ModuleFilePath,
|
|
7
|
+
type ModulePath,
|
|
8
|
+
} from "@valbuild/core";
|
|
9
|
+
import { createService } from "@valbuild/server";
|
|
10
|
+
import {
|
|
11
|
+
createDefaultValFSHost,
|
|
12
|
+
runValidation,
|
|
13
|
+
ValidationEvent,
|
|
14
|
+
IValRemote,
|
|
15
|
+
} from "./runValidation";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* `val validate` over a project with `s.imagedir().remote()` and
|
|
19
|
+
* `s.filedir().remote()` collections, and a page that picks from them — as a
|
|
20
|
+
* field, and as an image inside rich text. End to end: module loading, the fix
|
|
21
|
+
* handlers, the patches and the `.val.ts` rewrite.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const BASIC_FIXTURE = path.resolve(__dirname, "__fixtures__/basic");
|
|
25
|
+
|
|
26
|
+
// An 8x8 solid-colour PNG, so the metadata extractor has real bytes to read.
|
|
27
|
+
const PNG = Buffer.from(
|
|
28
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX/AAD//" +
|
|
29
|
+
"/+l2Z/dAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oIBRELLnJqTAAAAAxJREFUCNdjYGBgAAAABAABJzQnCgAAAABJRU5ErkJggg==",
|
|
30
|
+
"base64",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const LOGO = "/public/val/images/logo_abcde.png";
|
|
34
|
+
/**
|
|
35
|
+
* What the Studio stores in a field when an image is uploaded through it into
|
|
36
|
+
* a remote gallery: a ref, while the gallery keys the entry by the local path
|
|
37
|
+
* inside it (here, LOGO).
|
|
38
|
+
*/
|
|
39
|
+
const LOGO_UPLOADED_THROUGH_FIELD = `https://remote.val.build/file/p/pubproj/b/01/v/0.0.0/h/${"c".repeat(
|
|
40
|
+
64,
|
|
41
|
+
)}/f/${"d".repeat(12)}/p${LOGO}`;
|
|
42
|
+
const DOC = "/public/val/files/doc_12345.pdf";
|
|
43
|
+
|
|
44
|
+
const PROJECT: Record<string, string> = {
|
|
45
|
+
"val.modules.ts": `import { modules } from "@valbuild/core";
|
|
46
|
+
import { config } from "./val.config";
|
|
47
|
+
|
|
48
|
+
export default modules(config, [
|
|
49
|
+
{ def: () => import("./content/images.val") },
|
|
50
|
+
{ def: () => import("./content/files.val") },
|
|
51
|
+
{ def: () => import("./content/page.val") },
|
|
52
|
+
{ def: () => import("./content/cards.val") },
|
|
53
|
+
{ def: () => import("./content/local-images.val") },
|
|
54
|
+
{ def: () => import("./content/remote-image.val") },
|
|
55
|
+
]);
|
|
56
|
+
`,
|
|
57
|
+
"content/images.val.ts": `import { c, s } from "../val.config";
|
|
58
|
+
|
|
59
|
+
export default c.define(
|
|
60
|
+
"/content/images.val.ts",
|
|
61
|
+
s.imagedir("/public/val/images").remote(),
|
|
62
|
+
{
|
|
63
|
+
"${LOGO}": { width: 8, height: 8, mimeType: "image/png", alt: "Logo" },
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
`,
|
|
67
|
+
"content/files.val.ts": `import { c, s } from "../val.config";
|
|
68
|
+
|
|
69
|
+
export default c.define(
|
|
70
|
+
"/content/files.val.ts",
|
|
71
|
+
s.filedir("/public/val/files", { accept: "application/pdf" }).remote(),
|
|
72
|
+
{
|
|
73
|
+
"${DOC}": { mimeType: "application/pdf" },
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
`,
|
|
77
|
+
// A field inside a `.jsonValues()` entry: its value is in the entry's
|
|
78
|
+
// `*.val.json`, which is where the rename has to land.
|
|
79
|
+
"content/cards.val.ts": `import { c, s } from "../val.config";
|
|
80
|
+
import imagesVal from "./images.val";
|
|
81
|
+
|
|
82
|
+
export default c.define(
|
|
83
|
+
"/content/cards.val.ts",
|
|
84
|
+
s.record(s.object({ image: s.image(imagesVal) })).jsonValues(),
|
|
85
|
+
{
|
|
86
|
+
"/a": c.json(() => import("./cards/a.val.json")),
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
`,
|
|
90
|
+
"content/cards/a.val.json": JSON.stringify({ image: { path: LOGO } }),
|
|
91
|
+
"content/page.val.ts": `import { c, s } from "../val.config";
|
|
92
|
+
import imagesVal from "./images.val";
|
|
93
|
+
import filesVal from "./files.val";
|
|
94
|
+
|
|
95
|
+
export default c.define(
|
|
96
|
+
"/content/page.val.ts",
|
|
97
|
+
s.object({
|
|
98
|
+
hero: s.image(imagesVal),
|
|
99
|
+
card: s.image(imagesVal),
|
|
100
|
+
body: s.richtext({ img: s.image(imagesVal) }),
|
|
101
|
+
attachment: s.file(filesVal),
|
|
102
|
+
}),
|
|
103
|
+
{
|
|
104
|
+
hero: { path: "${LOGO}", alt: "Kept" },
|
|
105
|
+
card: { path: "${LOGO_UPLOADED_THROUGH_FIELD}" },
|
|
106
|
+
body: [
|
|
107
|
+
{
|
|
108
|
+
tag: "p",
|
|
109
|
+
children: ["Look: ", { tag: "img", src: { path: "${LOGO}" } }],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
attachment: { path: "${DOC}" },
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
`,
|
|
116
|
+
// Not \`.remote()\`, and keyed by a ref anyway: an error for a person.
|
|
117
|
+
"content/local-images.val.ts": `import { c, s } from "../val.config";
|
|
118
|
+
|
|
119
|
+
export default c.define(
|
|
120
|
+
"/content/local-images.val.ts",
|
|
121
|
+
s.imagedir("/public/val/local"),
|
|
122
|
+
{
|
|
123
|
+
"https://remote.val.build/file/p/pubproj/b/01/v/0.0.0/h/${"a".repeat(64)}/f/${"b".repeat(12)}/p/public/val/local/x_00000.png":
|
|
124
|
+
{ width: 8, height: 8, mimeType: "image/png", alt: null },
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
`,
|
|
128
|
+
// A field of its own, not from a gallery, with a ref that does not parse.
|
|
129
|
+
"content/remote-image.val.ts": `import { c, s } from "../val.config";
|
|
130
|
+
|
|
131
|
+
export default c.define(
|
|
132
|
+
"/content/remote-image.val.ts",
|
|
133
|
+
s.image().remote(),
|
|
134
|
+
{ path: "https://remote.val.build/file/not-a-ref.png", width: 8, height: 8, mimeType: "image/png" },
|
|
135
|
+
);
|
|
136
|
+
`,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const notExpected: IValRemote = {
|
|
140
|
+
remoteHost: DEFAULT_VAL_REMOTE_HOST,
|
|
141
|
+
getSettings: async () => {
|
|
142
|
+
throw new Error("Not expected to be called");
|
|
143
|
+
},
|
|
144
|
+
uploadFile: async () => {
|
|
145
|
+
throw new Error("Not expected to be called");
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
async function run(
|
|
150
|
+
root: string,
|
|
151
|
+
valFiles: string[],
|
|
152
|
+
fix: boolean,
|
|
153
|
+
remote: IValRemote = notExpected,
|
|
154
|
+
): Promise<ValidationEvent[]> {
|
|
155
|
+
const events: ValidationEvent[] = [];
|
|
156
|
+
for await (const event of runValidation({
|
|
157
|
+
root,
|
|
158
|
+
fix,
|
|
159
|
+
valFiles,
|
|
160
|
+
project: fix ? "test/project" : undefined,
|
|
161
|
+
remote,
|
|
162
|
+
fs: createDefaultValFSHost(),
|
|
163
|
+
})) {
|
|
164
|
+
events.push(event);
|
|
165
|
+
}
|
|
166
|
+
return events;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function sourceOf(root: string, moduleFilePath: string) {
|
|
170
|
+
const service = await createService(root, createDefaultValFSHost());
|
|
171
|
+
try {
|
|
172
|
+
const result = await service.get(
|
|
173
|
+
moduleFilePath as ModuleFilePath,
|
|
174
|
+
"" as ModulePath,
|
|
175
|
+
{ validate: false },
|
|
176
|
+
);
|
|
177
|
+
return result.source;
|
|
178
|
+
} finally {
|
|
179
|
+
service.dispose();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function errorsOf(events: ValidationEvent[]) {
|
|
184
|
+
return events.filter(
|
|
185
|
+
(e) =>
|
|
186
|
+
e.type === "validation-error" || e.type === "validation-fixable-error",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
describe("val validate with remote image and file collections", () => {
|
|
191
|
+
let root: string;
|
|
192
|
+
|
|
193
|
+
beforeEach(() => {
|
|
194
|
+
const tmpBase = path.join(__dirname, ".tmp");
|
|
195
|
+
fs.mkdirSync(tmpBase, { recursive: true });
|
|
196
|
+
root = fs.mkdtempSync(path.join(tmpBase, "imageset-"));
|
|
197
|
+
for (const file of ["val.config.ts", "tsconfig.json"]) {
|
|
198
|
+
fs.copyFileSync(path.join(BASIC_FIXTURE, file), path.join(root, file));
|
|
199
|
+
}
|
|
200
|
+
const files: Record<string, string | Buffer> = {
|
|
201
|
+
...PROJECT,
|
|
202
|
+
[LOGO.slice(1)]: PNG,
|
|
203
|
+
[DOC.slice(1)]: "%PDF-1.4\n",
|
|
204
|
+
"public/val/local/x_00000.png": PNG,
|
|
205
|
+
};
|
|
206
|
+
for (const [file, content] of Object.entries(files)) {
|
|
207
|
+
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
|
|
208
|
+
fs.writeFileSync(path.join(root, file), content);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
afterEach(() => {
|
|
213
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("--fix uploads the entries, renames their keys, and points the page's field and rich text image at them", async () => {
|
|
217
|
+
fs.mkdirSync(path.join(root, ".val"), { recursive: true });
|
|
218
|
+
fs.writeFileSync(
|
|
219
|
+
path.join(root, ".val", "pat.json"),
|
|
220
|
+
JSON.stringify({ pat: "test-pat" }),
|
|
221
|
+
);
|
|
222
|
+
const uploaded: string[] = [];
|
|
223
|
+
const remote: IValRemote = {
|
|
224
|
+
remoteHost: DEFAULT_VAL_REMOTE_HOST,
|
|
225
|
+
getSettings: async () => ({
|
|
226
|
+
success: true,
|
|
227
|
+
data: {
|
|
228
|
+
publicProjectId: "pubproj",
|
|
229
|
+
remoteFileBuckets: [{ bucket: "01" }],
|
|
230
|
+
},
|
|
231
|
+
}),
|
|
232
|
+
uploadFile: async (_project, _bucket, fileHash) => {
|
|
233
|
+
uploaded.push(fileHash);
|
|
234
|
+
return { success: true };
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
const modules = [
|
|
238
|
+
"content/cards.val.ts",
|
|
239
|
+
"content/files.val.ts",
|
|
240
|
+
"content/images.val.ts",
|
|
241
|
+
"content/page.val.ts",
|
|
242
|
+
];
|
|
243
|
+
// Before: the page is valid against both collections, and the two
|
|
244
|
+
// entries are fixable.
|
|
245
|
+
expect(
|
|
246
|
+
errorsOf(await run(root, modules, false)).map((e) => e.sourcePath),
|
|
247
|
+
).toEqual([
|
|
248
|
+
`/content/files.val.ts?p="${DOC}"`,
|
|
249
|
+
`/content/images.val.ts?p="${LOGO}"`,
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
await run(root, modules, true, remote);
|
|
253
|
+
expect(uploaded).toHaveLength(2);
|
|
254
|
+
|
|
255
|
+
const images = await sourceOf(root, "/content/images.val.ts");
|
|
256
|
+
const [logoRef] = Object.keys(images ?? {});
|
|
257
|
+
expect(logoRef).toMatch(
|
|
258
|
+
/^https:\/\/.*pubproj.*public\/val\/images\/logo_abcde\.png$/,
|
|
259
|
+
);
|
|
260
|
+
const files = await sourceOf(root, "/content/files.val.ts");
|
|
261
|
+
const [docRef] = Object.keys(files ?? {});
|
|
262
|
+
expect(docRef).toMatch(
|
|
263
|
+
/^https:\/\/.*pubproj.*public\/val\/files\/doc_12345\.pdf$/,
|
|
264
|
+
);
|
|
265
|
+
expect(await sourceOf(root, "/content/page.val.ts")).toEqual({
|
|
266
|
+
hero: { path: logoRef, alt: "Kept" },
|
|
267
|
+
card: { path: logoRef },
|
|
268
|
+
body: [
|
|
269
|
+
{
|
|
270
|
+
tag: "p",
|
|
271
|
+
children: ["Look: ", { tag: "img", src: { path: logoRef } }],
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
attachment: { path: docRef },
|
|
275
|
+
});
|
|
276
|
+
expect(
|
|
277
|
+
JSON.parse(
|
|
278
|
+
fs.readFileSync(path.join(root, "content/cards/a.val.json"), "utf-8"),
|
|
279
|
+
),
|
|
280
|
+
).toEqual({ image: { path: logoRef } });
|
|
281
|
+
|
|
282
|
+
// And all of them are valid against each other afterwards.
|
|
283
|
+
const again = await run(root, modules, false);
|
|
284
|
+
expect(errorsOf(again)).toEqual([]);
|
|
285
|
+
expect(again.at(-1)).toEqual({ type: "summary-success" });
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("a key that is wrong as a key", () => {
|
|
289
|
+
test("is reported with what core says, and the module is not valid", async () => {
|
|
290
|
+
const events = await run(root, ["content/local-images.val.ts"], false);
|
|
291
|
+
expect(errorsOf(events)).toEqual([
|
|
292
|
+
expect.objectContaining({
|
|
293
|
+
type: "validation-error",
|
|
294
|
+
message: expect.stringContaining("Remote URLs are not allowed"),
|
|
295
|
+
keyError: true,
|
|
296
|
+
}),
|
|
297
|
+
]);
|
|
298
|
+
expect(events.filter((e) => e.type === "file-valid")).toEqual([]);
|
|
299
|
+
expect(events.at(-1)).toEqual({ type: "summary-errors", count: 1 });
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("--fix leaves it to a person, and still reports it", async () => {
|
|
303
|
+
const before = fs.readFileSync(
|
|
304
|
+
path.join(root, "content/local-images.val.ts"),
|
|
305
|
+
"utf-8",
|
|
306
|
+
);
|
|
307
|
+
const events = await run(root, ["content/local-images.val.ts"], true);
|
|
308
|
+
expect(errorsOf(events)).toEqual([
|
|
309
|
+
expect.objectContaining({
|
|
310
|
+
type: "validation-error",
|
|
311
|
+
message: expect.stringContaining("Remote URLs are not allowed"),
|
|
312
|
+
}),
|
|
313
|
+
]);
|
|
314
|
+
expect(events.at(-1)).toEqual({ type: "summary-errors", count: 1 });
|
|
315
|
+
expect(
|
|
316
|
+
fs.readFileSync(
|
|
317
|
+
path.join(root, "content/local-images.val.ts"),
|
|
318
|
+
"utf-8",
|
|
319
|
+
),
|
|
320
|
+
).toBe(before);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The singular `image:check-remote` is core's "was not checked" marker, put
|
|
326
|
+
* on every remote image whether or not it is wrong. It stays a hand-off to
|
|
327
|
+
* `createFixPatch`, which checks the ref and reports only what is wrong —
|
|
328
|
+
* this pins that the hand-off does report it.
|
|
329
|
+
*/
|
|
330
|
+
test("a field's malformed remote ref is still reported, through the check createFixPatch makes", async () => {
|
|
331
|
+
const events = await run(root, ["content/remote-image.val.ts"], false);
|
|
332
|
+
expect(errorsOf(events)).toEqual([
|
|
333
|
+
expect.objectContaining({
|
|
334
|
+
sourcePath: "/content/remote-image.val.ts",
|
|
335
|
+
message: expect.stringContaining("not-a-ref.png"),
|
|
336
|
+
}),
|
|
337
|
+
]);
|
|
338
|
+
expect(events.at(-1)).toEqual({ type: "summary-errors", count: 1 });
|
|
339
|
+
});
|
|
340
|
+
});
|
|
@@ -125,6 +125,52 @@ describe("runValidation", () => {
|
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
describe("a module where --fix repairs one error and not the other", () => {
|
|
129
|
+
const FILE = "content/basic-fixable-and-unfixable.val.ts";
|
|
130
|
+
const runOn = async (fix: boolean) => {
|
|
131
|
+
const events: ValidationEvent[] = [];
|
|
132
|
+
for await (const event of runValidation({
|
|
133
|
+
root: tmpDir,
|
|
134
|
+
fix,
|
|
135
|
+
valFiles: [FILE],
|
|
136
|
+
project: undefined,
|
|
137
|
+
remote: mockRemote,
|
|
138
|
+
fs: createDefaultValFSHost(),
|
|
139
|
+
})) {
|
|
140
|
+
events.push(event);
|
|
141
|
+
}
|
|
142
|
+
return events;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
test("is not reported valid after --fix, and counts only what is left", async () => {
|
|
146
|
+
const events = await runOn(true);
|
|
147
|
+
|
|
148
|
+
expect(events.filter((e) => e.type === "fix-applied")).toHaveLength(1);
|
|
149
|
+
expect(events.filter((e) => e.type === "validation-error")).toEqual([
|
|
150
|
+
expect.objectContaining({
|
|
151
|
+
sourcePath: `/${FILE}?p="title"`,
|
|
152
|
+
}),
|
|
153
|
+
]);
|
|
154
|
+
// One fixed and one left must not add up to "valid".
|
|
155
|
+
expect(events.filter((e) => e.type === "file-valid")).toEqual([]);
|
|
156
|
+
expect(events).toContainEqual(
|
|
157
|
+
expect.objectContaining({
|
|
158
|
+
type: "file-error-count",
|
|
159
|
+
file: `/${FILE}`,
|
|
160
|
+
errorCount: 1,
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
expect(events.at(-1)).toEqual({ type: "summary-errors", count: 1 });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("without --fix, reports both", async () => {
|
|
167
|
+
const events = await runOn(false);
|
|
168
|
+
|
|
169
|
+
expect(events.filter((e) => e.type === "file-valid")).toEqual([]);
|
|
170
|
+
expect(events.at(-1)).toEqual({ type: "summary-errors", count: 2 });
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
128
174
|
test("handles module with both s.image and s.images", async () => {
|
|
129
175
|
const events: ValidationEvent[] = [];
|
|
130
176
|
|
package/src/runValidation.ts
CHANGED
|
@@ -175,8 +175,9 @@ export async function* runValidation({
|
|
|
175
175
|
};
|
|
176
176
|
return;
|
|
177
177
|
} else {
|
|
178
|
+
// What is still wrong with this module once every handler has run: an
|
|
179
|
+
// error that was fixed is not counted, one that was not is.
|
|
178
180
|
let fileErrors = 0;
|
|
179
|
-
let fixedErrors = 0;
|
|
180
181
|
if (valModule.errors) {
|
|
181
182
|
if (valModule.errors.validation) {
|
|
182
183
|
// Resolve schema/source fixes (keyof:check-keys, router:check-route)
|
|
@@ -265,7 +266,6 @@ export async function* runValidation({
|
|
|
265
266
|
}
|
|
266
267
|
|
|
267
268
|
if (result.appliedFix) {
|
|
268
|
-
fixedErrors += 1;
|
|
269
269
|
yield { type: "fix-applied", file, sourcePath };
|
|
270
270
|
}
|
|
271
271
|
|
|
@@ -306,7 +306,6 @@ export async function* runValidation({
|
|
|
306
306
|
sourcePath: other.moduleFilePath,
|
|
307
307
|
};
|
|
308
308
|
}
|
|
309
|
-
fixedErrors += 1;
|
|
310
309
|
yield { type: "fix-applied", file, sourcePath };
|
|
311
310
|
} else if (
|
|
312
311
|
!fix &&
|
|
@@ -339,8 +338,11 @@ export async function* runValidation({
|
|
|
339
338
|
}
|
|
340
339
|
}
|
|
341
340
|
}
|
|
341
|
+
// Valid means nothing is left, not that as many errors were fixed as
|
|
342
|
+
// are left: one fix and one error that no fix touches add up to the
|
|
343
|
+
// same count, and printed a module with an error in it as valid.
|
|
342
344
|
if (
|
|
343
|
-
|
|
345
|
+
fileErrors === 0 &&
|
|
344
346
|
(!valModule.errors.fatal || valModule.errors.fatal.length == 0)
|
|
345
347
|
) {
|
|
346
348
|
yield {
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./runValidation";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* `val validate` over a project with an `s.
|
|
18
|
+
* `val validate` over a project with an `s.videodir()`, end to end: module
|
|
19
19
|
* loading, the fix handlers, the patches and the `.val.ts` rewrite.
|
|
20
20
|
*
|
|
21
21
|
* The project is written here rather than kept as a fixture because its whole
|
|
@@ -55,7 +55,7 @@ export default modules(config, [
|
|
|
55
55
|
|
|
56
56
|
export default c.define(
|
|
57
57
|
"/content/videos.val.ts",
|
|
58
|
-
s.
|
|
58
|
+
s.videodir("/public/val/videos"),
|
|
59
59
|
{
|
|
60
60
|
"/public/val/videos/clip_abcde.mp4": { alt: "A clip" },
|
|
61
61
|
"/public/val/videos/intro_05198/master.m3u8": {
|
|
@@ -72,7 +72,7 @@ export default c.define(
|
|
|
72
72
|
|
|
73
73
|
export default c.define(
|
|
74
74
|
"/content/videos-remote.val.ts",
|
|
75
|
-
s.
|
|
75
|
+
s.videodir("/public/val/remote-videos").remote(),
|
|
76
76
|
{
|
|
77
77
|
"/public/val/remote-videos/clip_fghij.mp4": {
|
|
78
78
|
mimeType: "video/mp4",
|
|
@@ -155,7 +155,7 @@ async function sourceOf(root: string, moduleFilePath: string) {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
describe("val validate with an s.
|
|
158
|
+
describe("val validate with an s.videodir()", () => {
|
|
159
159
|
let root: string;
|
|
160
160
|
|
|
161
161
|
beforeEach(() => {
|