@valbuild/cli 0.150.0 → 0.150.2
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 +72 -0
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +248 -64
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +248 -64
- package/cli/dist/valbuild-cli-cli.esm.js +248 -64
- package/package.json +8 -8
- package/src/checkBuildTools.ts +16 -0
- package/src/cli.ts +16 -0
- package/src/connect.test.ts +32 -1
- package/src/connect.ts +27 -13
- package/src/publish/buildArtifacts.ts +51 -0
- package/src/publish.ts +5 -1
- package/src/runValidation.imageset.test.ts +23 -19
- package/src/runValidation.test.ts +64 -23
- package/src/runValidation.ts +9 -0
- package/src/validate.test.ts +4 -4
- package/src/validate.ts +186 -58
- package/src/validate.warnings.test.ts +170 -0
package/src/connect.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { connectUrl, getGitHubOwnerAndRepo, repoFolder } from "./connect";
|
|
2
3
|
|
|
3
4
|
describe("val connect", () => {
|
|
4
5
|
test("opens /new on your own hosting, with the repository and the project filled in", () => {
|
|
@@ -17,6 +18,36 @@ describe("val connect", () => {
|
|
|
17
18
|
});
|
|
18
19
|
});
|
|
19
20
|
|
|
21
|
+
test("in a monorepo, the app's folder goes with the repository", () => {
|
|
22
|
+
const url = new URL(
|
|
23
|
+
connectUrl("https://admin.val.build", {
|
|
24
|
+
project: null,
|
|
25
|
+
repo: { owner: "acme", repo: "monorepo" },
|
|
26
|
+
folder: "apps/web",
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
expect(Object.fromEntries(url.searchParams)).toEqual({
|
|
30
|
+
hosting: "own",
|
|
31
|
+
repo: "acme/monorepo",
|
|
32
|
+
folder: "apps/web",
|
|
33
|
+
});
|
|
34
|
+
// At the top of the repository there is no folder to send.
|
|
35
|
+
expect(
|
|
36
|
+
connectUrl("https://admin.val.build", {
|
|
37
|
+
project: null,
|
|
38
|
+
repo: { owner: "acme", repo: "site" },
|
|
39
|
+
folder: "",
|
|
40
|
+
}),
|
|
41
|
+
).toBe("https://admin.val.build/new?hosting=own&repo=acme%2Fsite");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("the folder is the app's place in its repository, with forward slashes", () => {
|
|
45
|
+
const top = path.resolve("/work/monorepo");
|
|
46
|
+
expect(repoFolder(top, path.join(top, "apps", "web"))).toBe("apps/web");
|
|
47
|
+
expect(repoFolder(top, top)).toBe("");
|
|
48
|
+
expect(repoFolder(top, path.resolve("/elsewhere"))).toBe(null);
|
|
49
|
+
});
|
|
50
|
+
|
|
20
51
|
test("with nothing found, it is /new on your own hosting alone", () => {
|
|
21
52
|
expect(
|
|
22
53
|
connectUrl("http://localhost:3000/", { project: null, repo: null }),
|
package/src/connect.ts
CHANGED
|
@@ -8,17 +8,20 @@ const host = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* `val connect`: opens admin's `/new` on "your own hosting", with what it can
|
|
11
|
-
* tell from here filled in -- the project `val.config.ts` names
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* tell from here filled in -- the project `val.config.ts` names, the GitHub
|
|
12
|
+
* repository `origin` points at, and which folder of it this app is in (a
|
|
13
|
+
* monorepo has several) -- so the person only confirms. The link is printed
|
|
14
|
+
* too, for when no browser opens (over SSH, in a container).
|
|
14
15
|
*/
|
|
15
16
|
export async function connect(options: { root?: string }) {
|
|
16
17
|
const { root } = options;
|
|
17
18
|
const projectRoot = root ? path.resolve(root) : process.cwd();
|
|
18
19
|
|
|
19
20
|
const project = await tryGetProject(projectRoot);
|
|
20
|
-
const
|
|
21
|
+
const git = findGitDir(projectRoot);
|
|
22
|
+
const gitRemote = git ? await tryGetGitRemote(git.config) : null;
|
|
21
23
|
const repo = gitRemote !== null ? getGitHubOwnerAndRepo(gitRemote) : null;
|
|
24
|
+
const folder = git ? repoFolder(git.top, projectRoot) : null;
|
|
22
25
|
|
|
23
26
|
console.log("");
|
|
24
27
|
if (project) {
|
|
@@ -28,7 +31,7 @@ export async function connect(options: { root?: string }) {
|
|
|
28
31
|
}
|
|
29
32
|
if (repo) {
|
|
30
33
|
console.log(
|
|
31
|
-
`Found ${pc.bold(`${repo.owner}/${repo.repo}`)} on GitHub (git remote "origin")`,
|
|
34
|
+
`Found ${pc.bold(`${repo.owner}/${repo.repo}`)} on GitHub (git remote "origin")${folder ? `, this app in ${pc.bold(folder)}` : ""}`,
|
|
32
35
|
);
|
|
33
36
|
} else {
|
|
34
37
|
console.log(
|
|
@@ -38,7 +41,7 @@ export async function connect(options: { root?: string }) {
|
|
|
38
41
|
);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
const url = connectUrl(host, { project, repo });
|
|
44
|
+
const url = connectUrl(host, { project, repo, folder });
|
|
42
45
|
const opened = openInBrowser(url);
|
|
43
46
|
console.log(
|
|
44
47
|
`\n${opened ? "Finish in your browser. If it did not open, go to:" : "Finish in your browser:"}\n${pc.cyan(url)}\n`,
|
|
@@ -55,11 +58,14 @@ export function connectUrl(
|
|
|
55
58
|
found: {
|
|
56
59
|
project: { orgName: string; projectName: string } | null;
|
|
57
60
|
repo: { owner: string; repo: string } | null;
|
|
61
|
+
/** The app's folder in the repository, `apps/web`; "" or null at the top. */
|
|
62
|
+
folder?: string | null;
|
|
58
63
|
},
|
|
59
64
|
): string {
|
|
60
65
|
const params = new URLSearchParams({ hosting: "own" });
|
|
61
66
|
if (found.repo) {
|
|
62
67
|
params.set("repo", `${found.repo.owner}/${found.repo.repo}`);
|
|
68
|
+
if (found.folder) params.set("folder", found.folder);
|
|
63
69
|
}
|
|
64
70
|
if (found.project) {
|
|
65
71
|
params.set("org", found.project.orgName);
|
|
@@ -141,12 +147,19 @@ export function getGitHubOwnerAndRepo(
|
|
|
141
147
|
return match ? { owner: match[1], repo: match[2] } : null;
|
|
142
148
|
}
|
|
143
149
|
|
|
144
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Where the app is in its repository, with forward slashes: `apps/web`, or ""
|
|
152
|
+
* when it is at the top. Null when it is not inside the repository at all.
|
|
153
|
+
*/
|
|
154
|
+
export function repoFolder(top: string, projectRoot: string): string | null {
|
|
155
|
+
const relative = path.relative(top, projectRoot);
|
|
156
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) return null;
|
|
157
|
+
return relative.split(path.sep).join("/");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function tryGetGitRemote(gitConfigPath: string): Promise<string | null> {
|
|
145
161
|
try {
|
|
146
|
-
const gitConfig =
|
|
147
|
-
if (!gitConfig) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
162
|
+
const gitConfig = fs.readFileSync(gitConfigPath, "utf-8");
|
|
150
163
|
const remoteMatch = gitConfig.match(
|
|
151
164
|
/\[remote "origin"\][\s\S]*?url = (.+)/,
|
|
152
165
|
);
|
|
@@ -160,13 +173,14 @@ async function tryGetGitRemote(root: string): Promise<string | null> {
|
|
|
160
173
|
}
|
|
161
174
|
}
|
|
162
175
|
|
|
163
|
-
|
|
176
|
+
/** The repository `root` is in: its top folder and its `.git/config`. */
|
|
177
|
+
function findGitDir(root: string): { top: string; config: string } | null {
|
|
164
178
|
let currentDir = root;
|
|
165
179
|
let lastDir = null;
|
|
166
180
|
while (currentDir !== lastDir) {
|
|
167
181
|
const gitConfigPath = path.join(currentDir, ".git", "config");
|
|
168
182
|
if (fs.existsSync(gitConfigPath)) {
|
|
169
|
-
return
|
|
183
|
+
return { top: currentDir, config: gitConfigPath };
|
|
170
184
|
}
|
|
171
185
|
lastDir = currentDir;
|
|
172
186
|
currentDir = path.dirname(currentDir);
|
|
@@ -60,6 +60,14 @@ async function loadBuilder(): Promise<
|
|
|
60
60
|
import("@valbuild/tanstack-build"),
|
|
61
61
|
import("@valbuild/tanstack-build/node"),
|
|
62
62
|
]);
|
|
63
|
+
/*
|
|
64
|
+
* Native rolldown rather than its WebAssembly build, whose glue the site's
|
|
65
|
+
* lockfile picks and can break (`rolldown.ts` in tanstack-build). Asked
|
|
66
|
+
* for, because the project's own tanstack-build may predate the choice.
|
|
67
|
+
*/
|
|
68
|
+
if ("useRolldown" in build && "nativeRolldown" in node) {
|
|
69
|
+
build.useRolldown(node.nativeRolldown);
|
|
70
|
+
}
|
|
63
71
|
return { status: "ok", build, node };
|
|
64
72
|
} catch (e) {
|
|
65
73
|
return {
|
|
@@ -74,6 +82,49 @@ async function loadBuilder(): Promise<
|
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Whether `val publish` could build here: the builder loaded as a publish
|
|
87
|
+
* loads it, and its bundler made to parse and bundle a module. Nothing is
|
|
88
|
+
* read from the project and nothing is asked of content.
|
|
89
|
+
*
|
|
90
|
+
* What Val Build runs on an upgrade's install before a connected project
|
|
91
|
+
* may publish it, since the project's CI is what builds a connected publish:
|
|
92
|
+
* an upgrade that resolved a bundler which cannot load would otherwise land
|
|
93
|
+
* on the branch and fail every build after it (architecture/quirks.md,
|
|
94
|
+
* 2026-10-26).
|
|
95
|
+
*/
|
|
96
|
+
export async function checkBuildTools(): Promise<
|
|
97
|
+
{ status: "ok" } | { status: "error"; message: string }
|
|
98
|
+
> {
|
|
99
|
+
const loaded = await loadBuilder();
|
|
100
|
+
if (loaded.status === "error") return loaded;
|
|
101
|
+
try {
|
|
102
|
+
// A builder older than `loadRolldown` loaded its bundler on import.
|
|
103
|
+
if (!("loadRolldown" in loaded.build)) return { status: "ok" };
|
|
104
|
+
const { build, parseAstAsync } = await loaded.build.loadRolldown();
|
|
105
|
+
await parseAstAsync("export const checked = true;");
|
|
106
|
+
const ENTRY = "\0val-check-build-tools";
|
|
107
|
+
await build({
|
|
108
|
+
input: ENTRY,
|
|
109
|
+
cwd: "/",
|
|
110
|
+
write: false,
|
|
111
|
+
plugins: [
|
|
112
|
+
{
|
|
113
|
+
name: "val-check-build-tools",
|
|
114
|
+
resolveId: (id) => (id === ENTRY ? id : null),
|
|
115
|
+
load: (id) => (id === ENTRY ? "export default 1;" : null),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
return { status: "ok" };
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return {
|
|
122
|
+
status: "error",
|
|
123
|
+
message: `rolldown loaded, and could not build: ${e instanceof Error ? e.message : String(e)}`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
77
128
|
/**
|
|
78
129
|
* The `@valbuild/ui` this checkout runs: the one its `@valbuild/tanstack`
|
|
79
130
|
* resolves, which is a transitive dependency pnpm does not hoist -- so it is
|
package/src/publish.ts
CHANGED
|
@@ -22,6 +22,8 @@ export async function publish(options: {
|
|
|
22
22
|
linksOwnCss?: boolean;
|
|
23
23
|
dryRun?: boolean;
|
|
24
24
|
skipValidation?: boolean;
|
|
25
|
+
/** Let a validation warning stop the publish too, as `val validate --strict`. */
|
|
26
|
+
strict?: boolean;
|
|
25
27
|
}): Promise<void> {
|
|
26
28
|
// Content that does not validate is not published. This is the check that
|
|
27
29
|
// lets the Studio trust a connected project's published content without
|
|
@@ -32,7 +34,8 @@ export async function publish(options: {
|
|
|
32
34
|
// the Studio leaves to the server to repair (a local image in a remote
|
|
33
35
|
// project, metadata to fill in) is printed and published, as it would be
|
|
34
36
|
// from the Studio. Every site made on /new starts with its template's
|
|
35
|
-
// images local, so counting those would refuse its first publish
|
|
37
|
+
// images local, so counting those would refuse its first publish: they are
|
|
38
|
+
// warnings, and refuse a publish only under --strict.
|
|
36
39
|
if (!options.skipValidation) {
|
|
37
40
|
const projectRoot = options.root
|
|
38
41
|
? path.resolve(options.root)
|
|
@@ -41,6 +44,7 @@ export async function publish(options: {
|
|
|
41
44
|
projectRoot,
|
|
42
45
|
fix: false,
|
|
43
46
|
blockingOnly: true,
|
|
47
|
+
...(options.strict ? { strict: true } : {}),
|
|
44
48
|
});
|
|
45
49
|
if (errorCount > 0) {
|
|
46
50
|
error(
|
|
@@ -187,6 +187,10 @@ function errorsOf(events: ValidationEvent[]) {
|
|
|
187
187
|
);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
function warningsOf(events: ValidationEvent[]) {
|
|
191
|
+
return events.filter((e) => e.type === "validation-warning");
|
|
192
|
+
}
|
|
193
|
+
|
|
190
194
|
describe("val validate with remote image and file collections", () => {
|
|
191
195
|
let root: string;
|
|
192
196
|
|
|
@@ -214,22 +218,22 @@ describe("val validate with remote image and file collections", () => {
|
|
|
214
218
|
});
|
|
215
219
|
|
|
216
220
|
// What `val publish` refuses on (`validateOnce({ blockingOnly })`): a
|
|
217
|
-
// local file still to upload is
|
|
218
|
-
//
|
|
219
|
-
test("
|
|
220
|
-
const events =
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
false,
|
|
225
|
-
),
|
|
221
|
+
// local file still to upload is a warning, and is published; a gallery
|
|
222
|
+
// keyed by a ref it does not know is a person's to fix.
|
|
223
|
+
test("a local file still to upload is a warning, and a person's error is not repaired on save", async () => {
|
|
224
|
+
const events = await run(
|
|
225
|
+
root,
|
|
226
|
+
["content/images.val.ts", "content/local-images.val.ts"],
|
|
227
|
+
false,
|
|
226
228
|
);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
expect(
|
|
232
|
-
|
|
229
|
+
expect(warningsOf(events).map((e) => [e.sourcePath, e.files])).toEqual([
|
|
230
|
+
[`/content/images.val.ts?p="${LOGO}"`, [LOGO]],
|
|
231
|
+
]);
|
|
232
|
+
const errors = errorsOf(events);
|
|
233
|
+
expect(
|
|
234
|
+
errors.filter((e) => e.sourcePath.startsWith("/content/images.val.ts")),
|
|
235
|
+
).toEqual([]);
|
|
236
|
+
const local = errors.filter((e) =>
|
|
233
237
|
e.sourcePath.startsWith("/content/local-images.val.ts"),
|
|
234
238
|
);
|
|
235
239
|
expect(local.length).toBeGreaterThan(0);
|
|
@@ -264,10 +268,10 @@ describe("val validate with remote image and file collections", () => {
|
|
|
264
268
|
"content/page.val.ts",
|
|
265
269
|
];
|
|
266
270
|
// Before: the page is valid against both collections, and the two
|
|
267
|
-
// entries are
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
).toEqual([
|
|
271
|
+
// entries are warnings: local files still to upload.
|
|
272
|
+
const before = await run(root, modules, false);
|
|
273
|
+
expect(errorsOf(before)).toEqual([]);
|
|
274
|
+
expect(warningsOf(before).map((e) => e.sourcePath)).toEqual([
|
|
271
275
|
`/content/files.val.ts?p="${DOC}"`,
|
|
272
276
|
`/content/images.val.ts?p="${LOGO}"`,
|
|
273
277
|
]);
|
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
type ModulePath,
|
|
8
8
|
} from "@valbuild/core";
|
|
9
9
|
import { createService } from "@valbuild/server";
|
|
10
|
+
import {
|
|
11
|
+
LOCAL_FILE_IN_REMOTE_PROJECT_HINT,
|
|
12
|
+
LOCAL_FILE_IN_REMOTE_PROJECT_WARNING,
|
|
13
|
+
} from "@valbuild/shared/internal";
|
|
10
14
|
import {
|
|
11
15
|
createDefaultValFSHost,
|
|
12
16
|
runValidation,
|
|
@@ -430,32 +434,69 @@ describe("runValidation", () => {
|
|
|
430
434
|
}
|
|
431
435
|
});
|
|
432
436
|
|
|
433
|
-
|
|
434
|
-
const
|
|
437
|
+
describe("a local entry in a remote gallery", () => {
|
|
438
|
+
const run = async (fix: boolean) => {
|
|
439
|
+
const events: ValidationEvent[] = [];
|
|
440
|
+
for await (const event of runValidation({
|
|
441
|
+
root: tmpDir,
|
|
442
|
+
fix,
|
|
443
|
+
valFiles: ["content/basic-gallery-remote.val.ts"],
|
|
444
|
+
project: undefined,
|
|
445
|
+
remote: mockRemote,
|
|
446
|
+
fs: createDefaultValFSHost(),
|
|
447
|
+
})) {
|
|
448
|
+
events.push(event);
|
|
449
|
+
}
|
|
450
|
+
return events;
|
|
451
|
+
};
|
|
452
|
+
const entry = `/content/basic-gallery-remote.val.ts?p="/public/val/images-remote/image.png"`;
|
|
435
453
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
454
|
+
test("is a warning, not an error: the site serves it from public/", async () => {
|
|
455
|
+
const events = await run(false);
|
|
456
|
+
expect(events.at(-1)).toEqual({ type: "summary-success" });
|
|
457
|
+
expect(events.filter((e) => e.type === "validation-warning")).toEqual([
|
|
458
|
+
{
|
|
459
|
+
type: "validation-warning",
|
|
460
|
+
sourcePath: entry,
|
|
461
|
+
message: LOCAL_FILE_IN_REMOTE_PROJECT_WARNING,
|
|
462
|
+
files: ["/public/val/images-remote/image.png"],
|
|
463
|
+
hint: LOCAL_FILE_IN_REMOTE_PROJECT_HINT,
|
|
464
|
+
keyError: true,
|
|
465
|
+
},
|
|
466
|
+
]);
|
|
467
|
+
});
|
|
446
468
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
469
|
+
test("is an error when the file does not exist", async () => {
|
|
470
|
+
fs.rmSync(
|
|
471
|
+
path.join(tmpDir, "public", "val", "images-remote", "image.png"),
|
|
472
|
+
);
|
|
473
|
+
const events = await run(false);
|
|
474
|
+
expect(events.filter((e) => e.type === "validation-warning")).toEqual([]);
|
|
475
|
+
// The gallery's own check reports the missing file too.
|
|
476
|
+
expect(events.at(-1)).toEqual({
|
|
477
|
+
type: "summary-errors",
|
|
478
|
+
count: expect.any(Number),
|
|
479
|
+
});
|
|
480
|
+
expect(
|
|
481
|
+
events.some(
|
|
482
|
+
(e) =>
|
|
483
|
+
e.type === "validation-error" &&
|
|
484
|
+
e.sourcePath === entry &&
|
|
485
|
+
/does not exist/.test(e.message),
|
|
486
|
+
),
|
|
487
|
+
).toBe(true);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test("is an error under --fix when it cannot be uploaded: not logged in", async () => {
|
|
491
|
+
fs.rmSync(path.join(tmpDir, ".val", "pat.json"), { force: true });
|
|
492
|
+
const events = await run(true);
|
|
493
|
+
expect(events.filter((e) => e.type === "validation-warning")).toEqual([]);
|
|
494
|
+
expect(events.at(-1)).toEqual({ type: "summary-errors", count: 1 });
|
|
495
|
+
const error = events.find((e) => e.type === "validation-error");
|
|
496
|
+
expect(error && "message" in error ? error.message : "").toMatch(
|
|
497
|
+
/not logged in/,
|
|
498
|
+
);
|
|
450
499
|
});
|
|
451
|
-
const errors = events.filter((e) => e.type === "validation-error");
|
|
452
|
-
expect(
|
|
453
|
-
errors.some(
|
|
454
|
-
(e) =>
|
|
455
|
-
"message" in e &&
|
|
456
|
-
(e.message as string).includes("needs to be uploaded"),
|
|
457
|
-
),
|
|
458
|
-
).toBe(true);
|
|
459
500
|
});
|
|
460
501
|
|
|
461
502
|
test("uploads local entry in a remote gallery and rewrites the key when fix is true", async () => {
|
package/src/runValidation.ts
CHANGED
|
@@ -276,6 +276,15 @@ export async function* runValidation({
|
|
|
276
276
|
yield { type: "fix-applied", file, sourcePath };
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
if (result.warning !== undefined) {
|
|
280
|
+
yield {
|
|
281
|
+
type: "validation-warning",
|
|
282
|
+
sourcePath,
|
|
283
|
+
...result.warning,
|
|
284
|
+
...(v.keyError ? { keyError: true } : {}),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
279
288
|
if (result.fixableErrorMessage !== undefined) {
|
|
280
289
|
fileErrors += 1;
|
|
281
290
|
yield {
|
package/src/validate.test.ts
CHANGED
|
@@ -221,7 +221,7 @@ describe("validate handlers", () => {
|
|
|
221
221
|
});
|
|
222
222
|
|
|
223
223
|
describe("handleRemoteFileUpload", () => {
|
|
224
|
-
test("should return
|
|
224
|
+
test("should return a warning when fix is false", () => {
|
|
225
225
|
const ctx: FixHandlerContext = {
|
|
226
226
|
...baseContext,
|
|
227
227
|
fix: false,
|
|
@@ -232,8 +232,8 @@ describe("validate handlers", () => {
|
|
|
232
232
|
};
|
|
233
233
|
|
|
234
234
|
// Expected behavior:
|
|
235
|
-
// When fix=false, handler should return
|
|
236
|
-
//
|
|
235
|
+
// When fix=false and the file exists, handler should return a warning
|
|
236
|
+
// naming the file (see validate.warnings.test.ts), not an error
|
|
237
237
|
|
|
238
238
|
expect(ctx.fix).toBe(false);
|
|
239
239
|
});
|
|
@@ -320,7 +320,7 @@ describe("validate handlers", () => {
|
|
|
320
320
|
expect(ctx.fix).toBe(true);
|
|
321
321
|
});
|
|
322
322
|
|
|
323
|
-
test("should return
|
|
323
|
+
test("should return a warning when fix is false", () => {
|
|
324
324
|
const ctx: FixHandlerContext = {
|
|
325
325
|
...baseContext,
|
|
326
326
|
fix: false,
|