@savvy-web/lint-staged 0.8.0 → 1.0.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/878.js +310 -187
- package/README.md +3 -3
- package/biome/silk.jsonc +5 -2
- package/index.d.ts +121 -512
- package/index.js +22 -616
- package/package.json +8 -11
- package/tsdoc-metadata.json +1 -1
package/index.js
CHANGED
|
@@ -1,60 +1,7 @@
|
|
|
1
|
-
import typescript from "typescript";
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
1
|
import sort_package_json from "sort-package-json";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import eslint_plugin_tsdoc from "eslint-plugin-tsdoc";
|
|
8
|
-
import { parse } from "yaml";
|
|
9
|
-
import { Command as Command_Command, Yaml, PnpmWorkspace, Filter } from "./878.js";
|
|
10
|
-
class Biome {
|
|
11
|
-
static glob = "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}";
|
|
12
|
-
static defaultExcludes = [
|
|
13
|
-
"package.json",
|
|
14
|
-
"package-lock.json",
|
|
15
|
-
"__fixtures__",
|
|
16
|
-
"__test__/fixtures"
|
|
17
|
-
];
|
|
18
|
-
static handler = Biome.create();
|
|
19
|
-
static findBiome() {
|
|
20
|
-
const result = Command_Command.findTool("biome");
|
|
21
|
-
return result.command;
|
|
22
|
-
}
|
|
23
|
-
static isAvailable() {
|
|
24
|
-
return Command_Command.findTool("biome").available;
|
|
25
|
-
}
|
|
26
|
-
static findConfig() {
|
|
27
|
-
for (const name of [
|
|
28
|
-
"biome.jsonc",
|
|
29
|
-
"biome.json"
|
|
30
|
-
]){
|
|
31
|
-
const libPath = `lib/configs/${name}`;
|
|
32
|
-
if (existsSync(libPath)) return libPath;
|
|
33
|
-
if (existsSync(name)) return name;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
static create(options = {}) {
|
|
37
|
-
const excludes = options.exclude ?? [
|
|
38
|
-
...Biome.defaultExcludes
|
|
39
|
-
];
|
|
40
|
-
const config = options.config ?? Biome.findConfig();
|
|
41
|
-
return (filenames)=>{
|
|
42
|
-
const filtered = Filter.exclude(filenames, excludes);
|
|
43
|
-
if (0 === filtered.length) return [];
|
|
44
|
-
const biomeCmd = Command_Command.requireTool("biome", "Biome is not available. Install it globally (recommended) or add @biomejs/biome as a dev dependency.");
|
|
45
|
-
const files = Filter.shellEscape(filtered);
|
|
46
|
-
const flags = options.flags ?? [];
|
|
47
|
-
const configFlag = config ? `--config-path=${config}` : "";
|
|
48
|
-
const cmd = [
|
|
49
|
-
`${biomeCmd} check --write --no-errors-on-unmatched`,
|
|
50
|
-
configFlag,
|
|
51
|
-
...flags,
|
|
52
|
-
files
|
|
53
|
-
].filter(Boolean).join(" ");
|
|
54
|
-
return cmd;
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { Filter, Command as Command_Command, isWorkspacePackagePath, Biome, PnpmWorkspace, Yaml, getWorkspaceRoot } from "./878.js";
|
|
58
5
|
class Markdown {
|
|
59
6
|
static glob = "**/*.{md,mdx}";
|
|
60
7
|
static defaultExcludes = [];
|
|
@@ -67,6 +14,7 @@ class Markdown {
|
|
|
67
14
|
return Command_Command.findTool("markdownlint-cli2").available;
|
|
68
15
|
}
|
|
69
16
|
static findConfig() {
|
|
17
|
+
const root = getWorkspaceRoot() ?? process.cwd();
|
|
70
18
|
const filenames = [
|
|
71
19
|
".markdownlint-cli2.jsonc",
|
|
72
20
|
".markdownlint-cli2.json",
|
|
@@ -77,9 +25,10 @@ class Markdown {
|
|
|
77
25
|
".markdownlint.yaml"
|
|
78
26
|
];
|
|
79
27
|
for (const name of filenames){
|
|
80
|
-
const libPath =
|
|
28
|
+
const libPath = join(root, "lib/configs", name);
|
|
81
29
|
if (existsSync(libPath)) return libPath;
|
|
82
|
-
|
|
30
|
+
const rootPath = join(root, name);
|
|
31
|
+
if (existsSync(rootPath)) return rootPath;
|
|
83
32
|
}
|
|
84
33
|
}
|
|
85
34
|
static create(options = {}) {
|
|
@@ -112,12 +61,18 @@ class PackageJson {
|
|
|
112
61
|
"__fixtures__"
|
|
113
62
|
];
|
|
114
63
|
static handler = PackageJson.create();
|
|
64
|
+
static filterToWorkspaceRoots(filenames, excludes) {
|
|
65
|
+
const excluded = Filter.exclude(filenames, [
|
|
66
|
+
...excludes
|
|
67
|
+
]);
|
|
68
|
+
return excluded.filter((f)=>isWorkspacePackagePath(f));
|
|
69
|
+
}
|
|
115
70
|
static fmtCommand(options = {}) {
|
|
116
71
|
const excludes = options.exclude ?? [
|
|
117
72
|
...PackageJson.defaultExcludes
|
|
118
73
|
];
|
|
119
74
|
return (filenames)=>{
|
|
120
|
-
const filtered =
|
|
75
|
+
const filtered = PackageJson.filterToWorkspaceRoots(filenames, excludes);
|
|
121
76
|
if (0 === filtered.length) return [];
|
|
122
77
|
const cmd = Command_Command.findSavvyLint();
|
|
123
78
|
return `${cmd} fmt package-json ${Filter.shellEscape(filtered)}`;
|
|
@@ -130,7 +85,7 @@ class PackageJson {
|
|
|
130
85
|
const skipSort = options.skipSort ?? false;
|
|
131
86
|
const skipFormat = options.skipFormat ?? false;
|
|
132
87
|
return (filenames)=>{
|
|
133
|
-
const filtered =
|
|
88
|
+
const filtered = PackageJson.filterToWorkspaceRoots(filenames, excludes);
|
|
134
89
|
if (0 === filtered.length) return [];
|
|
135
90
|
if (!skipSort) for (const filepath of filtered){
|
|
136
91
|
const content = readFileSync(filepath, "utf-8");
|
|
@@ -163,524 +118,9 @@ class ShellScripts {
|
|
|
163
118
|
};
|
|
164
119
|
}
|
|
165
120
|
}
|
|
166
|
-
class TsDocLinter {
|
|
167
|
-
eslint;
|
|
168
|
-
constructor(options = {}){
|
|
169
|
-
const ignorePatterns = options.ignorePatterns ?? [];
|
|
170
|
-
const config = [
|
|
171
|
-
{
|
|
172
|
-
ignores: [
|
|
173
|
-
"**/node_modules/**",
|
|
174
|
-
"**/dist/**",
|
|
175
|
-
"**/coverage/**",
|
|
176
|
-
...ignorePatterns
|
|
177
|
-
]
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
files: [
|
|
181
|
-
"**/*.ts",
|
|
182
|
-
"**/*.tsx",
|
|
183
|
-
"**/*.mts",
|
|
184
|
-
"**/*.cts"
|
|
185
|
-
],
|
|
186
|
-
languageOptions: {
|
|
187
|
-
parser: parser
|
|
188
|
-
},
|
|
189
|
-
plugins: {
|
|
190
|
-
tsdoc: eslint_plugin_tsdoc
|
|
191
|
-
},
|
|
192
|
-
rules: {
|
|
193
|
-
"tsdoc/syntax": "error"
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
];
|
|
197
|
-
this.eslint = new ESLint({
|
|
198
|
-
overrideConfigFile: true,
|
|
199
|
-
overrideConfig: config
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
async lintFiles(filePaths) {
|
|
203
|
-
if (0 === filePaths.length) return [];
|
|
204
|
-
const results = await this.eslint.lintFiles(filePaths);
|
|
205
|
-
return results.map((result)=>({
|
|
206
|
-
filePath: result.filePath,
|
|
207
|
-
errorCount: result.errorCount,
|
|
208
|
-
warningCount: result.warningCount,
|
|
209
|
-
messages: result.messages.map((msg)=>({
|
|
210
|
-
line: msg.line,
|
|
211
|
-
column: msg.column,
|
|
212
|
-
severity: msg.severity,
|
|
213
|
-
message: msg.message,
|
|
214
|
-
ruleId: msg.ruleId
|
|
215
|
-
}))
|
|
216
|
-
}));
|
|
217
|
-
}
|
|
218
|
-
async lintFilesAndThrow(filePaths) {
|
|
219
|
-
const results = await this.lintFiles(filePaths);
|
|
220
|
-
const errors = [];
|
|
221
|
-
for (const result of results)if (result.errorCount > 0) {
|
|
222
|
-
for (const msg of result.messages)if (2 === msg.severity) errors.push(`${result.filePath}:${msg.line}:${msg.column} - ${msg.message}`);
|
|
223
|
-
}
|
|
224
|
-
if (errors.length > 0) throw new Error(`TSDoc validation failed:\n${errors.join("\n")}`);
|
|
225
|
-
}
|
|
226
|
-
static formatResults(results) {
|
|
227
|
-
const lines = [];
|
|
228
|
-
for (const result of results)if (0 !== result.errorCount || 0 !== result.warningCount) {
|
|
229
|
-
lines.push(`\n${result.filePath}`);
|
|
230
|
-
for (const msg of result.messages){
|
|
231
|
-
const severity = 2 === msg.severity ? "error" : "warning";
|
|
232
|
-
const rule = msg.ruleId ? ` (${msg.ruleId})` : "";
|
|
233
|
-
lines.push(` ${msg.line}:${msg.column} ${severity} ${msg.message}${rule}`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const totalErrors = results.reduce((sum, r)=>sum + r.errorCount, 0);
|
|
237
|
-
const totalWarnings = results.reduce((sum, r)=>sum + r.warningCount, 0);
|
|
238
|
-
if (totalErrors > 0 || totalWarnings > 0) lines.push(`\n✖ ${totalErrors} error(s), ${totalWarnings} warning(s)`);
|
|
239
|
-
return lines.join("\n");
|
|
240
|
-
}
|
|
241
|
-
static hasErrors(results) {
|
|
242
|
-
return results.some((r)=>r.errorCount > 0);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
const TS_EXTENSIONS = [
|
|
246
|
-
".ts",
|
|
247
|
-
".tsx",
|
|
248
|
-
".mts",
|
|
249
|
-
".cts"
|
|
250
|
-
];
|
|
251
|
-
class EntryExtractor {
|
|
252
|
-
extract(packageJson) {
|
|
253
|
-
const entries = {};
|
|
254
|
-
const unresolved = [];
|
|
255
|
-
const { exports } = packageJson;
|
|
256
|
-
if (!exports) {
|
|
257
|
-
const mainEntry = packageJson.module ?? packageJson.main;
|
|
258
|
-
if (mainEntry && this.isTypeScriptFile(mainEntry)) entries["."] = mainEntry;
|
|
259
|
-
else if (mainEntry) unresolved.push(".");
|
|
260
|
-
return {
|
|
261
|
-
entries,
|
|
262
|
-
unresolved
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
if ("string" == typeof exports) {
|
|
266
|
-
if (this.isTypeScriptFile(exports)) entries["."] = exports;
|
|
267
|
-
else unresolved.push(".");
|
|
268
|
-
return {
|
|
269
|
-
entries,
|
|
270
|
-
unresolved
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
this.extractFromObject(exports, entries, unresolved, ".");
|
|
274
|
-
return {
|
|
275
|
-
entries,
|
|
276
|
-
unresolved
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
extractFromObject(obj, entries, unresolved, currentPath) {
|
|
280
|
-
for (const [key, value] of Object.entries(obj)){
|
|
281
|
-
const exportPath = key.startsWith(".") ? key : currentPath;
|
|
282
|
-
if ("string" == typeof value) {
|
|
283
|
-
if (this.isTypeScriptFile(value)) entries[exportPath] = value;
|
|
284
|
-
else if (key.startsWith(".")) unresolved.push(exportPath);
|
|
285
|
-
} else if (value && "object" == typeof value && !Array.isArray(value)) {
|
|
286
|
-
const nested = value;
|
|
287
|
-
const tsPath = this.findTypeScriptCondition(nested);
|
|
288
|
-
if (tsPath) entries[exportPath] = tsPath;
|
|
289
|
-
else if (key.startsWith(".")) this.extractFromObject(nested, entries, unresolved, exportPath);
|
|
290
|
-
else {
|
|
291
|
-
const sourcePath = this.findSourceCondition(nested);
|
|
292
|
-
if (sourcePath && this.isTypeScriptFile(sourcePath)) entries[exportPath] = sourcePath;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
findTypeScriptCondition(conditions) {
|
|
298
|
-
const priorityKeys = [
|
|
299
|
-
"source",
|
|
300
|
-
"typescript",
|
|
301
|
-
"development",
|
|
302
|
-
"default"
|
|
303
|
-
];
|
|
304
|
-
for (const key of priorityKeys){
|
|
305
|
-
const value = conditions[key];
|
|
306
|
-
if ("string" == typeof value && this.isTypeScriptFile(value)) return value;
|
|
307
|
-
if (value && "object" == typeof value) {
|
|
308
|
-
const nested = this.findTypeScriptCondition(value);
|
|
309
|
-
if (nested) return nested;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return null;
|
|
313
|
-
}
|
|
314
|
-
findSourceCondition(conditions) {
|
|
315
|
-
const priorityKeys = [
|
|
316
|
-
"source",
|
|
317
|
-
"import",
|
|
318
|
-
"require",
|
|
319
|
-
"default"
|
|
320
|
-
];
|
|
321
|
-
for (const key of priorityKeys){
|
|
322
|
-
const value = conditions[key];
|
|
323
|
-
if ("string" == typeof value) return value;
|
|
324
|
-
if (value && "object" == typeof value) {
|
|
325
|
-
const nested = this.findSourceCondition(value);
|
|
326
|
-
if (nested) return nested;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
isTypeScriptFile(filePath) {
|
|
332
|
-
return TS_EXTENSIONS.some((ext)=>filePath.endsWith(ext));
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
class ImportGraph {
|
|
336
|
-
options;
|
|
337
|
-
program = null;
|
|
338
|
-
compilerOptions = null;
|
|
339
|
-
moduleResolutionCache = null;
|
|
340
|
-
constructor(options){
|
|
341
|
-
this.options = options;
|
|
342
|
-
}
|
|
343
|
-
traceFromEntries(entryPaths) {
|
|
344
|
-
const errors = [];
|
|
345
|
-
const visited = new Set();
|
|
346
|
-
const entries = [];
|
|
347
|
-
const initResult = this.initializeProgram();
|
|
348
|
-
if (!initResult.success) return {
|
|
349
|
-
files: [],
|
|
350
|
-
entries: [],
|
|
351
|
-
errors: [
|
|
352
|
-
initResult.error
|
|
353
|
-
]
|
|
354
|
-
};
|
|
355
|
-
for (const entryPath of entryPaths){
|
|
356
|
-
const absolutePath = this.resolveEntryPath(entryPath);
|
|
357
|
-
if (!existsSync(absolutePath)) {
|
|
358
|
-
errors.push({
|
|
359
|
-
type: "entry_not_found",
|
|
360
|
-
message: `Entry file not found: ${entryPath}`,
|
|
361
|
-
path: absolutePath
|
|
362
|
-
});
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
entries.push(absolutePath);
|
|
366
|
-
this.traceImports(absolutePath, visited, errors);
|
|
367
|
-
}
|
|
368
|
-
const files = Array.from(visited).filter((file)=>this.isSourceFile(file));
|
|
369
|
-
return {
|
|
370
|
-
files: files.sort(),
|
|
371
|
-
entries,
|
|
372
|
-
errors
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
traceFromPackageExports(packageJsonPath) {
|
|
376
|
-
const absolutePath = this.resolveEntryPath(packageJsonPath);
|
|
377
|
-
let packageJson;
|
|
378
|
-
try {
|
|
379
|
-
if (!existsSync(absolutePath)) return {
|
|
380
|
-
files: [],
|
|
381
|
-
entries: [],
|
|
382
|
-
errors: [
|
|
383
|
-
{
|
|
384
|
-
type: "package_json_not_found",
|
|
385
|
-
message: `Failed to read package.json: File not found at ${absolutePath}`,
|
|
386
|
-
path: absolutePath
|
|
387
|
-
}
|
|
388
|
-
]
|
|
389
|
-
};
|
|
390
|
-
const content = readFileSync(absolutePath, "utf-8");
|
|
391
|
-
packageJson = JSON.parse(content);
|
|
392
|
-
} catch (error) {
|
|
393
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
394
|
-
return {
|
|
395
|
-
files: [],
|
|
396
|
-
entries: [],
|
|
397
|
-
errors: [
|
|
398
|
-
{
|
|
399
|
-
type: "package_json_parse_error",
|
|
400
|
-
message: `Failed to parse package.json: ${message}`,
|
|
401
|
-
path: absolutePath
|
|
402
|
-
}
|
|
403
|
-
]
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
const extractor = new EntryExtractor();
|
|
407
|
-
const { entries } = extractor.extract(packageJson);
|
|
408
|
-
const packageDir = dirname(absolutePath);
|
|
409
|
-
const entryPaths = Object.values(entries).map((p)=>resolve(packageDir, p));
|
|
410
|
-
return this.traceFromEntries(entryPaths);
|
|
411
|
-
}
|
|
412
|
-
initializeProgram() {
|
|
413
|
-
if (this.program) return {
|
|
414
|
-
success: true
|
|
415
|
-
};
|
|
416
|
-
const configPath = this.findTsConfig();
|
|
417
|
-
if (!configPath) {
|
|
418
|
-
this.compilerOptions = {
|
|
419
|
-
moduleResolution: typescript.ModuleResolutionKind.NodeNext,
|
|
420
|
-
module: typescript.ModuleKind.NodeNext,
|
|
421
|
-
target: typescript.ScriptTarget.ESNext,
|
|
422
|
-
strict: true
|
|
423
|
-
};
|
|
424
|
-
this.moduleResolutionCache = typescript.createModuleResolutionCache(this.options.rootDir, (fileName)=>fileName.toLowerCase(), this.compilerOptions);
|
|
425
|
-
const host = typescript.createCompilerHost(this.compilerOptions, true);
|
|
426
|
-
host.getCurrentDirectory = ()=>this.options.rootDir;
|
|
427
|
-
this.program = typescript.createProgram([], this.compilerOptions, host);
|
|
428
|
-
return {
|
|
429
|
-
success: true
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
const configFile = typescript.readConfigFile(configPath, (path)=>readFileSync(path, "utf-8"));
|
|
433
|
-
if (configFile.error) {
|
|
434
|
-
const message = typescript.flattenDiagnosticMessageText(configFile.error.messageText, "\n");
|
|
435
|
-
return {
|
|
436
|
-
success: false,
|
|
437
|
-
error: {
|
|
438
|
-
type: "tsconfig_read_error",
|
|
439
|
-
message: `Failed to read tsconfig.json: ${message}`,
|
|
440
|
-
path: configPath
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
const parsed = typescript.parseJsonConfigFileContent(configFile.config, typescript.sys, dirname(configPath));
|
|
445
|
-
if (parsed.errors.length > 0) {
|
|
446
|
-
const messages = parsed.errors.map((e)=>typescript.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
447
|
-
return {
|
|
448
|
-
success: false,
|
|
449
|
-
error: {
|
|
450
|
-
type: "tsconfig_parse_error",
|
|
451
|
-
message: `Failed to parse tsconfig.json: ${messages}`,
|
|
452
|
-
path: configPath
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
this.compilerOptions = parsed.options;
|
|
457
|
-
this.moduleResolutionCache = typescript.createModuleResolutionCache(this.options.rootDir, (fileName)=>fileName.toLowerCase(), this.compilerOptions);
|
|
458
|
-
const host = typescript.createCompilerHost(this.compilerOptions, true);
|
|
459
|
-
host.getCurrentDirectory = ()=>this.options.rootDir;
|
|
460
|
-
this.program = typescript.createProgram([], this.compilerOptions, host);
|
|
461
|
-
return {
|
|
462
|
-
success: true
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
findTsConfig() {
|
|
466
|
-
if (this.options.tsconfigPath) {
|
|
467
|
-
const customPath = isAbsolute(this.options.tsconfigPath) ? this.options.tsconfigPath : resolve(this.options.rootDir, this.options.tsconfigPath);
|
|
468
|
-
if (existsSync(customPath)) return customPath;
|
|
469
|
-
return null;
|
|
470
|
-
}
|
|
471
|
-
const configPath = typescript.findConfigFile(this.options.rootDir, (path)=>existsSync(path));
|
|
472
|
-
return configPath ?? null;
|
|
473
|
-
}
|
|
474
|
-
resolveEntryPath(entryPath) {
|
|
475
|
-
if (isAbsolute(entryPath)) return normalize(entryPath);
|
|
476
|
-
return normalize(resolve(this.options.rootDir, entryPath));
|
|
477
|
-
}
|
|
478
|
-
traceImports(filePath, visited, errors) {
|
|
479
|
-
const normalizedPath = normalize(filePath);
|
|
480
|
-
if (visited.has(normalizedPath)) return;
|
|
481
|
-
if (this.isExternalModule(normalizedPath)) return;
|
|
482
|
-
visited.add(normalizedPath);
|
|
483
|
-
let content;
|
|
484
|
-
try {
|
|
485
|
-
content = readFileSync(normalizedPath, "utf-8");
|
|
486
|
-
} catch {
|
|
487
|
-
errors.push({
|
|
488
|
-
type: "file_read_error",
|
|
489
|
-
message: `Failed to read file: ${normalizedPath}`,
|
|
490
|
-
path: normalizedPath
|
|
491
|
-
});
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
const sourceFile = typescript.createSourceFile(normalizedPath, content, typescript.ScriptTarget.Latest, true);
|
|
495
|
-
const imports = this.extractImports(sourceFile);
|
|
496
|
-
for (const importPath of imports){
|
|
497
|
-
const resolved = this.resolveImport(importPath, normalizedPath);
|
|
498
|
-
if (resolved) this.traceImports(resolved, visited, errors);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
extractImports(sourceFile) {
|
|
502
|
-
const imports = [];
|
|
503
|
-
const visit = (node)=>{
|
|
504
|
-
if (typescript.isImportDeclaration(node)) {
|
|
505
|
-
const specifier = node.moduleSpecifier;
|
|
506
|
-
if (typescript.isStringLiteral(specifier)) imports.push(specifier.text);
|
|
507
|
-
} else if (typescript.isExportDeclaration(node)) {
|
|
508
|
-
const specifier = node.moduleSpecifier;
|
|
509
|
-
if (specifier && typescript.isStringLiteral(specifier)) imports.push(specifier.text);
|
|
510
|
-
} else if (typescript.isCallExpression(node)) {
|
|
511
|
-
const expression = node.expression;
|
|
512
|
-
if (expression.kind === typescript.SyntaxKind.ImportKeyword && node.arguments.length > 0) {
|
|
513
|
-
const arg = node.arguments[0];
|
|
514
|
-
if (arg && typescript.isStringLiteral(arg)) imports.push(arg.text);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
typescript.forEachChild(node, visit);
|
|
518
|
-
};
|
|
519
|
-
visit(sourceFile);
|
|
520
|
-
return imports;
|
|
521
|
-
}
|
|
522
|
-
resolveImport(specifier, fromFile) {
|
|
523
|
-
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
524
|
-
if (!this.compilerOptions?.paths || !Object.keys(this.compilerOptions.paths).length) return null;
|
|
525
|
-
}
|
|
526
|
-
if (!this.compilerOptions || !this.moduleResolutionCache) return null;
|
|
527
|
-
const resolved = typescript.resolveModuleName(specifier, fromFile, this.compilerOptions, typescript.sys, this.moduleResolutionCache);
|
|
528
|
-
if (resolved.resolvedModule) {
|
|
529
|
-
const resolvedPath = resolved.resolvedModule.resolvedFileName;
|
|
530
|
-
if (resolved.resolvedModule.isExternalLibraryImport) return null;
|
|
531
|
-
if (resolvedPath.endsWith(".d.ts")) {
|
|
532
|
-
const sourcePath = resolvedPath.replace(/\.d\.ts$/, ".ts");
|
|
533
|
-
if (existsSync(sourcePath)) return sourcePath;
|
|
534
|
-
return null;
|
|
535
|
-
}
|
|
536
|
-
return resolvedPath;
|
|
537
|
-
}
|
|
538
|
-
return null;
|
|
539
|
-
}
|
|
540
|
-
isExternalModule(filePath) {
|
|
541
|
-
return filePath.includes("/node_modules/") || filePath.includes("\\node_modules\\");
|
|
542
|
-
}
|
|
543
|
-
isSourceFile(filePath) {
|
|
544
|
-
if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".mts") && !filePath.endsWith(".cts")) return false;
|
|
545
|
-
if (filePath.endsWith(".d.ts") || filePath.endsWith(".d.mts") || filePath.endsWith(".d.cts")) return false;
|
|
546
|
-
if (filePath.includes(".test.") || filePath.includes(".spec.")) return false;
|
|
547
|
-
if (filePath.includes("/__test__/") || filePath.includes("\\__test__\\")) return false;
|
|
548
|
-
if (filePath.includes("/__tests__/") || filePath.includes("\\__tests__\\")) return false;
|
|
549
|
-
const excludePatterns = this.options.excludePatterns ?? [];
|
|
550
|
-
for (const pattern of excludePatterns)if (filePath.includes(pattern)) return false;
|
|
551
|
-
return true;
|
|
552
|
-
}
|
|
553
|
-
static fromEntries(entryPaths, options) {
|
|
554
|
-
const graph = new ImportGraph(options);
|
|
555
|
-
return graph.traceFromEntries(entryPaths);
|
|
556
|
-
}
|
|
557
|
-
static fromPackageExports(packageJsonPath, options) {
|
|
558
|
-
const graph = new ImportGraph(options);
|
|
559
|
-
return graph.traceFromPackageExports(packageJsonPath);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
function expandWorkspaceGlob(rootDir, pattern) {
|
|
563
|
-
const base = pattern.replace(/\/\*+$/, "");
|
|
564
|
-
const baseDir = resolve(rootDir, base);
|
|
565
|
-
if (!existsSync(baseDir)) return [];
|
|
566
|
-
const results = [];
|
|
567
|
-
for (const entry of readdirSync(baseDir, {
|
|
568
|
-
withFileTypes: true
|
|
569
|
-
}))if (entry.isDirectory()) {
|
|
570
|
-
const candidate = join(baseDir, entry.name);
|
|
571
|
-
if (existsSync(join(candidate, "package.json"))) results.push(candidate);
|
|
572
|
-
}
|
|
573
|
-
return results;
|
|
574
|
-
}
|
|
575
|
-
function detectWorkspacePackages(rootDir) {
|
|
576
|
-
const pnpmWorkspacePath = join(rootDir, "pnpm-workspace.yaml");
|
|
577
|
-
if (existsSync(pnpmWorkspacePath)) try {
|
|
578
|
-
const content = readFileSync(pnpmWorkspacePath, "utf-8");
|
|
579
|
-
const parsed = parse(content);
|
|
580
|
-
if (Array.isArray(parsed?.packages)) return parsed.packages.flatMap((pattern)=>expandWorkspaceGlob(rootDir, pattern));
|
|
581
|
-
} catch {}
|
|
582
|
-
const rootPkgPath = join(rootDir, "package.json");
|
|
583
|
-
if (existsSync(rootPkgPath)) try {
|
|
584
|
-
const pkg = JSON.parse(readFileSync(rootPkgPath, "utf-8"));
|
|
585
|
-
const patterns = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
586
|
-
if (Array.isArray(patterns)) return patterns.flatMap((pattern)=>expandWorkspaceGlob(rootDir, pattern));
|
|
587
|
-
} catch {}
|
|
588
|
-
return [];
|
|
589
|
-
}
|
|
590
|
-
class TsDocResolver {
|
|
591
|
-
options;
|
|
592
|
-
constructor(options){
|
|
593
|
-
this.options = options;
|
|
594
|
-
}
|
|
595
|
-
resolve() {
|
|
596
|
-
const { rootDir } = this.options;
|
|
597
|
-
const workspaces = [];
|
|
598
|
-
const repoTsdocPath = join(rootDir, "tsdoc.json");
|
|
599
|
-
const repoTsdocConfig = existsSync(repoTsdocPath) ? repoTsdocPath : void 0;
|
|
600
|
-
const workspacePaths = detectWorkspacePackages(rootDir);
|
|
601
|
-
const isMonorepo = workspacePaths.length > 1;
|
|
602
|
-
if (0 === workspacePaths.length) {
|
|
603
|
-
const result = this.resolveWorkspace(rootDir, repoTsdocConfig);
|
|
604
|
-
if (result) workspaces.push(result);
|
|
605
|
-
} else for (const workspacePath of workspacePaths){
|
|
606
|
-
const result = this.resolveWorkspace(workspacePath, repoTsdocConfig);
|
|
607
|
-
if (result) workspaces.push(result);
|
|
608
|
-
}
|
|
609
|
-
const result = {
|
|
610
|
-
workspaces,
|
|
611
|
-
isMonorepo
|
|
612
|
-
};
|
|
613
|
-
if (void 0 !== repoTsdocConfig) result.repoTsdocConfig = repoTsdocConfig;
|
|
614
|
-
return result;
|
|
615
|
-
}
|
|
616
|
-
resolveWorkspace(workspacePath, repoTsdocConfig) {
|
|
617
|
-
const packageJsonPath = join(workspacePath, "package.json");
|
|
618
|
-
if (!existsSync(packageJsonPath)) return null;
|
|
619
|
-
let packageJson;
|
|
620
|
-
try {
|
|
621
|
-
const content = readFileSync(packageJsonPath, "utf-8");
|
|
622
|
-
packageJson = JSON.parse(content);
|
|
623
|
-
} catch {
|
|
624
|
-
return null;
|
|
625
|
-
}
|
|
626
|
-
const workspaceTsdocPath = join(workspacePath, "tsdoc.json");
|
|
627
|
-
const workspaceTsdocConfig = existsSync(workspaceTsdocPath) ? workspaceTsdocPath : void 0;
|
|
628
|
-
const tsdocConfigPath = workspaceTsdocConfig ?? repoTsdocConfig;
|
|
629
|
-
if (!tsdocConfigPath) return null;
|
|
630
|
-
if (!packageJson.exports) return null;
|
|
631
|
-
const name = packageJson.name ?? relative(this.options.rootDir, workspacePath);
|
|
632
|
-
const errors = [];
|
|
633
|
-
const graphOptions = {
|
|
634
|
-
rootDir: workspacePath
|
|
635
|
-
};
|
|
636
|
-
if (void 0 !== this.options.excludePatterns) graphOptions.excludePatterns = this.options.excludePatterns;
|
|
637
|
-
const graph = new ImportGraph(graphOptions);
|
|
638
|
-
const result = graph.traceFromPackageExports(packageJsonPath);
|
|
639
|
-
for (const error of result.errors)errors.push(error.message);
|
|
640
|
-
return {
|
|
641
|
-
name,
|
|
642
|
-
path: workspacePath,
|
|
643
|
-
tsdocConfigPath,
|
|
644
|
-
files: result.files,
|
|
645
|
-
errors
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
filterStagedFiles(stagedFiles) {
|
|
649
|
-
const result = this.resolve();
|
|
650
|
-
const output = [];
|
|
651
|
-
for (const workspace of result.workspaces){
|
|
652
|
-
const workspaceFiles = new Set(workspace.files);
|
|
653
|
-
const matchedFiles = stagedFiles.filter((f)=>workspaceFiles.has(f));
|
|
654
|
-
if (matchedFiles.length > 0) output.push({
|
|
655
|
-
files: matchedFiles,
|
|
656
|
-
tsdocConfigPath: workspace.tsdocConfigPath
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
return output;
|
|
660
|
-
}
|
|
661
|
-
needsLinting(filePath) {
|
|
662
|
-
const result = this.resolve();
|
|
663
|
-
for (const workspace of result.workspaces)if (workspace.files.includes(filePath)) return true;
|
|
664
|
-
return false;
|
|
665
|
-
}
|
|
666
|
-
getTsDocConfig(filePath) {
|
|
667
|
-
const result = this.resolve();
|
|
668
|
-
for (const workspace of result.workspaces)if (workspace.files.includes(filePath)) return workspace.tsdocConfigPath;
|
|
669
|
-
}
|
|
670
|
-
findWorkspace(filePath) {
|
|
671
|
-
const result = this.resolve();
|
|
672
|
-
for (const workspace of result.workspaces)if (filePath.startsWith(workspace.path)) return workspace;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
121
|
class TypeScript {
|
|
676
122
|
static glob = "*.{ts,cts,mts,tsx}";
|
|
677
123
|
static defaultExcludes = [];
|
|
678
|
-
static defaultTsdocExcludes = [
|
|
679
|
-
".test.",
|
|
680
|
-
".spec.",
|
|
681
|
-
"__test__",
|
|
682
|
-
"__tests__"
|
|
683
|
-
];
|
|
684
124
|
static cachedCompilerResult = null;
|
|
685
125
|
static detectCompiler(_cwd) {
|
|
686
126
|
if (null !== TypeScript.cachedCompilerResult) return TypeScript.cachedCompilerResult.compiler;
|
|
@@ -713,57 +153,23 @@ class TypeScript {
|
|
|
713
153
|
TypeScript.cachedCompilerResult = null;
|
|
714
154
|
}
|
|
715
155
|
static handler = TypeScript.create();
|
|
716
|
-
static isTsdocAvailable(cwd = process.cwd()) {
|
|
717
|
-
let dir = resolve(cwd);
|
|
718
|
-
while(true){
|
|
719
|
-
if (existsSync(join(dir, "tsdoc.json"))) return true;
|
|
720
|
-
const parent = dirname(dir);
|
|
721
|
-
if (parent === dir) break;
|
|
722
|
-
dir = parent;
|
|
723
|
-
}
|
|
724
|
-
return false;
|
|
725
|
-
}
|
|
726
156
|
static create(options = {}) {
|
|
727
157
|
const excludes = options.exclude ?? [
|
|
728
158
|
...TypeScript.defaultExcludes
|
|
729
159
|
];
|
|
730
|
-
const tsdocExcludes = options.excludeTsdoc ?? [
|
|
731
|
-
...TypeScript.defaultTsdocExcludes
|
|
732
|
-
];
|
|
733
|
-
const skipTsdoc = options.skipTsdoc ?? false;
|
|
734
160
|
const skipTypecheck = options.skipTypecheck ?? false;
|
|
735
|
-
const rootDir = options.rootDir ?? Command_Command.findRoot();
|
|
736
161
|
let typecheckCommand;
|
|
737
162
|
const getTypecheckCommand = ()=>{
|
|
738
163
|
if (void 0 === typecheckCommand) typecheckCommand = options.typecheckCommand ?? TypeScript.getDefaultTypecheckCommand();
|
|
739
164
|
return typecheckCommand;
|
|
740
165
|
};
|
|
741
|
-
return
|
|
166
|
+
return (filenames)=>{
|
|
742
167
|
const filtered = Filter.exclude(filenames, excludes);
|
|
743
168
|
if (0 === filtered.length) return [];
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
excludePatterns: [
|
|
749
|
-
...tsdocExcludes
|
|
750
|
-
]
|
|
751
|
-
});
|
|
752
|
-
const absoluteFiles = filtered.map((f)=>isAbsolute(f) ? f : join(rootDir, f));
|
|
753
|
-
const tsdocGroups = resolver.filterStagedFiles(absoluteFiles);
|
|
754
|
-
for (const group of tsdocGroups)if (group.files.length > 0) {
|
|
755
|
-
const linter = new TsDocLinter({
|
|
756
|
-
ignorePatterns: tsdocExcludes.map((p)=>`**/*${p}*`)
|
|
757
|
-
});
|
|
758
|
-
const results = await linter.lintFiles(group.files);
|
|
759
|
-
if (TsDocLinter.hasErrors(results)) {
|
|
760
|
-
const output = TsDocLinter.formatResults(results);
|
|
761
|
-
throw new Error(`TSDoc validation failed:\n${output}`);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
if (!skipTypecheck && filtered.length > 0) commands.push(getTypecheckCommand());
|
|
766
|
-
return commands;
|
|
169
|
+
if (!skipTypecheck) return [
|
|
170
|
+
getTypecheckCommand()
|
|
171
|
+
];
|
|
172
|
+
return [];
|
|
767
173
|
};
|
|
768
174
|
}
|
|
769
175
|
}
|
|
@@ -888,6 +294,6 @@ class Handler {
|
|
|
888
294
|
throw new Error("Handler.create() must be implemented by subclass");
|
|
889
295
|
}
|
|
890
296
|
}
|
|
891
|
-
export { Command, Filter, PnpmWorkspace, Yaml, checkCommand, fmtCommand, initCommand, rootCommand, runCli } from "./878.js";
|
|
297
|
+
export { Biome, Command, Filter, PnpmWorkspace, Yaml, checkCommand, fmtCommand, getWorkspacePackagePaths, getWorkspacePackages, getWorkspaceRoot, initCommand, isWorkspacePackagePath, resetWorkspaceCache, rootCommand, runCli } from "./878.js";
|
|
892
298
|
export { ConfigDiscovery, ConfigDiscoveryLive } from "@savvy-web/silk-effects";
|
|
893
|
-
export {
|
|
299
|
+
export { Handler, Markdown, PackageJson, Preset, ShellScripts, TypeScript, createConfig };
|