@valbuild/cli 0.146.4 → 0.148.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 +38 -0
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +295 -230
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +295 -230
- package/cli/dist/valbuild-cli-cli.esm.js +297 -232
- package/package.json +7 -7
- package/src/cli.ts +6 -0
- package/src/publish.test.ts +84 -0
- package/src/publish.ts +37 -0
- package/src/runValidation.imageset.test.ts +23 -0
- package/src/runValidation.ts +15 -1
- package/src/validate.ts +325 -303
package/src/validate.ts
CHANGED
|
@@ -45,338 +45,360 @@ export async function validate({
|
|
|
45
45
|
console.log("Prettier not found, skipping formatting");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
// errors found. Re-reads config and val files each call so it always reflects
|
|
50
|
-
// the latest state on disk (used both for one-shot and watch mode).
|
|
51
|
-
async function runOnce(): Promise<number> {
|
|
52
|
-
const valConfigFile = await findAndEvalValConfigFile(projectRoot);
|
|
53
|
-
|
|
54
|
-
const resolvedValConfigFile = valConfigFile
|
|
55
|
-
? {
|
|
56
|
-
...valConfigFile,
|
|
57
|
-
project: process.env.VAL_PROJECT || valConfigFile.project,
|
|
58
|
-
}
|
|
59
|
-
: process.env.VAL_PROJECT
|
|
60
|
-
? { project: process.env.VAL_PROJECT }
|
|
61
|
-
: undefined;
|
|
48
|
+
const runOnce = () => validateOnce({ projectRoot, fix: !!fix, prettier });
|
|
62
49
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
|
|
50
|
+
if (!watch) {
|
|
51
|
+
const totalErrors = await runOnce();
|
|
52
|
+
if (totalErrors > 0) {
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
68
57
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
reports.set(key, report);
|
|
104
|
-
}
|
|
105
|
-
return report;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
for await (const event of runValidation({
|
|
109
|
-
root: projectRoot,
|
|
110
|
-
fix: !!fix,
|
|
111
|
-
valFiles,
|
|
112
|
-
project: resolvedValConfigFile?.project,
|
|
113
|
-
remote: {
|
|
114
|
-
remoteHost: process.env.VAL_REMOTE_HOST || DEFAULT_VAL_REMOTE_HOST,
|
|
115
|
-
getSettings: (projectName, options) =>
|
|
116
|
-
getSettings(projectName, options),
|
|
117
|
-
uploadFile: (project, bucket, fileHash, fileExt, fileBuffer, options) =>
|
|
118
|
-
uploadRemoteFile(
|
|
119
|
-
process.env.VAL_CONTENT_URL || DEFAULT_CONTENT_HOST,
|
|
120
|
-
project,
|
|
121
|
-
bucket,
|
|
122
|
-
fileHash,
|
|
123
|
-
fileExt ?? "",
|
|
124
|
-
fileBuffer,
|
|
125
|
-
options,
|
|
126
|
-
),
|
|
127
|
-
},
|
|
128
|
-
fs: createDefaultValFSHost(),
|
|
129
|
-
})) {
|
|
130
|
-
switch (event.type) {
|
|
131
|
-
case "file-valid":
|
|
132
|
-
valid.push({
|
|
133
|
-
file: relFile(event.file),
|
|
134
|
-
durationMs: event.durationMs,
|
|
135
|
-
});
|
|
136
|
-
break;
|
|
137
|
-
case "file-error-count":
|
|
138
|
-
reportFor(event.file).durationMs = event.durationMs;
|
|
139
|
-
totalErrors += event.errorCount;
|
|
140
|
-
break;
|
|
141
|
-
case "validation-error":
|
|
142
|
-
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
143
|
-
severity: "error",
|
|
144
|
-
sourcePath: event.sourcePath,
|
|
145
|
-
message: event.message,
|
|
146
|
-
...(event.keyError ? { keyError: true } : {}),
|
|
147
|
-
});
|
|
148
|
-
break;
|
|
149
|
-
case "validation-fixable-error":
|
|
150
|
-
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
151
|
-
severity: event.fixable ? "fixable" : "error",
|
|
152
|
-
sourcePath: event.sourcePath,
|
|
153
|
-
message: event.message,
|
|
154
|
-
...(event.keyError ? { keyError: true } : {}),
|
|
155
|
-
});
|
|
156
|
-
break;
|
|
157
|
-
case "unknown-fix":
|
|
158
|
-
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
159
|
-
severity: "error",
|
|
160
|
-
sourcePath: event.sourcePath,
|
|
161
|
-
message: `Unknown fix: ${event.fixes.join(", ")}`,
|
|
162
|
-
...(event.keyError ? { keyError: true } : {}),
|
|
163
|
-
});
|
|
164
|
-
break;
|
|
165
|
-
case "unregistered-module":
|
|
166
|
-
skipped.push(event.file);
|
|
167
|
-
break;
|
|
168
|
-
case "fatal-error":
|
|
169
|
-
// No sourcePath for fatal errors; group by file, render message only.
|
|
170
|
-
reportFor(event.file).diagnostics.push({
|
|
171
|
-
severity: "error",
|
|
172
|
-
sourcePath: event.file,
|
|
173
|
-
message: event.message,
|
|
174
|
-
});
|
|
175
|
-
break;
|
|
176
|
-
case "fix-applied":
|
|
177
|
-
console.log(
|
|
178
|
-
picocolors.yellow("⚠"),
|
|
179
|
-
"Applied fix for",
|
|
180
|
-
event.sourcePath,
|
|
181
|
-
);
|
|
182
|
-
fixedFiles.add(event.file);
|
|
183
|
-
break;
|
|
184
|
-
case "remote-uploading":
|
|
185
|
-
console.log(
|
|
186
|
-
picocolors.yellow("⚠"),
|
|
187
|
-
`Uploading remote file: '${event.ref}'...`,
|
|
188
|
-
);
|
|
189
|
-
break;
|
|
190
|
-
case "remote-uploaded":
|
|
191
|
-
console.log(
|
|
192
|
-
picocolors.green("✔"),
|
|
193
|
-
`Completed upload of remote file: '${event.ref}'`,
|
|
194
|
-
);
|
|
195
|
-
break;
|
|
196
|
-
case "remote-already-uploaded":
|
|
197
|
-
console.log(
|
|
198
|
-
picocolors.yellow("⚠"),
|
|
199
|
-
`Remote file ${event.filePath} already uploaded`,
|
|
200
|
-
);
|
|
201
|
-
break;
|
|
202
|
-
case "remote-downloading":
|
|
203
|
-
console.log(
|
|
204
|
-
picocolors.yellow("⚠"),
|
|
205
|
-
`Downloading remote file in ${event.sourcePath}...`,
|
|
206
|
-
);
|
|
207
|
-
break;
|
|
208
|
-
case "summary-errors":
|
|
209
|
-
case "summary-success":
|
|
210
|
-
break;
|
|
58
|
+
await watchAndValidate(projectRoot, runOnce);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* One validation pass over the project, printed as `val validate` prints it,
|
|
63
|
+
* returning the number of errors found.
|
|
64
|
+
*
|
|
65
|
+
* Re-reads config and val files on each call so it always reflects the latest
|
|
66
|
+
* state on disk: `val validate` calls it once or on every change (`--watch`),
|
|
67
|
+
* and `val publish` calls it before it builds anything.
|
|
68
|
+
*/
|
|
69
|
+
export async function validateOnce({
|
|
70
|
+
projectRoot,
|
|
71
|
+
fix,
|
|
72
|
+
prettier,
|
|
73
|
+
blockingOnly = false,
|
|
74
|
+
}: {
|
|
75
|
+
projectRoot: string;
|
|
76
|
+
fix: boolean;
|
|
77
|
+
/** Formats the files a fix rewrote. Absent: they are left as written. */
|
|
78
|
+
prettier?: typeof import("prettier");
|
|
79
|
+
/**
|
|
80
|
+
* Count only the errors that would hold a publish back in the Studio, not
|
|
81
|
+
* the ones it leaves to the server to repair on save (`isRepairedOnSave`):
|
|
82
|
+
* what `val publish` refuses on. Every error is still printed.
|
|
83
|
+
*/
|
|
84
|
+
blockingOnly?: boolean;
|
|
85
|
+
}): Promise<number> {
|
|
86
|
+
const valConfigFile = await findAndEvalValConfigFile(projectRoot);
|
|
87
|
+
|
|
88
|
+
const resolvedValConfigFile = valConfigFile
|
|
89
|
+
? {
|
|
90
|
+
...valConfigFile,
|
|
91
|
+
project: process.env.VAL_PROJECT || valConfigFile.project,
|
|
211
92
|
}
|
|
93
|
+
: process.env.VAL_PROJECT
|
|
94
|
+
? { project: process.env.VAL_PROJECT }
|
|
95
|
+
: undefined;
|
|
96
|
+
|
|
97
|
+
console.log(
|
|
98
|
+
picocolors.greenBright(
|
|
99
|
+
`Validating project${resolvedValConfigFile?.project ? ` '${picocolors.inverse(resolvedValConfigFile.project)}'` : ""}...`,
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const valFiles: string[] = await glob("**/*.val.{js,ts}", {
|
|
104
|
+
ignore: ["node_modules/**"],
|
|
105
|
+
cwd: projectRoot,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
console.log(picocolors.greenBright(`Found ${valFiles.length} files...`));
|
|
109
|
+
|
|
110
|
+
const fixedFiles = new Set<string>();
|
|
111
|
+
let totalErrors = 0;
|
|
112
|
+
let repairedOnSave = 0;
|
|
113
|
+
|
|
114
|
+
// Caches each val file's parsed source so files are read/parsed at most once
|
|
115
|
+
// per pass when resolving sourcePaths to file locations and code frames.
|
|
116
|
+
const sourceFileCache: SourceFileCache = new Map();
|
|
117
|
+
|
|
118
|
+
// Diagnostics are buffered per module (keyed by relative file path) so we can
|
|
119
|
+
// render them grouped and prioritised after the run, rather than streaming
|
|
120
|
+
// them out interleaved. Transient progress (remote/fix-applied) still streams
|
|
121
|
+
// live below.
|
|
122
|
+
const reports = new Map<string, ModuleReport>();
|
|
123
|
+
const valid: { file: string; durationMs: number }[] = [];
|
|
124
|
+
const skipped: string[] = [];
|
|
125
|
+
|
|
126
|
+
// Relative file path (no leading slash), matching the code frame's
|
|
127
|
+
// relativeFile so headers, diagnostics and frames all agree.
|
|
128
|
+
const relFile = (file: string) => file.replace(/^\//, "");
|
|
129
|
+
// The module a sourcePath/file belongs to is the part before the `?p=...`.
|
|
130
|
+
const moduleOf = (sourcePathOrFile: string) =>
|
|
131
|
+
relFile(sourcePathOrFile.split("?")[0]);
|
|
132
|
+
|
|
133
|
+
const reportFor = (file: string): ModuleReport => {
|
|
134
|
+
const key = relFile(file);
|
|
135
|
+
let report = reports.get(key);
|
|
136
|
+
if (!report) {
|
|
137
|
+
report = { file: key, durationMs: 0, diagnostics: [] };
|
|
138
|
+
reports.set(key, report);
|
|
139
|
+
}
|
|
140
|
+
return report;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
for await (const event of runValidation({
|
|
144
|
+
root: projectRoot,
|
|
145
|
+
fix,
|
|
146
|
+
valFiles,
|
|
147
|
+
project: resolvedValConfigFile?.project,
|
|
148
|
+
remote: {
|
|
149
|
+
remoteHost: process.env.VAL_REMOTE_HOST || DEFAULT_VAL_REMOTE_HOST,
|
|
150
|
+
getSettings: (projectName, options) => getSettings(projectName, options),
|
|
151
|
+
uploadFile: (project, bucket, fileHash, fileExt, fileBuffer, options) =>
|
|
152
|
+
uploadRemoteFile(
|
|
153
|
+
process.env.VAL_CONTENT_URL || DEFAULT_CONTENT_HOST,
|
|
154
|
+
project,
|
|
155
|
+
bucket,
|
|
156
|
+
fileHash,
|
|
157
|
+
fileExt ?? "",
|
|
158
|
+
fileBuffer,
|
|
159
|
+
options,
|
|
160
|
+
),
|
|
161
|
+
},
|
|
162
|
+
fs: createDefaultValFSHost(),
|
|
163
|
+
})) {
|
|
164
|
+
switch (event.type) {
|
|
165
|
+
case "file-valid":
|
|
166
|
+
valid.push({
|
|
167
|
+
file: relFile(event.file),
|
|
168
|
+
durationMs: event.durationMs,
|
|
169
|
+
});
|
|
170
|
+
break;
|
|
171
|
+
case "file-error-count":
|
|
172
|
+
reportFor(event.file).durationMs = event.durationMs;
|
|
173
|
+
totalErrors += event.errorCount;
|
|
174
|
+
break;
|
|
175
|
+
case "validation-error":
|
|
176
|
+
if (event.repairedOnSave) repairedOnSave += 1;
|
|
177
|
+
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
178
|
+
severity: "error",
|
|
179
|
+
sourcePath: event.sourcePath,
|
|
180
|
+
message: event.message,
|
|
181
|
+
...(event.keyError ? { keyError: true } : {}),
|
|
182
|
+
});
|
|
183
|
+
break;
|
|
184
|
+
case "validation-fixable-error":
|
|
185
|
+
if (event.repairedOnSave) repairedOnSave += 1;
|
|
186
|
+
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
187
|
+
severity: event.fixable ? "fixable" : "error",
|
|
188
|
+
sourcePath: event.sourcePath,
|
|
189
|
+
message: event.message,
|
|
190
|
+
...(event.keyError ? { keyError: true } : {}),
|
|
191
|
+
});
|
|
192
|
+
break;
|
|
193
|
+
case "unknown-fix":
|
|
194
|
+
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
195
|
+
severity: "error",
|
|
196
|
+
sourcePath: event.sourcePath,
|
|
197
|
+
message: `Unknown fix: ${event.fixes.join(", ")}`,
|
|
198
|
+
...(event.keyError ? { keyError: true } : {}),
|
|
199
|
+
});
|
|
200
|
+
break;
|
|
201
|
+
case "unregistered-module":
|
|
202
|
+
skipped.push(event.file);
|
|
203
|
+
break;
|
|
204
|
+
case "fatal-error":
|
|
205
|
+
// No sourcePath for fatal errors; group by file, render message only.
|
|
206
|
+
reportFor(event.file).diagnostics.push({
|
|
207
|
+
severity: "error",
|
|
208
|
+
sourcePath: event.file,
|
|
209
|
+
message: event.message,
|
|
210
|
+
});
|
|
211
|
+
break;
|
|
212
|
+
case "fix-applied":
|
|
213
|
+
console.log(
|
|
214
|
+
picocolors.yellow("⚠"),
|
|
215
|
+
"Applied fix for",
|
|
216
|
+
event.sourcePath,
|
|
217
|
+
);
|
|
218
|
+
fixedFiles.add(event.file);
|
|
219
|
+
break;
|
|
220
|
+
case "remote-uploading":
|
|
221
|
+
console.log(
|
|
222
|
+
picocolors.yellow("⚠"),
|
|
223
|
+
`Uploading remote file: '${event.ref}'...`,
|
|
224
|
+
);
|
|
225
|
+
break;
|
|
226
|
+
case "remote-uploaded":
|
|
227
|
+
console.log(
|
|
228
|
+
picocolors.green("✔"),
|
|
229
|
+
`Completed upload of remote file: '${event.ref}'`,
|
|
230
|
+
);
|
|
231
|
+
break;
|
|
232
|
+
case "remote-already-uploaded":
|
|
233
|
+
console.log(
|
|
234
|
+
picocolors.yellow("⚠"),
|
|
235
|
+
`Remote file ${event.filePath} already uploaded`,
|
|
236
|
+
);
|
|
237
|
+
break;
|
|
238
|
+
case "remote-downloading":
|
|
239
|
+
console.log(
|
|
240
|
+
picocolors.yellow("⚠"),
|
|
241
|
+
`Downloading remote file in ${event.sourcePath}...`,
|
|
242
|
+
);
|
|
243
|
+
break;
|
|
244
|
+
case "summary-errors":
|
|
245
|
+
case "summary-success":
|
|
246
|
+
break;
|
|
212
247
|
}
|
|
248
|
+
}
|
|
213
249
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
250
|
+
// Renders a module's diagnostics under a single left "│" gutter bar, with the
|
|
251
|
+
// file name on top and blank gutter lines for air. Output flows least- to
|
|
252
|
+
// most-actionable top-to-bottom, so fixable diagnostics are shown last (at the
|
|
253
|
+
// bottom, nearest the prompt).
|
|
254
|
+
const renderModule = (report: ModuleReport) => {
|
|
255
|
+
const bar = picocolors.dim("│");
|
|
256
|
+
const diagnostics = [...report.diagnostics].sort((a, b) =>
|
|
257
|
+
a.severity === b.severity ? 0 : a.severity === "fixable" ? 1 : -1,
|
|
258
|
+
);
|
|
259
|
+
const fixableCount = diagnostics.filter(
|
|
260
|
+
(d) => d.severity === "fixable",
|
|
261
|
+
).length;
|
|
262
|
+
const total = diagnostics.length;
|
|
263
|
+
const hasError = fixableCount < total;
|
|
264
|
+
const symbol = hasError ? picocolors.red("✘") : picocolors.yellow("⚠");
|
|
265
|
+
let label: string;
|
|
266
|
+
if (fixableCount === total) {
|
|
267
|
+
label = `${fixableCount} fixable`;
|
|
268
|
+
} else if (fixableCount > 0) {
|
|
269
|
+
label = `${total} error${total > 1 ? "s" : ""} (${fixableCount} fixable)`;
|
|
270
|
+
} else {
|
|
271
|
+
label = `${total} error${total > 1 ? "s" : ""}`;
|
|
272
|
+
}
|
|
273
|
+
console.log(
|
|
274
|
+
`${picocolors.bold(report.file)} ${symbol} ${label} ${picocolors.dim(
|
|
275
|
+
`(${report.durationMs}ms)`,
|
|
276
|
+
)}`,
|
|
277
|
+
);
|
|
278
|
+
for (const d of diagnostics) {
|
|
279
|
+
const target = d.keyError ? "key" : "value";
|
|
280
|
+
const dsym =
|
|
281
|
+
d.severity === "fixable" ? picocolors.yellow("⚠") : picocolors.red("✘");
|
|
282
|
+
const parts = sourcePathToLocationParts(
|
|
283
|
+
d.sourcePath,
|
|
284
|
+
projectRoot,
|
|
285
|
+
sourceFileCache,
|
|
286
|
+
target,
|
|
222
287
|
);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (fixableCount === total) {
|
|
231
|
-
label = `${fixableCount} fixable`;
|
|
232
|
-
} else if (fixableCount > 0) {
|
|
233
|
-
label = `${total} error${total > 1 ? "s" : ""} (${fixableCount} fixable)`;
|
|
288
|
+
console.log(bar);
|
|
289
|
+
if (parts) {
|
|
290
|
+
// `file:line:col` (no key/value label) so VS Code's terminal links it.
|
|
291
|
+
console.log(
|
|
292
|
+
`${bar} ${dsym} ${parts.relativeFile}:${parts.line}:${parts.character}`,
|
|
293
|
+
);
|
|
294
|
+
console.log(`${bar} ${d.message}`);
|
|
234
295
|
} else {
|
|
235
|
-
|
|
296
|
+
console.log(`${bar} ${dsym} ${d.message}`);
|
|
236
297
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
298
|
+
const frame = sourcePathToCodeFrame(
|
|
299
|
+
d.sourcePath,
|
|
300
|
+
projectRoot,
|
|
301
|
+
sourceFileCache,
|
|
302
|
+
target,
|
|
241
303
|
);
|
|
242
|
-
|
|
243
|
-
const target = d.keyError ? "key" : "value";
|
|
244
|
-
const dsym =
|
|
245
|
-
d.severity === "fixable"
|
|
246
|
-
? picocolors.yellow("⚠")
|
|
247
|
-
: picocolors.red("✘");
|
|
248
|
-
const parts = sourcePathToLocationParts(
|
|
249
|
-
d.sourcePath,
|
|
250
|
-
projectRoot,
|
|
251
|
-
sourceFileCache,
|
|
252
|
-
target,
|
|
253
|
-
);
|
|
304
|
+
if (frame !== undefined) {
|
|
254
305
|
console.log(bar);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
console.log(
|
|
258
|
-
`${bar} ${dsym} ${parts.relativeFile}:${parts.line}:${parts.character}`,
|
|
259
|
-
);
|
|
260
|
-
console.log(`${bar} ${d.message}`);
|
|
261
|
-
} else {
|
|
262
|
-
console.log(`${bar} ${dsym} ${d.message}`);
|
|
263
|
-
}
|
|
264
|
-
const frame = sourcePathToCodeFrame(
|
|
265
|
-
d.sourcePath,
|
|
266
|
-
projectRoot,
|
|
267
|
-
sourceFileCache,
|
|
268
|
-
target,
|
|
269
|
-
);
|
|
270
|
-
if (frame !== undefined) {
|
|
271
|
-
console.log(bar);
|
|
272
|
-
for (const frameLine of frame.split("\n")) {
|
|
273
|
-
console.log(`${bar} ${frameLine}`);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (d.severity === "fixable") {
|
|
277
|
-
console.log(
|
|
278
|
-
`${bar} ${picocolors.dim("→ run with --fix to apply")}`,
|
|
279
|
-
);
|
|
306
|
+
for (const frameLine of frame.split("\n")) {
|
|
307
|
+
console.log(`${bar} ${frameLine}`);
|
|
280
308
|
}
|
|
281
309
|
}
|
|
282
|
-
|
|
283
|
-
console.log("");
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
// Format the files that had fixes applied, the way the project formats
|
|
287
|
-
// everything else. See `formatFixedFiles` for why this cannot simply be
|
|
288
|
-
// `prettier.format(code, { filepath })`.
|
|
289
|
-
//
|
|
290
|
-
// A file that could not be formatted is reported and skipped rather than
|
|
291
|
-
// thrown: the fix itself landed, and aborting here would leave the fix
|
|
292
|
-
// applied and the report below unwritten.
|
|
293
|
-
if (prettier) {
|
|
294
|
-
const { failures } = await formatFixedFiles(
|
|
295
|
-
prettier,
|
|
296
|
-
projectRoot,
|
|
297
|
-
fixedFiles,
|
|
298
|
-
);
|
|
299
|
-
for (const failure of failures) {
|
|
310
|
+
if (d.severity === "fixable") {
|
|
300
311
|
console.log(
|
|
301
|
-
picocolors.
|
|
302
|
-
`Could not format ${relFile(failure.file)}: ${failure.message}`,
|
|
312
|
+
`${bar} ${picocolors.dim("→ run with --fix to apply")}`,
|
|
303
313
|
);
|
|
304
314
|
}
|
|
305
315
|
}
|
|
316
|
+
console.log(bar);
|
|
317
|
+
console.log("");
|
|
318
|
+
};
|
|
306
319
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
320
|
+
// Format the files that had fixes applied, the way the project formats
|
|
321
|
+
// everything else. See `formatFixedFiles` for why this cannot simply be
|
|
322
|
+
// `prettier.format(code, { filepath })`.
|
|
323
|
+
//
|
|
324
|
+
// A file that could not be formatted is reported and skipped rather than
|
|
325
|
+
// thrown: the fix itself landed, and aborting here would leave the fix
|
|
326
|
+
// applied and the report below unwritten.
|
|
327
|
+
if (prettier) {
|
|
328
|
+
const { failures } = await formatFixedFiles(
|
|
329
|
+
prettier,
|
|
330
|
+
projectRoot,
|
|
331
|
+
fixedFiles,
|
|
316
332
|
);
|
|
317
|
-
|
|
318
|
-
for (const v of valid) {
|
|
319
|
-
console.log(
|
|
320
|
-
picocolors.green("✔"),
|
|
321
|
-
picocolors.dim(`${v.file} valid (${v.durationMs}ms)`),
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
for (const file of skipped) {
|
|
333
|
+
for (const failure of failures) {
|
|
325
334
|
console.log(
|
|
326
335
|
picocolors.yellow("⚠"),
|
|
327
|
-
|
|
336
|
+
`Could not format ${relFile(failure.file)}: ${failure.message}`,
|
|
328
337
|
);
|
|
329
338
|
}
|
|
339
|
+
}
|
|
330
340
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
341
|
+
// Render the grouped report least- to most-actionable, top-to-bottom, so the
|
|
342
|
+
// most important things end up at the bottom nearest the prompt: valid files
|
|
343
|
+
// and skipped modules first, then error-only modules, then fixable modules.
|
|
344
|
+
const allReports = [...reports.values()];
|
|
345
|
+
const fixableModules = allReports.filter((r) =>
|
|
346
|
+
r.diagnostics.some((d) => d.severity === "fixable"),
|
|
347
|
+
);
|
|
348
|
+
const errorModules = allReports.filter(
|
|
349
|
+
(r) => !r.diagnostics.some((d) => d.severity === "fixable"),
|
|
350
|
+
);
|
|
337
351
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
352
|
+
for (const v of valid) {
|
|
353
|
+
console.log(
|
|
354
|
+
picocolors.green("✔"),
|
|
355
|
+
picocolors.dim(`${v.file} valid (${v.durationMs}ms)`),
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
for (const file of skipped) {
|
|
359
|
+
console.log(
|
|
360
|
+
picocolors.yellow("⚠"),
|
|
361
|
+
picocolors.dim(`/${file} is not registered in val.modules - skipping`),
|
|
342
362
|
);
|
|
343
|
-
if (totalErrors > 0) {
|
|
344
|
-
let summary = `${totalErrors} error${totalErrors > 1 ? "s" : ""}`;
|
|
345
|
-
if (fixableTotal > 0) {
|
|
346
|
-
summary += ` (${fixableTotal} fixable)`;
|
|
347
|
-
}
|
|
348
|
-
summary += ` across ${allReports.length} file${
|
|
349
|
-
allReports.length > 1 ? "s" : ""
|
|
350
|
-
}`;
|
|
351
|
-
if (valid.length > 0) {
|
|
352
|
-
summary += ` · ${valid.length} valid`;
|
|
353
|
-
}
|
|
354
|
-
if (skipped.length > 0) {
|
|
355
|
-
summary += ` · ${skipped.length} skipped`;
|
|
356
|
-
}
|
|
357
|
-
console.log(picocolors.red("✘"), summary);
|
|
358
|
-
} else {
|
|
359
|
-
let summary = "No validation errors found";
|
|
360
|
-
if (valid.length > 0) {
|
|
361
|
-
summary += ` · ${valid.length} valid`;
|
|
362
|
-
}
|
|
363
|
-
if (skipped.length > 0) {
|
|
364
|
-
summary += ` · ${skipped.length} skipped`;
|
|
365
|
-
}
|
|
366
|
-
console.log(picocolors.green("✔"), summary);
|
|
367
|
-
}
|
|
368
|
-
return totalErrors;
|
|
369
363
|
}
|
|
370
364
|
|
|
371
|
-
if (
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return;
|
|
365
|
+
if (allReports.length > 0) {
|
|
366
|
+
console.log("");
|
|
367
|
+
}
|
|
368
|
+
for (const report of [...errorModules, ...fixableModules]) {
|
|
369
|
+
renderModule(report);
|
|
377
370
|
}
|
|
378
371
|
|
|
379
|
-
|
|
372
|
+
const fixableTotal = allReports.reduce(
|
|
373
|
+
(n, r) => n + r.diagnostics.filter((d) => d.severity === "fixable").length,
|
|
374
|
+
0,
|
|
375
|
+
);
|
|
376
|
+
if (totalErrors > 0) {
|
|
377
|
+
let summary = `${totalErrors} error${totalErrors > 1 ? "s" : ""}`;
|
|
378
|
+
if (fixableTotal > 0) {
|
|
379
|
+
summary += ` (${fixableTotal} fixable)`;
|
|
380
|
+
}
|
|
381
|
+
summary += ` across ${allReports.length} file${
|
|
382
|
+
allReports.length > 1 ? "s" : ""
|
|
383
|
+
}`;
|
|
384
|
+
if (valid.length > 0) {
|
|
385
|
+
summary += ` · ${valid.length} valid`;
|
|
386
|
+
}
|
|
387
|
+
if (skipped.length > 0) {
|
|
388
|
+
summary += ` · ${skipped.length} skipped`;
|
|
389
|
+
}
|
|
390
|
+
console.log(picocolors.red("✘"), summary);
|
|
391
|
+
} else {
|
|
392
|
+
let summary = "No validation errors found";
|
|
393
|
+
if (valid.length > 0) {
|
|
394
|
+
summary += ` · ${valid.length} valid`;
|
|
395
|
+
}
|
|
396
|
+
if (skipped.length > 0) {
|
|
397
|
+
summary += ` · ${skipped.length} skipped`;
|
|
398
|
+
}
|
|
399
|
+
console.log(picocolors.green("✔"), summary);
|
|
400
|
+
}
|
|
401
|
+
return blockingOnly ? totalErrors - repairedOnSave : totalErrors;
|
|
380
402
|
}
|
|
381
403
|
|
|
382
404
|
// Directory names anywhere in the path that should never be watched.
|