@vizejs/nuxt 0.312.0 → 0.315.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addons-ecITHT3X.d.mts +369 -0
- package/dist/generation-bpnsF4y-.mjs +1201 -0
- package/dist/index.d.mts +11 -242
- package/dist/index.mjs +8 -4
- package/dist/lint/index.d.mts +212 -0
- package/dist/lint/index.mjs +3 -0
- package/package.json +19 -5
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { glob, lstat, mkdir, open, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
6
|
+
import { Worker, isMainThread, parentPort } from "node:worker_threads";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
9
|
+
import { buildNuxtLintPlan, collectNuxtLintDirs, resolveNuxtLintFeatures } from "@vizejs/nuxt-lint-config";
|
|
10
|
+
import { createServer } from "node:http";
|
|
11
|
+
//#region src/lint/checker/options.ts
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the engine-neutral part of `@nuxt/eslint`'s checker contract.
|
|
14
|
+
*
|
|
15
|
+
* `configType` and `eslintPath` intentionally do not exist: they choose an
|
|
16
|
+
* ESLint implementation, while this checker always executes oxlint + Patina.
|
|
17
|
+
*/
|
|
18
|
+
function resolveNuxtLintCheckerOptions(checker, project) {
|
|
19
|
+
if (checker !== true && (checker === false || checker == null)) return false;
|
|
20
|
+
const overrides = typeof checker === "object" ? checker : {};
|
|
21
|
+
return {
|
|
22
|
+
cache: overrides.cache ?? true,
|
|
23
|
+
include: [...overrides.include ?? [`${project.srcDir}/**/*.{js,jsx,ts,tsx,vue}`]],
|
|
24
|
+
exclude: [...overrides.exclude ?? ["**/node_modules/**", project.buildDir]],
|
|
25
|
+
formatter: overrides.formatter ?? "stylish",
|
|
26
|
+
lintOnStart: overrides.lintOnStart ?? true,
|
|
27
|
+
emitWarning: overrides.emitWarning ?? true,
|
|
28
|
+
emitError: overrides.emitError ?? true,
|
|
29
|
+
fix: overrides.fix ?? false
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/lint/checker/filter.ts
|
|
34
|
+
const GLOB_TOKEN = /[*?[\]{}()]/u;
|
|
35
|
+
function normalize(value) {
|
|
36
|
+
const normalized = value.replaceAll("\\", "/").replace(/\/{2,}/gu, "/");
|
|
37
|
+
if (normalized === "/" || /^[A-Za-z]:\/$/u.test(normalized)) return normalized;
|
|
38
|
+
return normalized.replace(/\/$/u, "");
|
|
39
|
+
}
|
|
40
|
+
function relativeToRoot(file, rootDir) {
|
|
41
|
+
const root = normalize(rootDir);
|
|
42
|
+
const absolute = normalize(file);
|
|
43
|
+
const prefix = `${root}/`;
|
|
44
|
+
if (absolute.startsWith(prefix)) return absolute.slice(prefix.length);
|
|
45
|
+
const normalized = normalize(path.relative(rootDir, file));
|
|
46
|
+
return normalized === ".." || normalized.startsWith("../") ? void 0 : normalized;
|
|
47
|
+
}
|
|
48
|
+
function matchesDirectory(candidate, pattern) {
|
|
49
|
+
if (GLOB_TOKEN.test(pattern)) return false;
|
|
50
|
+
return candidate === pattern || candidate.startsWith(`${pattern}/`);
|
|
51
|
+
}
|
|
52
|
+
function matchesPattern(absolute, relative, rawPattern) {
|
|
53
|
+
const pattern = normalize(rawPattern);
|
|
54
|
+
const candidates = path.isAbsolute(pattern) || /^[A-Za-z]:\//u.test(pattern) ? [absolute] : relative === void 0 ? [] : [relative];
|
|
55
|
+
for (const candidate of candidates) if (matchesDirectory(candidate, pattern) || path.matchesGlob(candidate, pattern)) return true;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
/** Whether one watcher path belongs to the checker include/exclude contract. */
|
|
59
|
+
function matchesNuxtLintCheckerFile(file, rootDir, options) {
|
|
60
|
+
const absolute = normalize(file);
|
|
61
|
+
const relative = relativeToRoot(file, rootDir);
|
|
62
|
+
if (options.exclude.some((pattern) => matchesPattern(absolute, relative, pattern))) return false;
|
|
63
|
+
return options.include.some((pattern) => matchesPattern(absolute, relative, pattern));
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/lint/checker/worker.ts
|
|
67
|
+
function resolveOxlintEntrypoint(cwd) {
|
|
68
|
+
try {
|
|
69
|
+
const manifest = createRequire(path.join(cwd, "package.json")).resolve("oxlint/package.json");
|
|
70
|
+
return path.join(path.dirname(manifest), "bin", "oxlint");
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new Error(`Unable to resolve oxlint from ${cwd}. Install oxlint before enabling the Nuxt lint checker.`, { cause: error });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function projectPattern(pattern, cwd) {
|
|
76
|
+
if (!path.isAbsolute(pattern)) return pattern;
|
|
77
|
+
const relative = path.relative(cwd, pattern);
|
|
78
|
+
if (path.isAbsolute(relative) || relative === ".." || relative.startsWith(`..${path.sep}`)) return pattern;
|
|
79
|
+
return relative.split(path.sep).join("/") || ".";
|
|
80
|
+
}
|
|
81
|
+
function expandBraces(pattern) {
|
|
82
|
+
const match = /\{([^{}]+)\}/u.exec(pattern);
|
|
83
|
+
if (!match || match.index === void 0) return [pattern];
|
|
84
|
+
const before = pattern.slice(0, match.index);
|
|
85
|
+
const after = pattern.slice(match.index + match[0].length);
|
|
86
|
+
return match[1].split(",").flatMap((part) => expandBraces(`${before}${part}${after}`));
|
|
87
|
+
}
|
|
88
|
+
function oxlintPatterns(pattern, cwd) {
|
|
89
|
+
const patterns = expandBraces(projectPattern(pattern, cwd)).flatMap((expanded) => {
|
|
90
|
+
const shallow = expanded.replace(/(^|\/)\*\*\//gu, "$1");
|
|
91
|
+
return shallow === expanded ? [expanded] : [expanded, shallow];
|
|
92
|
+
});
|
|
93
|
+
return [...new Set(patterns)];
|
|
94
|
+
}
|
|
95
|
+
async function oxlintTargets(pattern, cwd) {
|
|
96
|
+
const rebased = projectPattern(pattern, cwd);
|
|
97
|
+
if (rebased.endsWith("/**/*.{js,jsx,ts,tsx,vue}")) return [rebased.slice(0, -25) || "."];
|
|
98
|
+
if (!/[*?[\]{}()]/u.test(rebased)) return [rebased];
|
|
99
|
+
const matches = await Array.fromAsync(glob(rebased, { cwd }));
|
|
100
|
+
return matches.length > 0 ? matches : oxlintPatterns(pattern, cwd);
|
|
101
|
+
}
|
|
102
|
+
async function checkerArgs(task) {
|
|
103
|
+
const args = [
|
|
104
|
+
"--config",
|
|
105
|
+
task.configFile,
|
|
106
|
+
"--format",
|
|
107
|
+
"json",
|
|
108
|
+
"--no-error-on-unmatched-pattern"
|
|
109
|
+
];
|
|
110
|
+
if (task.fix) args.push("--fix");
|
|
111
|
+
for (const pattern of task.exclude) for (const exclude of oxlintPatterns(pattern, task.cwd)) args.push("--ignore-pattern", exclude);
|
|
112
|
+
for (const target of task.targets) args.push(...await oxlintTargets(target, task.cwd));
|
|
113
|
+
return args;
|
|
114
|
+
}
|
|
115
|
+
function spawnOxlint(node, entrypoint, args, cwd) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const child = spawn(node, [entrypoint, ...args], {
|
|
118
|
+
cwd,
|
|
119
|
+
stdio: [
|
|
120
|
+
"ignore",
|
|
121
|
+
"pipe",
|
|
122
|
+
"pipe"
|
|
123
|
+
]
|
|
124
|
+
});
|
|
125
|
+
const stdout = [];
|
|
126
|
+
const stderr = [];
|
|
127
|
+
child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
|
|
128
|
+
child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
|
|
129
|
+
child.on("error", (error) => {
|
|
130
|
+
reject(new Error(`Failed to start oxlint at ${entrypoint}: ${error.message}`, { cause: error }));
|
|
131
|
+
});
|
|
132
|
+
child.on("close", (status) => {
|
|
133
|
+
const output = Buffer.concat(stdout).toString("utf8");
|
|
134
|
+
if (output.trim()) {
|
|
135
|
+
resolve(output);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const details = Buffer.concat(stderr).toString("utf8").trim();
|
|
139
|
+
if (status === 0) {
|
|
140
|
+
resolve("{\"diagnostics\":[]}\n");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
reject(/* @__PURE__ */ new Error(`oxlint exited with ${String(status)} without JSON output${details ? `: ${details}` : ""}`));
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
async function executeOxlint(entrypoint, args, cwd) {
|
|
148
|
+
if (!existsSync(entrypoint)) throw new Error(`Failed to start oxlint: entrypoint does not exist: ${entrypoint}`);
|
|
149
|
+
const node = existsSync(process.execPath) ? process.execPath : "node";
|
|
150
|
+
try {
|
|
151
|
+
return await spawnOxlint(node, entrypoint, args, cwd);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const cause = error.cause;
|
|
154
|
+
if (node !== "node" && cause?.code === "ENOENT") return spawnOxlint("node", entrypoint, args, cwd);
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function diagnosticKind(diagnostic) {
|
|
159
|
+
if (diagnostic.severity === 2 || diagnostic.severity === "error") return "error";
|
|
160
|
+
if (diagnostic.severity === 1 || diagnostic.severity === "warn" || diagnostic.severity === "warning") return "warning";
|
|
161
|
+
}
|
|
162
|
+
function readableDiagnostics(diagnostics, formatter) {
|
|
163
|
+
const unix = formatter === "unix";
|
|
164
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
165
|
+
const span = diagnostic.labels?.find((label) => label.span)?.span;
|
|
166
|
+
const file = diagnostic.filename ?? diagnostic.filePath ?? "<unknown>";
|
|
167
|
+
const line = span?.line ?? 1;
|
|
168
|
+
const column = span?.column ?? 1;
|
|
169
|
+
const severity = diagnosticKind(diagnostic) ?? "warning";
|
|
170
|
+
const message = diagnostic.message ?? "lint diagnostic";
|
|
171
|
+
const code = diagnostic.code ? ` (${diagnostic.code})` : "";
|
|
172
|
+
return unix ? `${file}:${line}:${column}: ${message} [${severity}${code}]` : `${file}:${line}:${column} ${severity} ${message}${code}`;
|
|
173
|
+
});
|
|
174
|
+
return lines.length > 0 ? `${lines.join("\n")}\n` : "";
|
|
175
|
+
}
|
|
176
|
+
function resultFromPayload(payload, task) {
|
|
177
|
+
const diagnostics = payload.diagnostics.filter((diagnostic) => {
|
|
178
|
+
const kind = diagnosticKind(diagnostic);
|
|
179
|
+
return kind === "error" && task.emitError || kind === "warning" && task.emitWarning;
|
|
180
|
+
});
|
|
181
|
+
const hasErrors = diagnostics.some((diagnostic) => diagnosticKind(diagnostic) === "error");
|
|
182
|
+
const hasWarnings = diagnostics.some((diagnostic) => diagnosticKind(diagnostic) === "warning");
|
|
183
|
+
const output = task.formatter === "json" ? diagnostics.length > 0 ? `${JSON.stringify({
|
|
184
|
+
...payload,
|
|
185
|
+
diagnostics
|
|
186
|
+
})}\n` : "" : readableDiagnostics(diagnostics, task.formatter);
|
|
187
|
+
return {
|
|
188
|
+
diagnosticCount: diagnostics.length,
|
|
189
|
+
hasErrors,
|
|
190
|
+
hasWarnings,
|
|
191
|
+
output
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/** Run one checker pass. This function executes inside the long-lived worker. */
|
|
195
|
+
async function runNuxtLintCheckerTask(task) {
|
|
196
|
+
if (!task.emitError && !task.emitWarning && !task.fix) return {
|
|
197
|
+
diagnosticCount: 0,
|
|
198
|
+
hasErrors: false,
|
|
199
|
+
hasWarnings: false,
|
|
200
|
+
output: ""
|
|
201
|
+
};
|
|
202
|
+
const stdout = await executeOxlint(task.oxlintEntrypoint ?? resolveOxlintEntrypoint(task.cwd), await checkerArgs(task), task.cwd);
|
|
203
|
+
let payload;
|
|
204
|
+
try {
|
|
205
|
+
payload = JSON.parse(stdout);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
throw new Error(`oxlint returned invalid JSON: ${stdout.slice(0, 240)}`, { cause: error });
|
|
208
|
+
}
|
|
209
|
+
if (!Array.isArray(payload.diagnostics)) throw new Error("oxlint JSON has no diagnostics array");
|
|
210
|
+
return resultFromPayload(payload, task);
|
|
211
|
+
}
|
|
212
|
+
function serializeError(error) {
|
|
213
|
+
if (error instanceof Error) return {
|
|
214
|
+
message: error.message,
|
|
215
|
+
stack: error.stack
|
|
216
|
+
};
|
|
217
|
+
return { message: String(error) };
|
|
218
|
+
}
|
|
219
|
+
if (!isMainThread && parentPort) parentPort.on("message", async ({ id, task }) => {
|
|
220
|
+
try {
|
|
221
|
+
parentPort?.postMessage({
|
|
222
|
+
id,
|
|
223
|
+
result: await runNuxtLintCheckerTask(task)
|
|
224
|
+
});
|
|
225
|
+
} catch (error) {
|
|
226
|
+
parentPort?.postMessage({
|
|
227
|
+
error: serializeError(error),
|
|
228
|
+
id
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
/** Persistent worker-thread client used by the Vite dev plugin. */
|
|
233
|
+
var NuxtLintCheckerWorker = class {
|
|
234
|
+
worker = new Worker(new URL(import.meta.url));
|
|
235
|
+
pending = /* @__PURE__ */ new Map();
|
|
236
|
+
nextId = 0;
|
|
237
|
+
closed = false;
|
|
238
|
+
constructor() {
|
|
239
|
+
this.worker.on("message", (response) => {
|
|
240
|
+
const pending = this.pending.get(response.id);
|
|
241
|
+
if (!pending) return;
|
|
242
|
+
this.pending.delete(response.id);
|
|
243
|
+
if ("error" in response) {
|
|
244
|
+
const error = new Error(response.error.message);
|
|
245
|
+
error.stack = response.error.stack ?? error.stack;
|
|
246
|
+
pending.reject(error);
|
|
247
|
+
} else pending.resolve(response.result);
|
|
248
|
+
});
|
|
249
|
+
this.worker.on("error", (error) => this.rejectAll(error));
|
|
250
|
+
this.worker.on("exit", (code) => {
|
|
251
|
+
if (!this.closed) this.rejectAll(/* @__PURE__ */ new Error(`Nuxt lint worker exited with ${code}`));
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
run(task) {
|
|
255
|
+
if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Nuxt lint worker is closed"));
|
|
256
|
+
const id = ++this.nextId;
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
this.pending.set(id, {
|
|
259
|
+
reject,
|
|
260
|
+
resolve
|
|
261
|
+
});
|
|
262
|
+
this.worker.postMessage({
|
|
263
|
+
id,
|
|
264
|
+
task
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
async close() {
|
|
269
|
+
if (this.closed) return;
|
|
270
|
+
this.closed = true;
|
|
271
|
+
this.rejectAll(/* @__PURE__ */ new Error("Nuxt lint worker closed"));
|
|
272
|
+
await this.worker.terminate();
|
|
273
|
+
}
|
|
274
|
+
rejectAll(error) {
|
|
275
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
276
|
+
this.pending.clear();
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/lint/checker/vite.ts
|
|
281
|
+
const PLUGIN_NAME$1 = "vize:nuxt-lint-checker";
|
|
282
|
+
function overlayError(output) {
|
|
283
|
+
return {
|
|
284
|
+
type: "error",
|
|
285
|
+
err: {
|
|
286
|
+
message: "Vize lint checker found diagnostics",
|
|
287
|
+
stack: output,
|
|
288
|
+
plugin: PLUGIN_NAME$1
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
var NuxtLintChangeCollector = class {
|
|
293
|
+
pendingFiles = /* @__PURE__ */ new Set();
|
|
294
|
+
pendingFull = false;
|
|
295
|
+
draining = false;
|
|
296
|
+
closed = false;
|
|
297
|
+
overlayVisible = false;
|
|
298
|
+
config;
|
|
299
|
+
runner;
|
|
300
|
+
server;
|
|
301
|
+
oxlintEntrypoint;
|
|
302
|
+
constructor(config, runner, server, oxlintEntrypoint) {
|
|
303
|
+
this.config = config;
|
|
304
|
+
this.runner = runner;
|
|
305
|
+
this.server = server;
|
|
306
|
+
this.oxlintEntrypoint = oxlintEntrypoint;
|
|
307
|
+
}
|
|
308
|
+
full() {
|
|
309
|
+
if (this.closed) return;
|
|
310
|
+
this.pendingFull = true;
|
|
311
|
+
this.pendingFiles.clear();
|
|
312
|
+
this.schedule();
|
|
313
|
+
}
|
|
314
|
+
file(file) {
|
|
315
|
+
if (this.closed) return;
|
|
316
|
+
if (path.resolve(file) === path.resolve(this.config.configFile)) {
|
|
317
|
+
this.full();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (!matchesNuxtLintCheckerFile(file, this.config.rootDir, this.config.options)) return;
|
|
321
|
+
if (!this.config.options.cache) {
|
|
322
|
+
this.full();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (!this.pendingFull) this.pendingFiles.add(path.resolve(file));
|
|
326
|
+
this.schedule();
|
|
327
|
+
}
|
|
328
|
+
async close() {
|
|
329
|
+
if (this.closed) return;
|
|
330
|
+
this.closed = true;
|
|
331
|
+
this.pendingFiles.clear();
|
|
332
|
+
await this.runner.close();
|
|
333
|
+
}
|
|
334
|
+
schedule() {
|
|
335
|
+
if (this.draining) return;
|
|
336
|
+
this.draining = true;
|
|
337
|
+
queueMicrotask(() => void this.drain());
|
|
338
|
+
}
|
|
339
|
+
async drain() {
|
|
340
|
+
while (!this.closed && (this.pendingFull || this.pendingFiles.size > 0)) {
|
|
341
|
+
const targets = this.pendingFull ? [...this.config.options.include] : [...this.pendingFiles].sort();
|
|
342
|
+
this.pendingFull = false;
|
|
343
|
+
this.pendingFiles.clear();
|
|
344
|
+
try {
|
|
345
|
+
const result = await this.runner.run(this.task(targets));
|
|
346
|
+
if (!this.closed) this.report(result);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (!this.closed) this.reportFailure(error);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
this.draining = false;
|
|
352
|
+
if (!this.closed && (this.pendingFull || this.pendingFiles.size > 0)) this.schedule();
|
|
353
|
+
}
|
|
354
|
+
task(targets) {
|
|
355
|
+
const options = this.config.options;
|
|
356
|
+
return {
|
|
357
|
+
configFile: this.config.configFile,
|
|
358
|
+
cwd: this.config.rootDir,
|
|
359
|
+
emitError: options.emitError,
|
|
360
|
+
emitWarning: options.emitWarning,
|
|
361
|
+
exclude: [...options.exclude],
|
|
362
|
+
fix: options.fix,
|
|
363
|
+
formatter: options.formatter,
|
|
364
|
+
oxlintEntrypoint: this.oxlintEntrypoint,
|
|
365
|
+
targets
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
report(result) {
|
|
369
|
+
if (result.output) {
|
|
370
|
+
if (result.hasErrors) this.server.config.logger.error(result.output);
|
|
371
|
+
else if (result.hasWarnings) this.server.config.logger.warn(result.output);
|
|
372
|
+
}
|
|
373
|
+
if (result.diagnosticCount > 0) {
|
|
374
|
+
this.server.ws.send(overlayError(result.output));
|
|
375
|
+
this.overlayVisible = true;
|
|
376
|
+
} else if (this.overlayVisible) {
|
|
377
|
+
this.server.ws.send({
|
|
378
|
+
type: "update",
|
|
379
|
+
updates: []
|
|
380
|
+
});
|
|
381
|
+
this.overlayVisible = false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
reportFailure(error) {
|
|
385
|
+
const message = `Nuxt lint checker failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
386
|
+
this.server.config.logger.error(message);
|
|
387
|
+
this.server.ws.send(overlayError(message));
|
|
388
|
+
this.overlayVisible = true;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
/** Create the client-side Vite dev plugin; it has no transform/render hooks. */
|
|
392
|
+
function createNuxtLintCheckerVitePlugin(config, dependencies = {}) {
|
|
393
|
+
return {
|
|
394
|
+
name: PLUGIN_NAME$1,
|
|
395
|
+
apply: "serve",
|
|
396
|
+
enforce: "post",
|
|
397
|
+
configureServer(server) {
|
|
398
|
+
const collector = new NuxtLintChangeCollector(config, dependencies.createRunner?.() ?? new NuxtLintCheckerWorker(), server, dependencies.oxlintEntrypoint);
|
|
399
|
+
server.watcher.add(config.configFile);
|
|
400
|
+
const changed = (file) => collector.file(file);
|
|
401
|
+
server.watcher.on("add", changed);
|
|
402
|
+
server.watcher.on("change", changed);
|
|
403
|
+
server.watcher.on("unlink", changed);
|
|
404
|
+
server.httpServer?.once("close", () => void collector.close());
|
|
405
|
+
if (config.options.lintOnStart) collector.full();
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/lint/checker/webpack.ts
|
|
411
|
+
const PLUGIN_NAME = "VizeNuxtLintChecker";
|
|
412
|
+
function checkerError(message) {
|
|
413
|
+
const error = new Error(message);
|
|
414
|
+
error.name = PLUGIN_NAME;
|
|
415
|
+
return error;
|
|
416
|
+
}
|
|
417
|
+
function reportResult(compilation, result) {
|
|
418
|
+
if (result.hasErrors) compilation.errors.push(checkerError(result.output));
|
|
419
|
+
else if (result.hasWarnings) compilation.warnings.push(checkerError(result.output));
|
|
420
|
+
}
|
|
421
|
+
function task(config, targets, oxlintEntrypoint) {
|
|
422
|
+
const options = config.options;
|
|
423
|
+
return {
|
|
424
|
+
configFile: config.configFile,
|
|
425
|
+
cwd: config.rootDir,
|
|
426
|
+
emitError: options.emitError,
|
|
427
|
+
emitWarning: options.emitWarning,
|
|
428
|
+
exclude: [...options.exclude],
|
|
429
|
+
fix: options.fix,
|
|
430
|
+
formatter: options.formatter,
|
|
431
|
+
oxlintEntrypoint,
|
|
432
|
+
targets
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function changedTargets(compiler, config, initial) {
|
|
436
|
+
const modified = compiler.modifiedFiles;
|
|
437
|
+
if (initial && config.options.lintOnStart) return [...config.options.include];
|
|
438
|
+
if (!modified || modified.size === 0) return void 0;
|
|
439
|
+
if (!config.options.cache) return [...config.options.include];
|
|
440
|
+
const configFile = path.resolve(config.configFile);
|
|
441
|
+
if ([...modified].some((file) => path.resolve(file) === configFile)) return [...config.options.include];
|
|
442
|
+
const files = [...modified].filter((file) => matchesNuxtLintCheckerFile(file, config.rootDir, config.options)).map((file) => path.resolve(file)).sort();
|
|
443
|
+
return files.length > 0 ? files : void 0;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Create Nuxt 2's webpack adapter.
|
|
447
|
+
*
|
|
448
|
+
* The worker starts at `watchRun`, concurrently with compilation; only the
|
|
449
|
+
* final diagnostic handoff happens at `afterCompile`, where webpack can feed
|
|
450
|
+
* the result to its existing terminal and browser overlays.
|
|
451
|
+
*/
|
|
452
|
+
function createNuxtLintCheckerWebpackPlugin(config, dependencies = {}) {
|
|
453
|
+
return { apply(compiler) {
|
|
454
|
+
const runner = dependencies.createRunner?.() ?? new NuxtLintCheckerWorker();
|
|
455
|
+
let initial = true;
|
|
456
|
+
let pending;
|
|
457
|
+
compiler.hooks.watchRun.tap(PLUGIN_NAME, (nextCompiler) => {
|
|
458
|
+
const targets = changedTargets(nextCompiler, config, initial);
|
|
459
|
+
initial = false;
|
|
460
|
+
pending = targets ? runner.run(task(config, targets, dependencies.oxlintEntrypoint)) : void 0;
|
|
461
|
+
});
|
|
462
|
+
compiler.hooks.afterCompile.tapPromise(PLUGIN_NAME, async (compilation) => {
|
|
463
|
+
compilation.fileDependencies.add(config.configFile);
|
|
464
|
+
if (!pending) return;
|
|
465
|
+
const current = pending;
|
|
466
|
+
pending = void 0;
|
|
467
|
+
try {
|
|
468
|
+
reportResult(compilation, await current);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
compilation.errors.push(checkerError(`Nuxt lint checker failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
compiler.hooks.watchClose.tap(PLUGIN_NAME, () => void runner.close());
|
|
474
|
+
} };
|
|
475
|
+
}
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region src/lint/checker/setup.ts
|
|
478
|
+
async function addVitePlugin(plugin) {
|
|
479
|
+
(await import("@nuxt/kit")).addVitePlugin(plugin, { server: false });
|
|
480
|
+
}
|
|
481
|
+
async function addWebpackPlugin(plugin) {
|
|
482
|
+
(await import("@nuxt/kit")).addWebpackPlugin(plugin, { server: false });
|
|
483
|
+
}
|
|
484
|
+
function builderKind(builder) {
|
|
485
|
+
if (typeof builder !== "string") return "unsupported";
|
|
486
|
+
if (builder === "vite" || builder.includes("vite-builder")) return "vite";
|
|
487
|
+
if (builder === "webpack" || builder.includes("webpack-builder")) return "webpack";
|
|
488
|
+
return "unsupported";
|
|
489
|
+
}
|
|
490
|
+
/** Register the dev-only adapter over Phase 3's generated config artifact. */
|
|
491
|
+
async function setupNuxtLintChecker(checker, nuxt, generation, dependencies = {}) {
|
|
492
|
+
if (checker !== true && (checker === false || checker == null)) return void 0;
|
|
493
|
+
if (nuxt.options.dev !== true) return void 0;
|
|
494
|
+
if (!generation) throw new Error("The Nuxt lint checker requires lint config generation; enable `vize.lint`.");
|
|
495
|
+
const options = resolveNuxtLintCheckerOptions(checker, {
|
|
496
|
+
buildDir: nuxt.options.buildDir,
|
|
497
|
+
srcDir: nuxt.options.srcDir ?? nuxt.options.rootDir
|
|
498
|
+
});
|
|
499
|
+
if (options === false) return void 0;
|
|
500
|
+
const rootDir = path.resolve(nuxt.options.rootDir);
|
|
501
|
+
const config = {
|
|
502
|
+
configFile: generation.configFile,
|
|
503
|
+
options,
|
|
504
|
+
rootDir
|
|
505
|
+
};
|
|
506
|
+
const builder = builderKind(nuxt.options.builder);
|
|
507
|
+
if (builder === "vite") await (dependencies.addVitePlugin ?? addVitePlugin)(createNuxtLintCheckerVitePlugin(config));
|
|
508
|
+
else if (builder === "webpack") await (dependencies.addWebpackPlugin ?? addWebpackPlugin)(createNuxtLintCheckerWebpackPlugin(config));
|
|
509
|
+
else {
|
|
510
|
+
const label = typeof nuxt.options.builder === "string" ? nuxt.options.builder : "unknown";
|
|
511
|
+
(dependencies.warn ?? console.warn)(`Unsupported Nuxt builder ${label}; Vize lint checker is disabled.`);
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
builder,
|
|
515
|
+
configFile: generation.configFile,
|
|
516
|
+
options
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
//#endregion
|
|
520
|
+
//#region src/lint/addons.ts
|
|
521
|
+
/** Hook other Nuxt modules use to extend Vize's generated lint config. */
|
|
522
|
+
const VIZE_NUXT_LINT_CONFIG_ADDONS_HOOK = "vize:lint:config:addons";
|
|
523
|
+
function asImportContext(value) {
|
|
524
|
+
if (value == null || typeof value !== "object" || !("getImports" in value)) return;
|
|
525
|
+
return typeof value.getImports === "function" ? value : void 0;
|
|
526
|
+
}
|
|
527
|
+
function nitroImportContext(value) {
|
|
528
|
+
if (value == null || typeof value !== "object" || !("unimport" in value)) return;
|
|
529
|
+
return asImportContext(value.unimport);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Capture Nuxt's client and server auto-import registries as readonly globals.
|
|
533
|
+
*
|
|
534
|
+
* Nuxt publishes both contexts after modules have begun setting up. Keeping
|
|
535
|
+
* their latest values in the addon means every config regeneration observes
|
|
536
|
+
* the current registry rather than a module-setup-time snapshot.
|
|
537
|
+
*/
|
|
538
|
+
function createNuxtImportGlobalsAddon(nuxt) {
|
|
539
|
+
let unimport;
|
|
540
|
+
let nitroUnimport;
|
|
541
|
+
nuxt.hook("imports:context", (context) => {
|
|
542
|
+
unimport = asImportContext(context);
|
|
543
|
+
});
|
|
544
|
+
nuxt.hook("nitro:init", (nitro) => {
|
|
545
|
+
nitroUnimport = nitroImportContext(nitro);
|
|
546
|
+
});
|
|
547
|
+
return {
|
|
548
|
+
name: "vize:lint:import-globals",
|
|
549
|
+
async getConfigs() {
|
|
550
|
+
const imports = [...await unimport?.getImports() ?? [], ...await nitroUnimport?.getImports() ?? []].sort((left, right) => left.from.localeCompare(right.from) || left.name.localeCompare(right.name));
|
|
551
|
+
return [{
|
|
552
|
+
name: "nuxt/import-globals",
|
|
553
|
+
globals: Object.fromEntries(imports.map((imported) => [imported.as ?? imported.name, "readonly"]))
|
|
554
|
+
}];
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Register the built-in auto-import addon and return the generation-time
|
|
560
|
+
* resolver consumed by the Nuxt lint config writer.
|
|
561
|
+
*
|
|
562
|
+
* The addon array is rebuilt for every generation. This mirrors Nuxt's addon
|
|
563
|
+
* hook without retaining contributions across `builder:generateApp` runs.
|
|
564
|
+
* The Vize-namespaced hook carries engine-neutral config items instead of raw
|
|
565
|
+
* ESLint source, so contributors remain compatible with the oxlint emitter.
|
|
566
|
+
*/
|
|
567
|
+
function setupNuxtLintConfigAddons(nuxt) {
|
|
568
|
+
const defaults = [createNuxtImportGlobalsAddon(nuxt)];
|
|
569
|
+
return async () => {
|
|
570
|
+
const addons = [...defaults];
|
|
571
|
+
await nuxt.callHook(VIZE_NUXT_LINT_CONFIG_ADDONS_HOOK, addons);
|
|
572
|
+
const configs = [];
|
|
573
|
+
for (const addon of addons) {
|
|
574
|
+
const contributed = await addon.getConfigs();
|
|
575
|
+
if (contributed) configs.push(...contributed);
|
|
576
|
+
}
|
|
577
|
+
return configs;
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/lint/emitter.ts
|
|
582
|
+
const PATINA_RULE_NAMESPACES = new Set([
|
|
583
|
+
"a11y",
|
|
584
|
+
"css",
|
|
585
|
+
"ecosystem",
|
|
586
|
+
"html",
|
|
587
|
+
"musea",
|
|
588
|
+
"nuxt",
|
|
589
|
+
"petite-vue",
|
|
590
|
+
"script",
|
|
591
|
+
"ssr",
|
|
592
|
+
"type",
|
|
593
|
+
"vapor",
|
|
594
|
+
"vize",
|
|
595
|
+
"vue"
|
|
596
|
+
]);
|
|
597
|
+
function toOxlintRuleId(ruleId) {
|
|
598
|
+
const separator = ruleId.indexOf("/");
|
|
599
|
+
const namespace = separator < 0 ? ruleId : ruleId.slice(0, separator);
|
|
600
|
+
return PATINA_RULE_NAMESPACES.has(namespace) ? `vize/${ruleId}` : ruleId;
|
|
601
|
+
}
|
|
602
|
+
function prefixVizeRules(rules) {
|
|
603
|
+
if (!rules) return void 0;
|
|
604
|
+
return Object.fromEntries(Object.entries(rules).map(([ruleId, severity]) => [toOxlintRuleId(ruleId), severity]));
|
|
605
|
+
}
|
|
606
|
+
function mergeGlobals(target, source) {
|
|
607
|
+
if (!source) return;
|
|
608
|
+
for (const [name, access] of Object.entries(source)) Object.defineProperty(target, name, {
|
|
609
|
+
configurable: true,
|
|
610
|
+
enumerable: true,
|
|
611
|
+
value: access,
|
|
612
|
+
writable: true
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
function mergeRules(target, source) {
|
|
616
|
+
Object.assign(target, prefixVizeRules(source));
|
|
617
|
+
}
|
|
618
|
+
/** Render the complete generated oxlint config, including its trailing newline. */
|
|
619
|
+
function renderNuxtOxlintConfig(items, pluginSpecifier) {
|
|
620
|
+
const ignorePatterns = [];
|
|
621
|
+
const globals = {};
|
|
622
|
+
const rules = {};
|
|
623
|
+
const overrides = [];
|
|
624
|
+
for (const item of items) {
|
|
625
|
+
if (item.files) {
|
|
626
|
+
const override = { files: [...item.files] };
|
|
627
|
+
if (item.ignores) override.excludeFiles = [...item.ignores];
|
|
628
|
+
if (item.globals) override.globals = { ...item.globals };
|
|
629
|
+
if (item.rules) override.rules = prefixVizeRules(item.rules);
|
|
630
|
+
overrides.push(override);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (item.ignores) ignorePatterns.push(...item.ignores);
|
|
634
|
+
mergeGlobals(globals, item.globals);
|
|
635
|
+
mergeRules(rules, item.rules);
|
|
636
|
+
}
|
|
637
|
+
const config = {
|
|
638
|
+
plugins: ["vue"],
|
|
639
|
+
jsPlugins: [{
|
|
640
|
+
name: "vize",
|
|
641
|
+
specifier: pluginSpecifier
|
|
642
|
+
}],
|
|
643
|
+
settings: { vize: { preset: "incremental" } }
|
|
644
|
+
};
|
|
645
|
+
if (ignorePatterns.length > 0) config.ignorePatterns = ignorePatterns;
|
|
646
|
+
if (Object.keys(globals).length > 0) config.globals = globals;
|
|
647
|
+
if (Object.keys(rules).length > 0) config.rules = rules;
|
|
648
|
+
if (overrides.length > 0) config.overrides = overrides;
|
|
649
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
650
|
+
}
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/lint/nuxt-state.ts
|
|
653
|
+
/**
|
|
654
|
+
* Reduce a Nuxt instance to the project state lint config generation needs.
|
|
655
|
+
*
|
|
656
|
+
* Nuxt 2 has no `_layers`, so the project is treated as a single layer rooted
|
|
657
|
+
* at `srcDir` (falling back to `rootDir`). That keeps the Nuxt 2, 3 and 4 paths
|
|
658
|
+
* on one code path rather than branching on the detected major version.
|
|
659
|
+
*/
|
|
660
|
+
function toNuxtLintProjectState(options, overrides = {}) {
|
|
661
|
+
const rootDir = overrides.rootDir || options.rootDir;
|
|
662
|
+
const layers = (options._layers ?? []).map((layer) => layer.config).filter((config) => Boolean(config)).map((config) => ({
|
|
663
|
+
...config,
|
|
664
|
+
srcDir: config.srcDir || options.srcDir || options.rootDir
|
|
665
|
+
}));
|
|
666
|
+
return {
|
|
667
|
+
rootDir,
|
|
668
|
+
dir: options.dir ?? {},
|
|
669
|
+
layers: layers.length > 0 ? layers : [{ srcDir: options.srcDir || options.rootDir }]
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
//#endregion
|
|
673
|
+
//#region src/lint/inspector-view.ts
|
|
674
|
+
/** Render the dependency-free UI served by the in-process lint-plan inspector. */
|
|
675
|
+
function renderNuxtLintInspectorHtml(nonce) {
|
|
676
|
+
return `<!doctype html>
|
|
677
|
+
<html lang="en">
|
|
678
|
+
<head>
|
|
679
|
+
<meta charset="utf-8">
|
|
680
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
681
|
+
<title>Vize Nuxt Lint Plan</title>
|
|
682
|
+
<style nonce="${nonce}">
|
|
683
|
+
:root { color-scheme: light dark; font: 14px/1.5 ui-sans-serif, system-ui, sans-serif; }
|
|
684
|
+
* { box-sizing: border-box; }
|
|
685
|
+
body { margin: 0; background: #0d1117; color: #e6edf3; }
|
|
686
|
+
header { position: sticky; top: 0; z-index: 1; padding: 16px 20px; background: #161b22; border-bottom: 1px solid #30363d; }
|
|
687
|
+
h1 { margin: 0 0 12px; font-size: 18px; }
|
|
688
|
+
h2 { margin: 24px 0 10px; font-size: 16px; }
|
|
689
|
+
form { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
690
|
+
input { flex: 1 1 320px; min-width: 180px; padding: 8px 10px; color: inherit; background: #0d1117; border: 1px solid #484f58; border-radius: 6px; }
|
|
691
|
+
button { padding: 8px 12px; color: #fff; background: #238636; border: 0; border-radius: 6px; cursor: pointer; }
|
|
692
|
+
button.secondary { background: #30363d; }
|
|
693
|
+
button:disabled { opacity: .6; cursor: wait; }
|
|
694
|
+
main { max-width: 1100px; margin: 0 auto; padding: 0 20px 32px; }
|
|
695
|
+
#status { min-height: 22px; margin-top: 8px; color: #8c959f; }
|
|
696
|
+
#status.error { color: #ff7b72; }
|
|
697
|
+
.card { margin: 8px 0; padding: 12px; background: #161b22; border: 1px solid #30363d; border-radius: 8px; }
|
|
698
|
+
.meta { color: #8c959f; overflow-wrap: anywhere; }
|
|
699
|
+
.badge { display: inline-block; margin: 2px 4px 2px 0; padding: 1px 6px; border-radius: 10px; background: #30363d; }
|
|
700
|
+
.severity-error { color: #ff7b72; } .severity-warn { color: #d29922; } .severity-off { color: #8c959f; }
|
|
701
|
+
table { width: 100%; border-collapse: collapse; }
|
|
702
|
+
th, td { padding: 7px 8px; text-align: left; border-bottom: 1px solid #30363d; overflow-wrap: anywhere; }
|
|
703
|
+
th { color: #8c959f; font-weight: 600; }
|
|
704
|
+
code { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
|
|
705
|
+
</style>
|
|
706
|
+
</head>
|
|
707
|
+
<body>
|
|
708
|
+
<header>
|
|
709
|
+
<h1>Vize Nuxt Lint Plan</h1>
|
|
710
|
+
<form id="inspect-form">
|
|
711
|
+
<input id="file" name="file" autocomplete="off" placeholder="app/pages/index.vue" aria-label="Project-relative file">
|
|
712
|
+
<button type="submit">Inspect file</button>
|
|
713
|
+
<button id="refresh" class="secondary" type="button">Refresh plan</button>
|
|
714
|
+
</form>
|
|
715
|
+
<div id="status" role="status" aria-live="polite"></div>
|
|
716
|
+
</header>
|
|
717
|
+
<main>
|
|
718
|
+
<section><h2>Effective rules</h2><div id="effective"></div></section>
|
|
719
|
+
<section><h2>Ordered config items</h2><div id="items"></div></section>
|
|
720
|
+
</main>
|
|
721
|
+
<script nonce="${nonce}">
|
|
722
|
+
const fileInput = document.getElementById("file");
|
|
723
|
+
const form = document.getElementById("inspect-form");
|
|
724
|
+
const refresh = document.getElementById("refresh");
|
|
725
|
+
const status = document.getElementById("status");
|
|
726
|
+
const effective = document.getElementById("effective");
|
|
727
|
+
const items = document.getElementById("items");
|
|
728
|
+
const buttons = Array.from(document.querySelectorAll("button"));
|
|
729
|
+
|
|
730
|
+
function node(tag, text, className) {
|
|
731
|
+
const value = document.createElement(tag);
|
|
732
|
+
if (text !== undefined) value.textContent = String(text);
|
|
733
|
+
if (className) value.className = className;
|
|
734
|
+
return value;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function badges(values) {
|
|
738
|
+
const wrapper = node("div");
|
|
739
|
+
for (const value of values || []) wrapper.append(node("span", value, "badge"));
|
|
740
|
+
return wrapper;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function renderItems(payload) {
|
|
744
|
+
items.replaceChildren();
|
|
745
|
+
for (const item of payload.items || []) {
|
|
746
|
+
const card = node("article", undefined, "card");
|
|
747
|
+
card.append(node("strong", item.name || "unnamed"));
|
|
748
|
+
if (item.globalIgnore) card.append(node("span", " global ignore", "meta"));
|
|
749
|
+
if (item.basePath) card.append(node("div", "base: " + item.basePath, "meta"));
|
|
750
|
+
if (item.files) card.append(node("div", "files", "meta"), badges(item.files));
|
|
751
|
+
if (item.ignores && item.ignores.length) card.append(node("div", "ignores", "meta"), badges(item.ignores));
|
|
752
|
+
const rules = Object.entries(item.rules || {});
|
|
753
|
+
if (rules.length) card.append(badges(rules.map(([name, severity]) => name + ": " + severity)));
|
|
754
|
+
items.append(card);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function renderEffective(payload) {
|
|
759
|
+
effective.replaceChildren();
|
|
760
|
+
const file = payload.files && payload.files[0];
|
|
761
|
+
if (!file) {
|
|
762
|
+
effective.append(node("p", "Enter a project-relative file to explain its effective rules.", "meta"));
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const summary = node("div", undefined, "card");
|
|
766
|
+
summary.append(node("strong", file.path));
|
|
767
|
+
if (file.ignored) summary.append(node("div", "Ignored by: " + (file.ignoredBy || []).join(", "), "severity-warn"));
|
|
768
|
+
else summary.append(node("div", "Matched: " + (file.matchedItems || []).join(" → "), "meta"));
|
|
769
|
+
effective.append(summary);
|
|
770
|
+
if (!file.rules || !file.rules.length) return;
|
|
771
|
+
const table = node("table");
|
|
772
|
+
const head = node("tr");
|
|
773
|
+
for (const label of ["Rule", "Severity", "Set by"]) head.append(node("th", label));
|
|
774
|
+
const thead = node("thead"); thead.append(head); table.append(thead);
|
|
775
|
+
const tbody = node("tbody");
|
|
776
|
+
for (const rule of file.rules) {
|
|
777
|
+
const row = node("tr");
|
|
778
|
+
row.append(node("td", rule.name), node("td", rule.severity, "severity-" + rule.severity), node("td", rule.setBy));
|
|
779
|
+
tbody.append(row);
|
|
780
|
+
}
|
|
781
|
+
table.append(tbody); effective.append(table);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
async function load(fresh) {
|
|
785
|
+
const url = new URL("api", location.href);
|
|
786
|
+
const file = fileInput.value.trim();
|
|
787
|
+
if (file) url.searchParams.set("file", file);
|
|
788
|
+
if (fresh) url.searchParams.set("fresh", "1");
|
|
789
|
+
buttons.forEach(button => { button.disabled = true; });
|
|
790
|
+
status.className = ""; status.textContent = fresh ? "Refreshing…" : "Loading…";
|
|
791
|
+
try {
|
|
792
|
+
const response = await fetch(url, { headers: { accept: "application/json" } });
|
|
793
|
+
const payload = await response.json();
|
|
794
|
+
if (!response.ok) throw new Error(payload.error || "Inspector request failed");
|
|
795
|
+
renderEffective(payload); renderItems(payload);
|
|
796
|
+
status.textContent = "Resolved " + (payload.items || []).length + " config items";
|
|
797
|
+
} catch (error) {
|
|
798
|
+
status.className = "error";
|
|
799
|
+
status.textContent = error instanceof Error ? error.message : String(error);
|
|
800
|
+
} finally {
|
|
801
|
+
buttons.forEach(button => { button.disabled = false; });
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
form.addEventListener("submit", event => { event.preventDefault(); void load(false); });
|
|
806
|
+
refresh.addEventListener("click", () => { void load(true); });
|
|
807
|
+
void load(false);
|
|
808
|
+
<\/script>
|
|
809
|
+
</body>
|
|
810
|
+
</html>`;
|
|
811
|
+
}
|
|
812
|
+
//#endregion
|
|
813
|
+
//#region src/lint/inspector-devtools.ts
|
|
814
|
+
const MAX_URL_BYTES = 8 * 1024;
|
|
815
|
+
const MAX_FILES = 128;
|
|
816
|
+
const MAX_FILE_BYTES = 4 * 1024;
|
|
817
|
+
const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
818
|
+
function resolveNuxtLintDevtoolsOptions(options) {
|
|
819
|
+
const enabled = options?.enabled ?? "lazy";
|
|
820
|
+
const port = options?.port;
|
|
821
|
+
if (port !== void 0 && (!Number.isInteger(port) || port < 1 || port > 65535)) throw new RangeError("Nuxt lint inspector port must be an integer from 1 to 65535");
|
|
822
|
+
return {
|
|
823
|
+
enabled,
|
|
824
|
+
port
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
async function setupNuxtLintDevtools(options, nuxt, provider) {
|
|
828
|
+
const resolved = resolveNuxtLintDevtoolsOptions(options);
|
|
829
|
+
if (resolved.enabled === false) return void 0;
|
|
830
|
+
const controller = createController(resolved.port, provider, nuxt);
|
|
831
|
+
nuxt.hook("devtools:customTabs", (...args) => {
|
|
832
|
+
args[0].push(controller.tab());
|
|
833
|
+
});
|
|
834
|
+
nuxt.hook("close", () => controller.close());
|
|
835
|
+
if (resolved.enabled === true) try {
|
|
836
|
+
await controller.start();
|
|
837
|
+
} catch (error) {
|
|
838
|
+
console.warn(`[vize] Nuxt lint inspector failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
839
|
+
}
|
|
840
|
+
return controller;
|
|
841
|
+
}
|
|
842
|
+
function createController(requestedPort, provider, nuxt) {
|
|
843
|
+
const token = randomBytes(18).toString("base64url");
|
|
844
|
+
const nonce = randomBytes(18).toString("base64url");
|
|
845
|
+
const server = createServer((request, response) => {
|
|
846
|
+
handleRequest(request, response, provider, token, nonce, server);
|
|
847
|
+
});
|
|
848
|
+
let closed = false;
|
|
849
|
+
let startPromise;
|
|
850
|
+
let viewerUrl;
|
|
851
|
+
const start = async () => {
|
|
852
|
+
if (closed) throw new Error("Nuxt lint inspector is closed");
|
|
853
|
+
if (viewerUrl) return;
|
|
854
|
+
startPromise ||= listen(server, requestedPort).then(async (port) => {
|
|
855
|
+
viewerUrl = `http://127.0.0.1:${port}/${token}/`;
|
|
856
|
+
await nuxt.callHook?.("devtools:customTabs:refresh");
|
|
857
|
+
});
|
|
858
|
+
try {
|
|
859
|
+
await startPromise;
|
|
860
|
+
} catch (error) {
|
|
861
|
+
startPromise = void 0;
|
|
862
|
+
throw error;
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
return {
|
|
866
|
+
async close() {
|
|
867
|
+
closed = true;
|
|
868
|
+
try {
|
|
869
|
+
await startPromise;
|
|
870
|
+
} catch {}
|
|
871
|
+
if (!server.listening) return;
|
|
872
|
+
await new Promise((resolve, reject) => {
|
|
873
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
874
|
+
server.closeAllConnections();
|
|
875
|
+
});
|
|
876
|
+
viewerUrl = void 0;
|
|
877
|
+
startPromise = void 0;
|
|
878
|
+
},
|
|
879
|
+
start,
|
|
880
|
+
tab() {
|
|
881
|
+
return {
|
|
882
|
+
name: "vize-nuxt-lint-plan",
|
|
883
|
+
title: "Vize Nuxt Lint Plan",
|
|
884
|
+
icon: "carbon:rule",
|
|
885
|
+
requireAuth: true,
|
|
886
|
+
view: viewerUrl ? {
|
|
887
|
+
type: "iframe",
|
|
888
|
+
src: viewerUrl,
|
|
889
|
+
persistent: true
|
|
890
|
+
} : {
|
|
891
|
+
type: "launch",
|
|
892
|
+
description: "Inspect the effective Vize lint rules for any Nuxt source file.",
|
|
893
|
+
actions: [{
|
|
894
|
+
label: "Launch",
|
|
895
|
+
pending: startPromise !== void 0,
|
|
896
|
+
handle: start
|
|
897
|
+
}]
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
},
|
|
901
|
+
url: () => viewerUrl
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function listen(server, port) {
|
|
905
|
+
return new Promise((resolve, reject) => {
|
|
906
|
+
const onError = (error) => {
|
|
907
|
+
server.off("listening", onListening);
|
|
908
|
+
reject(error);
|
|
909
|
+
};
|
|
910
|
+
const onListening = () => {
|
|
911
|
+
server.off("error", onError);
|
|
912
|
+
resolve(server.address().port);
|
|
913
|
+
};
|
|
914
|
+
server.once("error", onError);
|
|
915
|
+
server.once("listening", onListening);
|
|
916
|
+
server.listen({
|
|
917
|
+
host: "127.0.0.1",
|
|
918
|
+
port: port ?? 0,
|
|
919
|
+
exclusive: true
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
async function handleRequest(request, response, provider, token, nonce, server) {
|
|
924
|
+
if (Buffer.byteLength(request.url ?? "") > MAX_URL_BYTES) {
|
|
925
|
+
sendJson(response, 414, { error: "request_uri_too_long" });
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
const port = server.address()?.port;
|
|
929
|
+
if (!port || ![`127.0.0.1:${port}`, `localhost:${port}`].includes(request.headers.host ?? "")) {
|
|
930
|
+
sendJson(response, 421, { error: "misdirected_request" });
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
934
|
+
response.setHeader("allow", "GET, HEAD");
|
|
935
|
+
sendJson(response, 405, { error: "method_not_allowed" });
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
let url;
|
|
939
|
+
try {
|
|
940
|
+
url = new URL(request.url ?? "", "http://localhost");
|
|
941
|
+
} catch {
|
|
942
|
+
sendJson(response, 400, { error: "invalid_url" }, request.method === "HEAD");
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const rootPath = `/${token}/`;
|
|
946
|
+
if (url.pathname === rootPath && url.search === "") {
|
|
947
|
+
sendHtml(response, renderNuxtLintInspectorHtml(nonce), nonce, request.method === "HEAD");
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (url.pathname !== `${rootPath}api`) {
|
|
951
|
+
sendJson(response, 404, { error: "not_found" }, request.method === "HEAD");
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const parsed = parseApiRequest(url);
|
|
955
|
+
if ("error" in parsed) {
|
|
956
|
+
sendJson(response, parsed.status, { error: parsed.error }, request.method === "HEAD");
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
sendJson(response, 200, await provider(parsed.request), request.method === "HEAD");
|
|
961
|
+
} catch {
|
|
962
|
+
sendJson(response, 500, { error: "inspector_lint_plan_failed" }, request.method === "HEAD");
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
function parseApiRequest(url) {
|
|
966
|
+
for (const key of url.searchParams.keys()) if (key !== "file" && key !== "fresh") return {
|
|
967
|
+
status: 400,
|
|
968
|
+
error: "invalid_query"
|
|
969
|
+
};
|
|
970
|
+
const fresh = url.searchParams.getAll("fresh");
|
|
971
|
+
if (fresh.length > 1 || fresh[0] !== void 0 && fresh[0] !== "1") return {
|
|
972
|
+
status: 400,
|
|
973
|
+
error: "invalid_fresh"
|
|
974
|
+
};
|
|
975
|
+
const requested = url.searchParams.getAll("file");
|
|
976
|
+
if (requested.length > MAX_FILES) return {
|
|
977
|
+
status: 413,
|
|
978
|
+
error: "too_many_files"
|
|
979
|
+
};
|
|
980
|
+
const files = [...new Set(requested)];
|
|
981
|
+
if (files.some((file) => !isSafeFile(file))) return {
|
|
982
|
+
status: 400,
|
|
983
|
+
error: "invalid_file"
|
|
984
|
+
};
|
|
985
|
+
return { request: {
|
|
986
|
+
files,
|
|
987
|
+
fresh: fresh[0] === "1"
|
|
988
|
+
} };
|
|
989
|
+
}
|
|
990
|
+
function isSafeFile(file) {
|
|
991
|
+
return file.length > 0 && Buffer.byteLength(file) <= MAX_FILE_BYTES && !file.includes("\0") && !file.includes("\\") && !file.startsWith("/") && !/^[A-Za-z]:/u.test(file) && !file.split("/").some((part) => part.length === 0 || part === "..");
|
|
992
|
+
}
|
|
993
|
+
function setCommonHeaders(response, resourcePolicy) {
|
|
994
|
+
response.setHeader("cache-control", "no-store");
|
|
995
|
+
response.setHeader("cross-origin-resource-policy", resourcePolicy);
|
|
996
|
+
response.setHeader("referrer-policy", "no-referrer");
|
|
997
|
+
response.setHeader("x-content-type-options", "nosniff");
|
|
998
|
+
}
|
|
999
|
+
function sendHtml(response, body, nonce, headOnly) {
|
|
1000
|
+
response.statusCode = 200;
|
|
1001
|
+
setCommonHeaders(response, "cross-origin");
|
|
1002
|
+
response.setHeader("content-security-policy", `default-src 'none'; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'; connect-src 'self'; frame-ancestors http://localhost:* http://127.0.0.1:*`);
|
|
1003
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
1004
|
+
response.setHeader("content-length", Buffer.byteLength(body));
|
|
1005
|
+
response.end(headOnly ? void 0 : body);
|
|
1006
|
+
}
|
|
1007
|
+
function sendJson(response, status, payload, headOnly = false) {
|
|
1008
|
+
let body = JSON.stringify(payload);
|
|
1009
|
+
if (body === void 0) {
|
|
1010
|
+
status = 500;
|
|
1011
|
+
body = JSON.stringify({ error: "inspector_lint_plan_failed" });
|
|
1012
|
+
}
|
|
1013
|
+
if (Buffer.byteLength(body) > MAX_RESPONSE_BYTES) {
|
|
1014
|
+
status = 413;
|
|
1015
|
+
body = JSON.stringify({ error: "inspector_response_too_large" });
|
|
1016
|
+
}
|
|
1017
|
+
response.statusCode = status;
|
|
1018
|
+
setCommonHeaders(response, "same-origin");
|
|
1019
|
+
response.setHeader("content-security-policy", "default-src 'none'");
|
|
1020
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
1021
|
+
response.setHeader("content-length", Buffer.byteLength(body));
|
|
1022
|
+
response.end(headOnly ? void 0 : body);
|
|
1023
|
+
}
|
|
1024
|
+
//#endregion
|
|
1025
|
+
//#region src/lint/inspector.ts
|
|
1026
|
+
function createNuxtLintInspectorProvider(generation, dependencies = {}) {
|
|
1027
|
+
const inspect = dependencies.inspectLintPlan ?? inspectWithNative;
|
|
1028
|
+
return async (request) => {
|
|
1029
|
+
const items = await generation.resolvePlan(request.fresh);
|
|
1030
|
+
const serialized = await inspect(JSON.stringify({ items }), generation.root, request.files);
|
|
1031
|
+
const payload = JSON.parse(serialized);
|
|
1032
|
+
if (!isLintPlanPayload(payload)) throw new Error("Native lint-plan inspector returned an invalid payload");
|
|
1033
|
+
return payload;
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
async function setupLintInspector(lint, nuxt, compiler, generation, enabled) {
|
|
1037
|
+
if (enabled === false || !generation) return;
|
|
1038
|
+
const provider = createNuxtLintInspectorProvider(generation);
|
|
1039
|
+
if (compiler !== false) {
|
|
1040
|
+
compiler.inspector ||= {};
|
|
1041
|
+
compiler.inspector.lintPlan ||= provider;
|
|
1042
|
+
}
|
|
1043
|
+
await setupNuxtLintDevtools(typeof lint === "object" && lint !== null ? lint.devtools : void 0, nuxt, compiler === false ? provider : compiler.inspector.lintPlan);
|
|
1044
|
+
}
|
|
1045
|
+
async function inspectWithNative(plan, root, files) {
|
|
1046
|
+
const { inspectLintPlan } = await import("@vizejs/native");
|
|
1047
|
+
return inspectLintPlan(plan, root, files);
|
|
1048
|
+
}
|
|
1049
|
+
function isLintPlanPayload(value) {
|
|
1050
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && value.schema === "vize.inspector.lint-plan" && value.version === 1 && typeof value.root === "string" && Array.isArray(value.items) && Array.isArray(value.files);
|
|
1051
|
+
}
|
|
1052
|
+
//#endregion
|
|
1053
|
+
//#region src/lint/generation.ts
|
|
1054
|
+
/** Nuxt module wiring for generated oxlint configuration. */
|
|
1055
|
+
const GENERATED_CONFIG_NAME = "oxlint.config.json";
|
|
1056
|
+
/** Root config names supported by oxlint's config discovery. */
|
|
1057
|
+
const ROOT_OXLINT_CONFIG_NAMES = [
|
|
1058
|
+
".oxlintrc.json",
|
|
1059
|
+
".oxlintrc.jsonc",
|
|
1060
|
+
"oxlint.config.ts",
|
|
1061
|
+
"oxlint.config.mts"
|
|
1062
|
+
];
|
|
1063
|
+
function isNotFound(error) {
|
|
1064
|
+
return error.code === "ENOENT";
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Write bytes only when they differ from the current regular file.
|
|
1068
|
+
*
|
|
1069
|
+
* The length fast path mirrors Canon's materializer. A full byte comparison is
|
|
1070
|
+
* still required before skipping so equal-size edits are never mistaken for a
|
|
1071
|
+
* cache hit. Symlinks are rejected instead of followed.
|
|
1072
|
+
*/
|
|
1073
|
+
async function writeFileIfChanged(file, content) {
|
|
1074
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
1075
|
+
try {
|
|
1076
|
+
const metadata = await lstat(file);
|
|
1077
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) throw new Error(`Generated oxlint config must be a regular file: ${file}`);
|
|
1078
|
+
if (metadata.size === Buffer.byteLength(content)) {
|
|
1079
|
+
if ((await readFile(file)).equals(Buffer.from(content))) return false;
|
|
1080
|
+
}
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
if (!isNotFound(error)) throw error;
|
|
1083
|
+
}
|
|
1084
|
+
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
1085
|
+
try {
|
|
1086
|
+
await writeFile(temporary, content, { flag: "wx" });
|
|
1087
|
+
await rename(temporary, file);
|
|
1088
|
+
} finally {
|
|
1089
|
+
await unlink(temporary).catch((error) => {
|
|
1090
|
+
if (!isNotFound(error)) throw error;
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
function hasTypeScript(rootDir) {
|
|
1096
|
+
try {
|
|
1097
|
+
createRequire(path.join(rootDir, "package.json")).resolve("typescript");
|
|
1098
|
+
return true;
|
|
1099
|
+
} catch {
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function relativeSpecifier(from, target) {
|
|
1104
|
+
const relative = path.relative(from, target);
|
|
1105
|
+
if (path.isAbsolute(relative)) return pathToFileURL(target).href;
|
|
1106
|
+
const normalized = relative.split(path.sep).join("/");
|
|
1107
|
+
return normalized.startsWith("./") || normalized.startsWith("../") ? normalized : `./${normalized}`;
|
|
1108
|
+
}
|
|
1109
|
+
function resolveVizePluginSpecifier(configDir) {
|
|
1110
|
+
return relativeSpecifier(configDir, fileURLToPath(import.meta.resolve("oxlint-plugin-vize")));
|
|
1111
|
+
}
|
|
1112
|
+
async function findRootOxlintConfig(rootDir) {
|
|
1113
|
+
let directory = path.resolve(rootDir);
|
|
1114
|
+
while (true) {
|
|
1115
|
+
for (const name of ROOT_OXLINT_CONFIG_NAMES) {
|
|
1116
|
+
const candidate = path.join(directory, name);
|
|
1117
|
+
try {
|
|
1118
|
+
const metadata = await lstat(candidate);
|
|
1119
|
+
if (metadata.isFile() || metadata.isSymbolicLink()) return candidate;
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
if (!isNotFound(error)) throw error;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
const parent = path.dirname(directory);
|
|
1125
|
+
if (parent === directory) return void 0;
|
|
1126
|
+
directory = parent;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
function renderRootOxlintConfig(rootDir, generatedConfig) {
|
|
1130
|
+
const specifier = relativeSpecifier(rootDir, generatedConfig);
|
|
1131
|
+
return [
|
|
1132
|
+
"// Generated by @vizejs/nuxt.",
|
|
1133
|
+
"import { readFileSync } from \"node:fs\";",
|
|
1134
|
+
"",
|
|
1135
|
+
"type OxlintJsPlugin = string | { name?: string; specifier: string };",
|
|
1136
|
+
"",
|
|
1137
|
+
`const generatedUrl = new URL(${JSON.stringify(specifier)}, import.meta.url);`,
|
|
1138
|
+
"const config = JSON.parse(readFileSync(generatedUrl, \"utf8\"));",
|
|
1139
|
+
"config.jsPlugins = config.jsPlugins.map((plugin: OxlintJsPlugin) =>",
|
|
1140
|
+
" typeof plugin === \"string\"",
|
|
1141
|
+
" ? new URL(plugin, generatedUrl).href",
|
|
1142
|
+
" : { ...plugin, specifier: new URL(plugin.specifier, generatedUrl).href },",
|
|
1143
|
+
");",
|
|
1144
|
+
"",
|
|
1145
|
+
"export default config;",
|
|
1146
|
+
""
|
|
1147
|
+
].join("\n");
|
|
1148
|
+
}
|
|
1149
|
+
async function initRootOxlintConfig(rootDir, generatedConfig) {
|
|
1150
|
+
if (await findRootOxlintConfig(rootDir)) return;
|
|
1151
|
+
const handle = await open(path.join(rootDir, "oxlint.config.mts"), "wx").catch((error) => {
|
|
1152
|
+
if (error.code === "EEXIST") return void 0;
|
|
1153
|
+
throw error;
|
|
1154
|
+
});
|
|
1155
|
+
if (!handle) return;
|
|
1156
|
+
try {
|
|
1157
|
+
await handle.writeFile(renderRootOxlintConfig(rootDir, generatedConfig), "utf8");
|
|
1158
|
+
} finally {
|
|
1159
|
+
await handle.close();
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Generate the initial config and register the Nuxt regeneration hook.
|
|
1164
|
+
*
|
|
1165
|
+
* Addons are resolved inside `regenerate`, not at setup time, so Nuxt's import
|
|
1166
|
+
* registry and third-party module hooks can change between generateApp passes.
|
|
1167
|
+
*/
|
|
1168
|
+
async function setupNuxtLintConfigGeneration(lint, nuxt, dependencies = {}) {
|
|
1169
|
+
if (lint === false) return void 0;
|
|
1170
|
+
const { autoInit = true, configFile: configuredFile, rootDir: configuredRoot, ...featureOptions } = typeof lint === "object" && lint !== null ? lint : {};
|
|
1171
|
+
const nuxtRoot = path.resolve(nuxt.options.rootDir);
|
|
1172
|
+
const planRoot = configuredRoot ? path.resolve(nuxtRoot, configuredRoot) : nuxtRoot;
|
|
1173
|
+
const configFile = path.resolve(nuxtRoot, configuredFile ?? path.join(nuxt.options.buildDir, GENERATED_CONFIG_NAME));
|
|
1174
|
+
const hasTypeScriptProbe = dependencies.hasTypeScript ?? hasTypeScript;
|
|
1175
|
+
const resolvePluginSpecifier = dependencies.resolvePluginSpecifier ?? resolveVizePluginSpecifier;
|
|
1176
|
+
const resolveAddons = dependencies.resolveAddons ?? ("callHook" in nuxt ? setupNuxtLintConfigAddons(nuxt) : void 0);
|
|
1177
|
+
let currentPlan = [];
|
|
1178
|
+
const regenerate = async () => {
|
|
1179
|
+
const plan = buildNuxtLintPlan(resolveNuxtLintFeatures(featureOptions, () => hasTypeScriptProbe(planRoot)), collectNuxtLintDirs(toNuxtLintProjectState(nuxt.options, { rootDir: planRoot })));
|
|
1180
|
+
const addons = await resolveAddons?.() ?? [];
|
|
1181
|
+
const nextPlan = [...plan, ...addons];
|
|
1182
|
+
const changed = await writeFileIfChanged(configFile, renderNuxtOxlintConfig(nextPlan, resolvePluginSpecifier(path.dirname(configFile))));
|
|
1183
|
+
currentPlan = nextPlan;
|
|
1184
|
+
return changed;
|
|
1185
|
+
};
|
|
1186
|
+
const resolvePlan = async (fresh = false) => {
|
|
1187
|
+
if (fresh) await regenerate();
|
|
1188
|
+
return currentPlan;
|
|
1189
|
+
};
|
|
1190
|
+
await regenerate();
|
|
1191
|
+
nuxt.hook("builder:generateApp", regenerate);
|
|
1192
|
+
if (autoInit) await initRootOxlintConfig(nuxtRoot, configFile);
|
|
1193
|
+
return {
|
|
1194
|
+
configFile,
|
|
1195
|
+
root: planRoot,
|
|
1196
|
+
regenerate,
|
|
1197
|
+
resolvePlan
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
//#endregion
|
|
1201
|
+
export { toNuxtLintProjectState as a, setupNuxtLintConfigAddons as c, createNuxtLintCheckerVitePlugin as d, resolveNuxtLintCheckerOptions as f, setupLintInspector as i, setupNuxtLintChecker as l, setupNuxtLintConfigGeneration as n, renderNuxtOxlintConfig as o, writeFileIfChanged as r, VIZE_NUXT_LINT_CONFIG_ADDONS_HOOK as s, ROOT_OXLINT_CONFIG_NAMES as t, createNuxtLintCheckerWebpackPlugin as u };
|