@xylabs/ts-scripts-yarn3 7.4.10 → 7.4.11
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/actions/deplint/checkPackage/checkPackage.mjs +115 -16
- package/dist/actions/deplint/checkPackage/checkPackage.mjs.map +1 -1
- package/dist/actions/deplint/checkPackage/getUnusedDependencies.mjs +2 -1
- package/dist/actions/deplint/checkPackage/getUnusedDependencies.mjs.map +1 -1
- package/dist/actions/deplint/checkPackage/getUnusedDevDependencies.mjs +114 -20
- package/dist/actions/deplint/checkPackage/getUnusedDevDependencies.mjs.map +1 -1
- package/dist/actions/deplint/checkPackage/getUnusedPeerDependencies.mjs +2 -1
- package/dist/actions/deplint/checkPackage/getUnusedPeerDependencies.mjs.map +1 -1
- package/dist/actions/deplint/checkPackage/index.mjs +115 -16
- package/dist/actions/deplint/checkPackage/index.mjs.map +1 -1
- package/dist/actions/deplint/deplint.mjs +166 -38
- package/dist/actions/deplint/deplint.mjs.map +1 -1
- package/dist/actions/deplint/getCliReferencedPackagesFromFiles.mjs +140 -0
- package/dist/actions/deplint/getCliReferencedPackagesFromFiles.mjs.map +1 -0
- package/dist/actions/deplint/getScriptReferencedPackages.mjs +3 -1
- package/dist/actions/deplint/getScriptReferencedPackages.mjs.map +1 -1
- package/dist/actions/deplint/index.mjs +166 -38
- package/dist/actions/deplint/index.mjs.map +1 -1
- package/dist/actions/index.mjs +147 -40
- package/dist/actions/index.mjs.map +1 -1
- package/dist/bin/xy.mjs +251 -117
- package/dist/bin/xy.mjs.map +1 -1
- package/dist/index.d.ts +23 -2
- package/dist/index.mjs +155 -42
- package/dist/index.mjs.map +1 -1
- package/dist/xy/index.mjs +251 -117
- package/dist/xy/index.mjs.map +1 -1
- package/dist/xy/xy.mjs +251 -117
- package/dist/xy/xy.mjs.map +1 -1
- package/dist/xy/xyLintCommands.mjs +205 -71
- package/dist/xy/xyLintCommands.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
// src/actions/deplint/checkPackage/getUnusedDevDependencies.ts
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
|
|
4
|
+
// src/actions/deplint/getCliReferencedPackagesFromFiles.ts
|
|
5
|
+
import fs3 from "fs";
|
|
6
|
+
import path3 from "path";
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
|
|
9
|
+
// src/actions/deplint/getBasePackageName.ts
|
|
10
|
+
function getBasePackageName(importName) {
|
|
11
|
+
const importNameScrubbed = importName.replaceAll('"', "").trim();
|
|
12
|
+
if (importNameScrubbed.startsWith("@")) {
|
|
13
|
+
const parts = importNameScrubbed.split("/");
|
|
14
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : importNameScrubbed;
|
|
15
|
+
}
|
|
16
|
+
return importNameScrubbed.split("/")[0];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/actions/deplint/getScriptReferencedPackages.ts
|
|
20
|
+
import fs2 from "fs";
|
|
21
|
+
import path2 from "path";
|
|
22
|
+
|
|
4
23
|
// src/actions/deplint/getRequiredPeerDependencies.ts
|
|
5
24
|
import fs from "fs";
|
|
6
25
|
import path from "path";
|
|
@@ -33,20 +52,6 @@ function getRequiredPeerDependencies(location, allDeps) {
|
|
|
33
52
|
return required;
|
|
34
53
|
}
|
|
35
54
|
|
|
36
|
-
// src/actions/deplint/getScriptReferencedPackages.ts
|
|
37
|
-
import fs2 from "fs";
|
|
38
|
-
import path2 from "path";
|
|
39
|
-
|
|
40
|
-
// src/actions/deplint/getBasePackageName.ts
|
|
41
|
-
function getBasePackageName(importName) {
|
|
42
|
-
const importNameScrubbed = importName.replaceAll('"', "").trim();
|
|
43
|
-
if (importNameScrubbed.startsWith("@")) {
|
|
44
|
-
const parts = importNameScrubbed.split("/");
|
|
45
|
-
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : importNameScrubbed;
|
|
46
|
-
}
|
|
47
|
-
return importNameScrubbed.split("/")[0];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
55
|
// src/actions/deplint/getScriptReferencedPackages.ts
|
|
51
56
|
function getBinNames(location, dep) {
|
|
52
57
|
const depPkgPath = findDepPackageJson(location, dep);
|
|
@@ -97,15 +102,101 @@ function getScriptReferencedPackages(location, allDeps) {
|
|
|
97
102
|
return referenced;
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
// src/actions/deplint/getCliReferencedPackagesFromFiles.ts
|
|
106
|
+
var shellCommandFunctions = /* @__PURE__ */ new Set(["execSync", "exec"]);
|
|
107
|
+
var directExecFunctions = /* @__PURE__ */ new Set(["spawn", "spawnSync", "execFile", "execFileSync"]);
|
|
108
|
+
var allExecFunctions = /* @__PURE__ */ new Set([...shellCommandFunctions, ...directExecFunctions]);
|
|
109
|
+
function getCommandTokensFromFile(filePath) {
|
|
110
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
111
|
+
let sourceCode;
|
|
112
|
+
try {
|
|
113
|
+
sourceCode = fs3.readFileSync(filePath, "utf8");
|
|
114
|
+
} catch {
|
|
115
|
+
return tokens;
|
|
116
|
+
}
|
|
117
|
+
const isMjsFile = filePath.endsWith(".mjs");
|
|
118
|
+
const sourceFile = ts.createSourceFile(
|
|
119
|
+
path3.basename(filePath),
|
|
120
|
+
sourceCode,
|
|
121
|
+
ts.ScriptTarget.Latest,
|
|
122
|
+
true,
|
|
123
|
+
isMjsFile ? ts.ScriptKind.JS : void 0
|
|
124
|
+
);
|
|
125
|
+
function visit(node) {
|
|
126
|
+
if (ts.isCallExpression(node) && node.arguments.length > 0) {
|
|
127
|
+
const fnName = getFunctionName(node.expression);
|
|
128
|
+
if (fnName && allExecFunctions.has(fnName)) {
|
|
129
|
+
const firstArg = node.arguments[0];
|
|
130
|
+
if (ts.isStringLiteral(firstArg) || ts.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
131
|
+
const value = firstArg.text;
|
|
132
|
+
if (shellCommandFunctions.has(fnName)) {
|
|
133
|
+
for (const token of tokenizeScript(value)) {
|
|
134
|
+
tokens.add(token);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
tokens.add(value);
|
|
138
|
+
}
|
|
139
|
+
} else if (ts.isTemplateExpression(firstArg)) {
|
|
140
|
+
const head = firstArg.head.text;
|
|
141
|
+
if (head) {
|
|
142
|
+
for (const token of tokenizeScript(head)) {
|
|
143
|
+
tokens.add(token);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
ts.forEachChild(node, visit);
|
|
150
|
+
}
|
|
151
|
+
visit(sourceFile);
|
|
152
|
+
return tokens;
|
|
153
|
+
}
|
|
154
|
+
function getFunctionName(expr) {
|
|
155
|
+
if (ts.isIdentifier(expr)) {
|
|
156
|
+
return expr.text;
|
|
157
|
+
}
|
|
158
|
+
if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) {
|
|
159
|
+
return expr.name.text;
|
|
160
|
+
}
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
function getCliReferencedPackagesFromFiles(allFiles, location, allDeps) {
|
|
164
|
+
const allTokens = /* @__PURE__ */ new Set();
|
|
165
|
+
for (const file of allFiles) {
|
|
166
|
+
for (const token of getCommandTokensFromFile(file)) {
|
|
167
|
+
allTokens.add(token);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (allTokens.size === 0) return /* @__PURE__ */ new Set();
|
|
171
|
+
const binToPackage = /* @__PURE__ */ new Map();
|
|
172
|
+
for (const dep of allDeps) {
|
|
173
|
+
for (const bin of getBinNames(location, dep)) {
|
|
174
|
+
binToPackage.set(bin, dep);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
178
|
+
for (const token of allTokens) {
|
|
179
|
+
const baseName = getBasePackageName(token);
|
|
180
|
+
if (allDeps.includes(baseName)) {
|
|
181
|
+
referenced.add(baseName);
|
|
182
|
+
}
|
|
183
|
+
const pkg = binToPackage.get(token);
|
|
184
|
+
if (pkg) {
|
|
185
|
+
referenced.add(pkg);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return referenced;
|
|
189
|
+
}
|
|
190
|
+
|
|
100
191
|
// src/actions/deplint/implicitDevDependencies.ts
|
|
101
|
-
import
|
|
192
|
+
import fs4 from "fs";
|
|
102
193
|
var hasFileWithExtension = (files, extensions) => files.some((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
103
194
|
var tsExtensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
104
195
|
var hasTypescriptFiles = ({ allFiles }) => hasFileWithExtension(allFiles, tsExtensions);
|
|
105
196
|
var decoratorPattern = /^\s*@[a-zA-Z]\w*/m;
|
|
106
197
|
var hasDecorators = ({ allFiles }) => allFiles.filter((f) => tsExtensions.some((ext) => f.endsWith(ext))).some((file) => {
|
|
107
198
|
try {
|
|
108
|
-
const content =
|
|
199
|
+
const content = fs4.readFileSync(file, "utf8");
|
|
109
200
|
return decoratorPattern.test(content);
|
|
110
201
|
} catch {
|
|
111
202
|
return false;
|
|
@@ -118,7 +209,7 @@ function hasImportPlugin({ location, allDependencies }) {
|
|
|
118
209
|
const pkgPath = findDepPackageJson(location, dep);
|
|
119
210
|
if (!pkgPath) continue;
|
|
120
211
|
try {
|
|
121
|
-
const pkg = JSON.parse(
|
|
212
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
|
|
122
213
|
const transitiveDeps = [
|
|
123
214
|
...Object.keys(pkg.dependencies ?? {}),
|
|
124
215
|
...Object.keys(pkg.peerDependencies ?? {})
|
|
@@ -170,10 +261,11 @@ var allExternalImports = ({
|
|
|
170
261
|
...externalDistTypeImports
|
|
171
262
|
]);
|
|
172
263
|
};
|
|
173
|
-
function isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs) {
|
|
264
|
+
function isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs, cliRefs) {
|
|
174
265
|
if (implicitDeps.has(dep)) return true;
|
|
175
266
|
if (requiredPeers.has(dep)) return true;
|
|
176
267
|
if (scriptRefs.has(dep)) return true;
|
|
268
|
+
if (cliRefs.has(dep)) return true;
|
|
177
269
|
if (dep.startsWith("@types/")) {
|
|
178
270
|
const baseName = dep.replace(/^@types\//, "");
|
|
179
271
|
return allImports.has(baseName) || allImports.has(dep) || implicitDeps.has(baseName);
|
|
@@ -184,7 +276,7 @@ function getUnusedDevDependencies({ name, location }, {
|
|
|
184
276
|
devDependencies,
|
|
185
277
|
dependencies,
|
|
186
278
|
peerDependencies
|
|
187
|
-
}, sourceParams, fileContext) {
|
|
279
|
+
}, sourceParams, fileContext, exclude) {
|
|
188
280
|
const allImports = allExternalImports(sourceParams);
|
|
189
281
|
const allDeps = [...dependencies, ...devDependencies, ...peerDependencies];
|
|
190
282
|
const implicitDeps = getImplicitDevDependencies({
|
|
@@ -194,10 +286,12 @@ function getUnusedDevDependencies({ name, location }, {
|
|
|
194
286
|
});
|
|
195
287
|
const requiredPeers = getRequiredPeerDependencies(location, allDeps);
|
|
196
288
|
const scriptRefs = getScriptReferencedPackages(location, allDeps);
|
|
289
|
+
const cliRefs = getCliReferencedPackagesFromFiles(fileContext.allFiles, location, allDeps);
|
|
197
290
|
let unusedDevDependencies = 0;
|
|
198
291
|
for (const dep of devDependencies) {
|
|
292
|
+
if (exclude?.has(dep)) continue;
|
|
199
293
|
if (dependencies.includes(dep) || peerDependencies.includes(dep)) continue;
|
|
200
|
-
if (!isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs)) {
|
|
294
|
+
if (!isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs, cliRefs)) {
|
|
201
295
|
unusedDevDependencies++;
|
|
202
296
|
console.log(`[${chalk.blue(name)}] Unused devDependency in package.json: ${chalk.red(dep)}`);
|
|
203
297
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/actions/deplint/checkPackage/getUnusedDevDependencies.ts","../../../../src/actions/deplint/getRequiredPeerDependencies.ts","../../../../src/actions/deplint/getScriptReferencedPackages.ts","../../../../src/actions/deplint/getBasePackageName.ts","../../../../src/actions/deplint/implicitDevDependencies.ts"],"sourcesContent":["import chalk from 'chalk'\n\nimport type { Workspace } from '../../../lib/index.ts'\nimport { getRequiredPeerDependencies } from '../getRequiredPeerDependencies.ts'\nimport { getScriptReferencedPackages } from '../getScriptReferencedPackages.ts'\nimport type { FileContext } from '../implicitDevDependencies.ts'\nimport { getImplicitDevDependencies } from '../implicitDevDependencies.ts'\nimport type { CheckPackageParams, CheckSourceParams } from './checkPackageTypes.ts'\n\nconst allExternalImports = ({\n externalAllImports,\n externalDistImports,\n externalDistTypeImports,\n}: CheckSourceParams) => {\n return new Set<string>([\n ...externalAllImports,\n ...externalDistImports,\n ...externalDistTypeImports,\n ])\n}\n\nfunction isDevDepUsed(\n dep: string,\n allImports: Set<string>,\n implicitDeps: Set<string>,\n requiredPeers: Set<string>,\n scriptRefs: Set<string>,\n) {\n if (implicitDeps.has(dep)) return true\n if (requiredPeers.has(dep)) return true\n if (scriptRefs.has(dep)) return true\n\n if (dep.startsWith('@types/')) {\n const baseName = dep.replace(/^@types\\//, '')\n return allImports.has(baseName) || allImports.has(dep) || implicitDeps.has(baseName)\n }\n\n return allImports.has(dep)\n}\n\nexport function getUnusedDevDependencies(\n { name, location }: Workspace,\n {\n devDependencies, dependencies, peerDependencies,\n }: CheckPackageParams,\n sourceParams: CheckSourceParams,\n fileContext: FileContext,\n) {\n const allImports = allExternalImports(sourceParams)\n const allDeps = [...dependencies, ...devDependencies, ...peerDependencies]\n const implicitDeps = getImplicitDevDependencies({\n ...fileContext, allDependencies: allDeps, location,\n })\n const requiredPeers = getRequiredPeerDependencies(location, allDeps)\n const scriptRefs = getScriptReferencedPackages(location, allDeps)\n let unusedDevDependencies = 0\n for (const dep of devDependencies) {\n // Skip devDeps that are also declared as dependencies or peerDependencies\n if (dependencies.includes(dep) || peerDependencies.includes(dep)) continue\n\n if (!isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs)) {\n unusedDevDependencies++\n console.log(`[${chalk.blue(name)}] Unused devDependency in package.json: ${chalk.red(dep)}`)\n }\n }\n if (unusedDevDependencies > 0) {\n const packageLocation = `${location}/package.json`\n console.log(` ${chalk.yellow(packageLocation)}\\n`)\n }\n return unusedDevDependencies\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nexport function findDepPackageJson(location: string, dep: string): string | undefined {\n let dir = location\n while (true) {\n const candidate = path.join(dir, 'node_modules', dep, 'package.json')\n if (fs.existsSync(candidate)) return candidate\n const parent = path.dirname(dir)\n if (parent === dir) return undefined\n dir = parent\n }\n}\n\n/**\n * Collects the peerDependencies declared by all of a package's\n * dependencies and devDependencies. A devDependency that satisfies\n * one of these peer requirements should not be flagged as unused.\n */\nexport function getRequiredPeerDependencies(\n location: string,\n allDeps: string[],\n): Set<string> {\n const required = new Set<string>()\n for (const dep of allDeps) {\n const depPkgPath = findDepPackageJson(location, dep)\n if (!depPkgPath) continue\n try {\n const raw = fs.readFileSync(depPkgPath, 'utf8')\n const pkg = JSON.parse(raw)\n if (pkg.peerDependencies) {\n for (const peer of Object.keys(pkg.peerDependencies)) {\n required.add(peer)\n }\n }\n } catch {\n // Package not readable — skip\n }\n }\n return required\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nimport { getBasePackageName } from './getBasePackageName.ts'\nimport { findDepPackageJson } from './getRequiredPeerDependencies.ts'\n\nfunction getBinNames(location: string, dep: string): string[] {\n const depPkgPath = findDepPackageJson(location, dep)\n if (!depPkgPath) return []\n try {\n const raw = fs.readFileSync(depPkgPath, 'utf8')\n const pkg = JSON.parse(raw)\n if (!pkg.bin) return []\n if (typeof pkg.bin === 'string') return [pkg.name?.split('/').pop() ?? dep]\n return Object.keys(pkg.bin)\n } catch {\n return []\n }\n}\n\nfunction tokenizeScript(script: string): string[] {\n // Split on shell operators and whitespace to get command tokens\n return script\n .split(/[&|;$()\"`\\s]+/)\n .map(t => t.trim())\n .filter(Boolean)\n}\n\n/**\n * Scans package.json scripts for references to installed packages,\n * either by package name or by binary name they provide.\n */\nexport function getScriptReferencedPackages(\n location: string,\n allDeps: string[],\n): Set<string> {\n const pkgPath = path.join(location, 'package.json')\n let scripts: Record<string, string> = {}\n try {\n const raw = fs.readFileSync(pkgPath, 'utf8')\n const pkg = JSON.parse(raw)\n scripts = pkg.scripts ?? {}\n } catch {\n return new Set()\n }\n\n const scriptText = Object.values(scripts).join(' ')\n const tokens = new Set(tokenizeScript(scriptText))\n\n // Build a map from bin name -> package name\n const binToPackage = new Map<string, string>()\n for (const dep of allDeps) {\n const bins = getBinNames(location, dep)\n for (const bin of bins) {\n binToPackage.set(bin, dep)\n }\n }\n\n const referenced = new Set<string>()\n for (const token of tokens) {\n // Direct package name match (e.g. \"yarn rimraf\" -> token \"rimraf\")\n const baseName = getBasePackageName(token)\n if (allDeps.includes(baseName)) {\n referenced.add(baseName)\n }\n // Binary name match (e.g. \"tsup\" -> @xylabs/ts-scripts-yarn3 provides \"tsup\"? no, tsup provides \"tsup\")\n const pkg = binToPackage.get(token)\n if (pkg) {\n referenced.add(pkg)\n }\n }\n\n return referenced\n}\n","export function getBasePackageName(importName: string) {\n const importNameScrubbed = importName.replaceAll('\"', '').trim()\n if (importNameScrubbed.startsWith('@')) {\n const parts = importNameScrubbed.split('/')\n return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : importNameScrubbed\n }\n return importNameScrubbed.split('/')[0]\n}\n","import fs from 'node:fs'\n\nimport { findDepPackageJson } from './getRequiredPeerDependencies.ts'\n\nexport interface ImplicitDevDependencyRule {\n isNeeded: (context: ImplicitDepContext) => boolean\n package: string\n}\n\nexport interface FileContext {\n allFiles: string[]\n distFiles: string[]\n}\n\nexport interface ImplicitDepContext extends FileContext {\n allDependencies: string[]\n location: string\n}\n\nconst hasFileWithExtension = (files: string[], extensions: string[]) =>\n files.some(f => extensions.some(ext => f.endsWith(ext)))\n\nconst tsExtensions = ['.ts', '.tsx', '.mts', '.cts']\n\nconst hasTypescriptFiles = ({ allFiles }: ImplicitDepContext) =>\n hasFileWithExtension(allFiles, tsExtensions)\n\n// Matches decorator usage: @something at the start of a line (after optional whitespace).\n// Safe from JSDoc false positives since those appear after * in comment blocks.\nconst decoratorPattern = /^\\s*@[a-zA-Z]\\w*/m\n\nconst hasDecorators = ({ allFiles }: ImplicitDepContext) =>\n allFiles\n .filter(f => tsExtensions.some(ext => f.endsWith(ext)))\n .some((file) => {\n try {\n const content = fs.readFileSync(file, 'utf8')\n return decoratorPattern.test(content)\n } catch {\n return false\n }\n })\n\nconst importPlugins = new Set(['eslint-plugin-import-x', 'eslint-plugin-import'])\n\n/**\n * Checks whether any dependency (direct or transitive) pulls in\n * one of the eslint import plugins that require a resolver.\n */\nfunction hasImportPlugin({ location, allDependencies }: ImplicitDepContext): boolean {\n // Direct dependency on the plugin\n if (allDependencies.some(d => importPlugins.has(d))) return true\n\n // Transitive: a dependency bundles the plugin as a dep or peer\n for (const dep of allDependencies) {\n const pkgPath = findDepPackageJson(location, dep)\n if (!pkgPath) continue\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))\n const transitiveDeps = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ]\n if (transitiveDeps.some(d => importPlugins.has(d))) return true\n } catch {\n // skip unreadable packages\n }\n }\n return false\n}\n\nconst hasVitest = ({ allDependencies }: ImplicitDepContext) =>\n allDependencies.includes('vitest')\n\nconst rules: ImplicitDevDependencyRule[] = [\n {\n package: 'typescript',\n isNeeded: hasTypescriptFiles,\n },\n {\n package: 'eslint-import-resolver-typescript',\n isNeeded: context =>\n hasTypescriptFiles(context)\n && context.allDependencies.includes('eslint')\n && hasImportPlugin(context),\n },\n {\n package: 'tslib',\n isNeeded: hasDecorators,\n },\n {\n package: '@vitest/coverage-v8',\n isNeeded: hasVitest,\n },\n]\n\nexport function getImplicitDevDependencies(context: ImplicitDepContext): Set<string> {\n const implicit = new Set<string>()\n for (const rule of rules) {\n if (rule.isNeeded(context)) {\n implicit.add(rule.package)\n }\n }\n return implicit\n}\n"],"mappings":"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","names":["fs","path","fs","path","fs","fs"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/actions/deplint/checkPackage/getUnusedDevDependencies.ts","../../../../src/actions/deplint/getCliReferencedPackagesFromFiles.ts","../../../../src/actions/deplint/getBasePackageName.ts","../../../../src/actions/deplint/getScriptReferencedPackages.ts","../../../../src/actions/deplint/getRequiredPeerDependencies.ts","../../../../src/actions/deplint/implicitDevDependencies.ts"],"sourcesContent":["import chalk from 'chalk'\n\nimport type { Workspace } from '../../../lib/index.ts'\nimport { getCliReferencedPackagesFromFiles } from '../getCliReferencedPackagesFromFiles.ts'\nimport { getRequiredPeerDependencies } from '../getRequiredPeerDependencies.ts'\nimport { getScriptReferencedPackages } from '../getScriptReferencedPackages.ts'\nimport type { FileContext } from '../implicitDevDependencies.ts'\nimport { getImplicitDevDependencies } from '../implicitDevDependencies.ts'\nimport type { CheckPackageParams, CheckSourceParams } from './checkPackageTypes.ts'\n\nconst allExternalImports = ({\n externalAllImports,\n externalDistImports,\n externalDistTypeImports,\n}: CheckSourceParams) => {\n return new Set<string>([\n ...externalAllImports,\n ...externalDistImports,\n ...externalDistTypeImports,\n ])\n}\n\nfunction isDevDepUsed(\n dep: string,\n allImports: Set<string>,\n implicitDeps: Set<string>,\n requiredPeers: Set<string>,\n scriptRefs: Set<string>,\n cliRefs: Set<string>,\n) {\n if (implicitDeps.has(dep)) return true\n if (requiredPeers.has(dep)) return true\n if (scriptRefs.has(dep)) return true\n if (cliRefs.has(dep)) return true\n\n if (dep.startsWith('@types/')) {\n const baseName = dep.replace(/^@types\\//, '')\n return allImports.has(baseName) || allImports.has(dep) || implicitDeps.has(baseName)\n }\n\n return allImports.has(dep)\n}\n\nexport function getUnusedDevDependencies(\n { name, location }: Workspace,\n {\n devDependencies, dependencies, peerDependencies,\n }: CheckPackageParams,\n sourceParams: CheckSourceParams,\n fileContext: FileContext,\n // Package names to skip (from xy.config deplint.exclude)\n exclude?: Set<string>,\n) {\n const allImports = allExternalImports(sourceParams)\n const allDeps = [...dependencies, ...devDependencies, ...peerDependencies]\n const implicitDeps = getImplicitDevDependencies({\n ...fileContext, allDependencies: allDeps, location,\n })\n const requiredPeers = getRequiredPeerDependencies(location, allDeps)\n const scriptRefs = getScriptReferencedPackages(location, allDeps)\n // Detect packages referenced via child_process calls (execSync, spawn, etc.)\n // in source files, e.g. execSync('npx typedoc ...') marks typedoc as used.\n const cliRefs = getCliReferencedPackagesFromFiles(fileContext.allFiles, location, allDeps)\n let unusedDevDependencies = 0\n for (const dep of devDependencies) {\n if (exclude?.has(dep)) continue\n // Skip devDeps that are also declared as dependencies or peerDependencies\n if (dependencies.includes(dep) || peerDependencies.includes(dep)) continue\n\n if (!isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs, cliRefs)) {\n unusedDevDependencies++\n console.log(`[${chalk.blue(name)}] Unused devDependency in package.json: ${chalk.red(dep)}`)\n }\n }\n if (unusedDevDependencies > 0) {\n const packageLocation = `${location}/package.json`\n console.log(` ${chalk.yellow(packageLocation)}\\n`)\n }\n return unusedDevDependencies\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nimport ts from 'typescript'\n\nimport { getBasePackageName } from './getBasePackageName.ts'\nimport { getBinNames, tokenizeScript } from './getScriptReferencedPackages.ts'\n\n/**\n * Names of child_process functions that execute shell commands as a single\n * string (first argument is a command string to tokenize).\n */\nconst shellCommandFunctions = new Set(['execSync', 'exec'])\n\n/**\n * Names of child_process functions where the first argument is the\n * executable name directly (not a full shell command string).\n */\nconst directExecFunctions = new Set(['spawn', 'spawnSync', 'execFile', 'execFileSync'])\n\n/**\n * All child_process function names we scan for.\n */\nconst allExecFunctions = new Set([...shellCommandFunctions, ...directExecFunctions])\n\n/**\n * Extracts command strings from child_process calls (execSync, spawn, etc.)\n * found in a single source file.\n *\n * For shell-style calls (execSync, exec) the first argument is a command\n * string like \"npx typedoc --options foo\" — we tokenize it and return the\n * tokens. For direct-exec calls (spawn, spawnSync, execFile, execFileSync)\n * the first argument is the executable name itself.\n */\nfunction getCommandTokensFromFile(filePath: string): Set<string> {\n const tokens = new Set<string>()\n let sourceCode: string\n try {\n sourceCode = fs.readFileSync(filePath, 'utf8')\n } catch {\n return tokens\n }\n\n const isMjsFile = filePath.endsWith('.mjs')\n const sourceFile = ts.createSourceFile(\n path.basename(filePath),\n sourceCode,\n ts.ScriptTarget.Latest,\n true,\n isMjsFile ? ts.ScriptKind.JS : undefined,\n )\n\n function visit(node: ts.Node) {\n if (ts.isCallExpression(node) && node.arguments.length > 0) {\n const fnName = getFunctionName(node.expression)\n if (fnName && allExecFunctions.has(fnName)) {\n const firstArg = node.arguments[0]\n if (ts.isStringLiteral(firstArg) || ts.isNoSubstitutionTemplateLiteral(firstArg)) {\n const value = firstArg.text\n if (shellCommandFunctions.has(fnName)) {\n // Shell command string — tokenize to extract the executable and args\n for (const token of tokenizeScript(value)) {\n tokens.add(token)\n }\n } else {\n // Direct exec — first arg is the executable name\n tokens.add(value)\n }\n } else if (ts.isTemplateExpression(firstArg)) {\n // Template literal like `npx typedoc --options ${path}` — extract\n // the static head which usually contains the command name\n const head = firstArg.head.text\n if (head) {\n for (const token of tokenizeScript(head)) {\n tokens.add(token)\n }\n }\n }\n }\n }\n ts.forEachChild(node, visit)\n }\n\n visit(sourceFile)\n return tokens\n}\n\n/**\n * Resolves the function name from a call expression, handling both direct\n * calls like `execSync(...)` and qualified calls like `child_process.execSync(...)`.\n */\nfunction getFunctionName(expr: ts.Expression): string | undefined {\n if (ts.isIdentifier(expr)) {\n return expr.text\n }\n if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) {\n return expr.name.text\n }\n return undefined\n}\n\n/**\n * Scans source files for child_process calls (execSync, spawn, etc.) and\n * resolves referenced packages by matching command tokens against known\n * dependency binary names.\n *\n * This complements getScriptReferencedPackages (which scans package.json\n * scripts) by catching CLI usage in actual source code, e.g.:\n * execSync(`npx typedoc --options ${configPath}`)\n * spawnSync('eslint', ['--fix', '.'])\n */\nexport function getCliReferencedPackagesFromFiles(\n allFiles: string[],\n location: string,\n allDeps: string[],\n): Set<string> {\n // Collect all command tokens from every source file\n const allTokens = new Set<string>()\n for (const file of allFiles) {\n for (const token of getCommandTokensFromFile(file)) {\n allTokens.add(token)\n }\n }\n\n if (allTokens.size === 0) return new Set()\n\n // Build bin-name -> package-name map (same approach as getScriptReferencedPackages)\n const binToPackage = new Map<string, string>()\n for (const dep of allDeps) {\n for (const bin of getBinNames(location, dep)) {\n binToPackage.set(bin, dep)\n }\n }\n\n const referenced = new Set<string>()\n for (const token of allTokens) {\n // Direct package name match\n const baseName = getBasePackageName(token)\n if (allDeps.includes(baseName)) {\n referenced.add(baseName)\n }\n // Binary name match (e.g. token \"typedoc\" -> package \"typedoc\")\n const pkg = binToPackage.get(token)\n if (pkg) {\n referenced.add(pkg)\n }\n }\n\n return referenced\n}\n","export function getBasePackageName(importName: string) {\n const importNameScrubbed = importName.replaceAll('\"', '').trim()\n if (importNameScrubbed.startsWith('@')) {\n const parts = importNameScrubbed.split('/')\n return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : importNameScrubbed\n }\n return importNameScrubbed.split('/')[0]\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nimport { getBasePackageName } from './getBasePackageName.ts'\nimport { findDepPackageJson } from './getRequiredPeerDependencies.ts'\n\nexport function getBinNames(location: string, dep: string): string[] {\n const depPkgPath = findDepPackageJson(location, dep)\n if (!depPkgPath) return []\n try {\n const raw = fs.readFileSync(depPkgPath, 'utf8')\n const pkg = JSON.parse(raw)\n if (!pkg.bin) return []\n if (typeof pkg.bin === 'string') return [pkg.name?.split('/').pop() ?? dep]\n return Object.keys(pkg.bin)\n } catch {\n return []\n }\n}\n\nexport function tokenizeScript(script: string): string[] {\n // Split on shell operators and whitespace to get command tokens\n return script\n .split(/[&|;$()\"`\\s]+/)\n .map(t => t.trim())\n .filter(Boolean)\n}\n\n/**\n * Scans package.json scripts for references to installed packages,\n * either by package name or by binary name they provide.\n */\nexport function getScriptReferencedPackages(\n location: string,\n allDeps: string[],\n): Set<string> {\n const pkgPath = path.join(location, 'package.json')\n let scripts: Record<string, string> = {}\n try {\n const raw = fs.readFileSync(pkgPath, 'utf8')\n const pkg = JSON.parse(raw)\n scripts = pkg.scripts ?? {}\n } catch {\n return new Set()\n }\n\n const scriptText = Object.values(scripts).join(' ')\n const tokens = new Set(tokenizeScript(scriptText))\n\n // Build a map from bin name -> package name\n const binToPackage = new Map<string, string>()\n for (const dep of allDeps) {\n const bins = getBinNames(location, dep)\n for (const bin of bins) {\n binToPackage.set(bin, dep)\n }\n }\n\n const referenced = new Set<string>()\n for (const token of tokens) {\n // Direct package name match (e.g. \"yarn rimraf\" -> token \"rimraf\")\n const baseName = getBasePackageName(token)\n if (allDeps.includes(baseName)) {\n referenced.add(baseName)\n }\n // Binary name match (e.g. \"tsup\" -> @xylabs/ts-scripts-yarn3 provides \"tsup\"? no, tsup provides \"tsup\")\n const pkg = binToPackage.get(token)\n if (pkg) {\n referenced.add(pkg)\n }\n }\n\n return referenced\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nexport function findDepPackageJson(location: string, dep: string): string | undefined {\n let dir = location\n while (true) {\n const candidate = path.join(dir, 'node_modules', dep, 'package.json')\n if (fs.existsSync(candidate)) return candidate\n const parent = path.dirname(dir)\n if (parent === dir) return undefined\n dir = parent\n }\n}\n\n/**\n * Collects the peerDependencies declared by all of a package's\n * dependencies and devDependencies. A devDependency that satisfies\n * one of these peer requirements should not be flagged as unused.\n */\nexport function getRequiredPeerDependencies(\n location: string,\n allDeps: string[],\n): Set<string> {\n const required = new Set<string>()\n for (const dep of allDeps) {\n const depPkgPath = findDepPackageJson(location, dep)\n if (!depPkgPath) continue\n try {\n const raw = fs.readFileSync(depPkgPath, 'utf8')\n const pkg = JSON.parse(raw)\n if (pkg.peerDependencies) {\n for (const peer of Object.keys(pkg.peerDependencies)) {\n required.add(peer)\n }\n }\n } catch {\n // Package not readable — skip\n }\n }\n return required\n}\n","import fs from 'node:fs'\n\nimport { findDepPackageJson } from './getRequiredPeerDependencies.ts'\n\nexport interface ImplicitDevDependencyRule {\n isNeeded: (context: ImplicitDepContext) => boolean\n package: string\n}\n\nexport interface FileContext {\n allFiles: string[]\n distFiles: string[]\n}\n\nexport interface ImplicitDepContext extends FileContext {\n allDependencies: string[]\n location: string\n}\n\nconst hasFileWithExtension = (files: string[], extensions: string[]) =>\n files.some(f => extensions.some(ext => f.endsWith(ext)))\n\nconst tsExtensions = ['.ts', '.tsx', '.mts', '.cts']\n\nconst hasTypescriptFiles = ({ allFiles }: ImplicitDepContext) =>\n hasFileWithExtension(allFiles, tsExtensions)\n\n// Matches decorator usage: @something at the start of a line (after optional whitespace).\n// Safe from JSDoc false positives since those appear after * in comment blocks.\nconst decoratorPattern = /^\\s*@[a-zA-Z]\\w*/m\n\nconst hasDecorators = ({ allFiles }: ImplicitDepContext) =>\n allFiles\n .filter(f => tsExtensions.some(ext => f.endsWith(ext)))\n .some((file) => {\n try {\n const content = fs.readFileSync(file, 'utf8')\n return decoratorPattern.test(content)\n } catch {\n return false\n }\n })\n\nconst importPlugins = new Set(['eslint-plugin-import-x', 'eslint-plugin-import'])\n\n/**\n * Checks whether any dependency (direct or transitive) pulls in\n * one of the eslint import plugins that require a resolver.\n */\nfunction hasImportPlugin({ location, allDependencies }: ImplicitDepContext): boolean {\n // Direct dependency on the plugin\n if (allDependencies.some(d => importPlugins.has(d))) return true\n\n // Transitive: a dependency bundles the plugin as a dep or peer\n for (const dep of allDependencies) {\n const pkgPath = findDepPackageJson(location, dep)\n if (!pkgPath) continue\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))\n const transitiveDeps = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ]\n if (transitiveDeps.some(d => importPlugins.has(d))) return true\n } catch {\n // skip unreadable packages\n }\n }\n return false\n}\n\nconst hasVitest = ({ allDependencies }: ImplicitDepContext) =>\n allDependencies.includes('vitest')\n\nconst rules: ImplicitDevDependencyRule[] = [\n {\n package: 'typescript',\n isNeeded: hasTypescriptFiles,\n },\n {\n package: 'eslint-import-resolver-typescript',\n isNeeded: context =>\n hasTypescriptFiles(context)\n && context.allDependencies.includes('eslint')\n && hasImportPlugin(context),\n },\n {\n package: 'tslib',\n isNeeded: hasDecorators,\n },\n {\n package: '@vitest/coverage-v8',\n isNeeded: hasVitest,\n },\n]\n\nexport function getImplicitDevDependencies(context: ImplicitDepContext): Set<string> {\n const implicit = new Set<string>()\n for (const rule of rules) {\n if (rule.isNeeded(context)) {\n implicit.add(rule.package)\n }\n }\n return implicit\n}\n"],"mappings":"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","names":["fs","path","fs","path","fs","path","fs","path","fs","fs"]}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/actions/deplint/checkPackage/getUnusedPeerDependencies.ts
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
function getUnusedPeerDependencies({ name, location }, { peerDependencies, dependencies }, { externalDistImports, externalDistTypeImports }) {
|
|
3
|
+
function getUnusedPeerDependencies({ name, location }, { peerDependencies, dependencies }, { externalDistImports, externalDistTypeImports }, exclude) {
|
|
4
4
|
let unusedDependencies = 0;
|
|
5
5
|
for (const dep of peerDependencies) {
|
|
6
|
+
if (exclude?.has(dep)) continue;
|
|
6
7
|
if (!externalDistImports.includes(dep) && !externalDistImports.includes(dep.replace(/^@types\//, "")) && !externalDistTypeImports.includes(dep) && !externalDistTypeImports.includes(dep.replace(/^@types\//, ""))) {
|
|
7
8
|
unusedDependencies++;
|
|
8
9
|
if (dependencies.includes(dep)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/actions/deplint/checkPackage/getUnusedPeerDependencies.ts"],"sourcesContent":["import chalk from 'chalk'\n\nimport type { Workspace } from '../../../lib/index.ts'\nimport type { CheckPackageParams, CheckSourceParams } from './checkPackageTypes.ts'\n\nexport function getUnusedPeerDependencies(\n { name, location }: Workspace,\n { peerDependencies, dependencies }: CheckPackageParams,\n { externalDistImports, externalDistTypeImports }: CheckSourceParams,\n) {\n let unusedDependencies = 0\n for (const dep of peerDependencies) {\n if (!externalDistImports.includes(dep) && !externalDistImports.includes(dep.replace(/^@types\\//, ''))\n && !externalDistTypeImports.includes(dep) && !externalDistTypeImports.includes(dep.replace(/^@types\\//, ''))) {\n unusedDependencies++\n if (dependencies.includes(dep)) {\n console.log(`[${chalk.blue(name)}] Unused peerDependency [already a dependency] in package.json: ${chalk.red(dep)}`)\n } else {\n console.log(`[${chalk.blue(name)}] Unused peerDependency in package.json: ${chalk.red(dep)}`)\n }\n }\n }\n if (unusedDependencies > 0) {\n const packageLocation = `${location}/package.json`\n console.log(` ${chalk.yellow(packageLocation)}\\n`)\n }\n return unusedDependencies\n}\n"],"mappings":";AAAA,OAAO,WAAW;AAKX,SAAS,0BACd,EAAE,MAAM,SAAS,GACjB,EAAE,kBAAkB,aAAa,GACjC,EAAE,qBAAqB,wBAAwB,
|
|
1
|
+
{"version":3,"sources":["../../../../src/actions/deplint/checkPackage/getUnusedPeerDependencies.ts"],"sourcesContent":["import chalk from 'chalk'\n\nimport type { Workspace } from '../../../lib/index.ts'\nimport type { CheckPackageParams, CheckSourceParams } from './checkPackageTypes.ts'\n\nexport function getUnusedPeerDependencies(\n { name, location }: Workspace,\n { peerDependencies, dependencies }: CheckPackageParams,\n { externalDistImports, externalDistTypeImports }: CheckSourceParams,\n // Package names to skip (from xy.config deplint.exclude)\n exclude?: Set<string>,\n) {\n let unusedDependencies = 0\n for (const dep of peerDependencies) {\n if (exclude?.has(dep)) continue\n if (!externalDistImports.includes(dep) && !externalDistImports.includes(dep.replace(/^@types\\//, ''))\n && !externalDistTypeImports.includes(dep) && !externalDistTypeImports.includes(dep.replace(/^@types\\//, ''))) {\n unusedDependencies++\n if (dependencies.includes(dep)) {\n console.log(`[${chalk.blue(name)}] Unused peerDependency [already a dependency] in package.json: ${chalk.red(dep)}`)\n } else {\n console.log(`[${chalk.blue(name)}] Unused peerDependency in package.json: ${chalk.red(dep)}`)\n }\n }\n }\n if (unusedDependencies > 0) {\n const packageLocation = `${location}/package.json`\n console.log(` ${chalk.yellow(packageLocation)}\\n`)\n }\n return unusedDependencies\n}\n"],"mappings":";AAAA,OAAO,WAAW;AAKX,SAAS,0BACd,EAAE,MAAM,SAAS,GACjB,EAAE,kBAAkB,aAAa,GACjC,EAAE,qBAAqB,wBAAwB,GAE/C,SACA;AACA,MAAI,qBAAqB;AACzB,aAAW,OAAO,kBAAkB;AAClC,QAAI,SAAS,IAAI,GAAG,EAAG;AACvB,QAAI,CAAC,oBAAoB,SAAS,GAAG,KAAK,CAAC,oBAAoB,SAAS,IAAI,QAAQ,aAAa,EAAE,CAAC,KAC/F,CAAC,wBAAwB,SAAS,GAAG,KAAK,CAAC,wBAAwB,SAAS,IAAI,QAAQ,aAAa,EAAE,CAAC,GAAG;AAC9G;AACA,UAAI,aAAa,SAAS,GAAG,GAAG;AAC9B,gBAAQ,IAAI,IAAI,MAAM,KAAK,IAAI,CAAC,mEAAmE,MAAM,IAAI,GAAG,CAAC,EAAE;AAAA,MACrH,OAAO;AACL,gBAAQ,IAAI,IAAI,MAAM,KAAK,IAAI,CAAC,4CAA4C,MAAM,IAAI,GAAG,CAAC,EAAE;AAAA,MAC9F;AAAA,IACF;AAAA,EACF;AACA,MAAI,qBAAqB,GAAG;AAC1B,UAAM,kBAAkB,GAAG,QAAQ;AACnC,YAAQ,IAAI,KAAK,MAAM,OAAO,eAAe,CAAC;AAAA,CAAI;AAAA,EACpD;AACA,SAAO;AACT;","names":[]}
|
|
@@ -172,11 +172,11 @@ function getExternalImportsFromFiles({
|
|
|
172
172
|
const allImportPaths = {};
|
|
173
173
|
const distImportPaths = {};
|
|
174
174
|
const distTypeImportPaths = {};
|
|
175
|
-
for (const
|
|
175
|
+
for (const path6 of allFiles) getImportsFromFile(path6, allImportPaths, allImportPaths).flat();
|
|
176
176
|
const distTypeFiles = distFiles.filter(isDeclarationFile);
|
|
177
177
|
const distCodeFiles = distFiles.filter((file) => !isDeclarationFile(file));
|
|
178
|
-
for (const
|
|
179
|
-
for (const
|
|
178
|
+
for (const path6 of distCodeFiles) getImportsFromFile(path6, distImportPaths, distImportPaths).flat();
|
|
179
|
+
for (const path6 of distTypeFiles) getImportsFromFile(path6, distTypeImportPaths, distTypeImportPaths).flat();
|
|
180
180
|
const allImports = Object.keys(allImportPaths);
|
|
181
181
|
const distImports = Object.keys(distImportPaths);
|
|
182
182
|
const externalAllImports = removeInternalImports(allImports);
|
|
@@ -270,9 +270,10 @@ function getUnusedDependencies({ name, location }, { dependencies }, {
|
|
|
270
270
|
externalDistImports,
|
|
271
271
|
externalDistTypeImports,
|
|
272
272
|
externalAllImports
|
|
273
|
-
}) {
|
|
273
|
+
}, exclude) {
|
|
274
274
|
let unusedDependencies = 0;
|
|
275
275
|
for (const dep of dependencies) {
|
|
276
|
+
if (exclude?.has(dep)) continue;
|
|
276
277
|
if (!externalDistImports.includes(dep) && !externalDistImports.includes(dep.replace(/^@types\//, "")) && !externalDistTypeImports.includes(dep) && !externalDistTypeImports.includes(dep.replace(/^@types\//, ""))) {
|
|
277
278
|
unusedDependencies++;
|
|
278
279
|
if (externalAllImports.includes(dep)) {
|
|
@@ -293,6 +294,15 @@ function getUnusedDependencies({ name, location }, { dependencies }, {
|
|
|
293
294
|
// src/actions/deplint/checkPackage/getUnusedDevDependencies.ts
|
|
294
295
|
import chalk4 from "chalk";
|
|
295
296
|
|
|
297
|
+
// src/actions/deplint/getCliReferencedPackagesFromFiles.ts
|
|
298
|
+
import fs7 from "fs";
|
|
299
|
+
import path5 from "path";
|
|
300
|
+
import ts2 from "typescript";
|
|
301
|
+
|
|
302
|
+
// src/actions/deplint/getScriptReferencedPackages.ts
|
|
303
|
+
import fs6 from "fs";
|
|
304
|
+
import path4 from "path";
|
|
305
|
+
|
|
296
306
|
// src/actions/deplint/getRequiredPeerDependencies.ts
|
|
297
307
|
import fs5 from "fs";
|
|
298
308
|
import path3 from "path";
|
|
@@ -326,8 +336,6 @@ function getRequiredPeerDependencies(location, allDeps) {
|
|
|
326
336
|
}
|
|
327
337
|
|
|
328
338
|
// src/actions/deplint/getScriptReferencedPackages.ts
|
|
329
|
-
import fs6 from "fs";
|
|
330
|
-
import path4 from "path";
|
|
331
339
|
function getBinNames(location, dep) {
|
|
332
340
|
const depPkgPath = findDepPackageJson(location, dep);
|
|
333
341
|
if (!depPkgPath) return [];
|
|
@@ -377,15 +385,101 @@ function getScriptReferencedPackages(location, allDeps) {
|
|
|
377
385
|
return referenced;
|
|
378
386
|
}
|
|
379
387
|
|
|
388
|
+
// src/actions/deplint/getCliReferencedPackagesFromFiles.ts
|
|
389
|
+
var shellCommandFunctions = /* @__PURE__ */ new Set(["execSync", "exec"]);
|
|
390
|
+
var directExecFunctions = /* @__PURE__ */ new Set(["spawn", "spawnSync", "execFile", "execFileSync"]);
|
|
391
|
+
var allExecFunctions = /* @__PURE__ */ new Set([...shellCommandFunctions, ...directExecFunctions]);
|
|
392
|
+
function getCommandTokensFromFile(filePath) {
|
|
393
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
394
|
+
let sourceCode;
|
|
395
|
+
try {
|
|
396
|
+
sourceCode = fs7.readFileSync(filePath, "utf8");
|
|
397
|
+
} catch {
|
|
398
|
+
return tokens;
|
|
399
|
+
}
|
|
400
|
+
const isMjsFile = filePath.endsWith(".mjs");
|
|
401
|
+
const sourceFile = ts2.createSourceFile(
|
|
402
|
+
path5.basename(filePath),
|
|
403
|
+
sourceCode,
|
|
404
|
+
ts2.ScriptTarget.Latest,
|
|
405
|
+
true,
|
|
406
|
+
isMjsFile ? ts2.ScriptKind.JS : void 0
|
|
407
|
+
);
|
|
408
|
+
function visit(node) {
|
|
409
|
+
if (ts2.isCallExpression(node) && node.arguments.length > 0) {
|
|
410
|
+
const fnName = getFunctionName(node.expression);
|
|
411
|
+
if (fnName && allExecFunctions.has(fnName)) {
|
|
412
|
+
const firstArg = node.arguments[0];
|
|
413
|
+
if (ts2.isStringLiteral(firstArg) || ts2.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
414
|
+
const value = firstArg.text;
|
|
415
|
+
if (shellCommandFunctions.has(fnName)) {
|
|
416
|
+
for (const token of tokenizeScript(value)) {
|
|
417
|
+
tokens.add(token);
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
tokens.add(value);
|
|
421
|
+
}
|
|
422
|
+
} else if (ts2.isTemplateExpression(firstArg)) {
|
|
423
|
+
const head = firstArg.head.text;
|
|
424
|
+
if (head) {
|
|
425
|
+
for (const token of tokenizeScript(head)) {
|
|
426
|
+
tokens.add(token);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
ts2.forEachChild(node, visit);
|
|
433
|
+
}
|
|
434
|
+
visit(sourceFile);
|
|
435
|
+
return tokens;
|
|
436
|
+
}
|
|
437
|
+
function getFunctionName(expr) {
|
|
438
|
+
if (ts2.isIdentifier(expr)) {
|
|
439
|
+
return expr.text;
|
|
440
|
+
}
|
|
441
|
+
if (ts2.isPropertyAccessExpression(expr) && ts2.isIdentifier(expr.name)) {
|
|
442
|
+
return expr.name.text;
|
|
443
|
+
}
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
446
|
+
function getCliReferencedPackagesFromFiles(allFiles, location, allDeps) {
|
|
447
|
+
const allTokens = /* @__PURE__ */ new Set();
|
|
448
|
+
for (const file of allFiles) {
|
|
449
|
+
for (const token of getCommandTokensFromFile(file)) {
|
|
450
|
+
allTokens.add(token);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (allTokens.size === 0) return /* @__PURE__ */ new Set();
|
|
454
|
+
const binToPackage = /* @__PURE__ */ new Map();
|
|
455
|
+
for (const dep of allDeps) {
|
|
456
|
+
for (const bin of getBinNames(location, dep)) {
|
|
457
|
+
binToPackage.set(bin, dep);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
461
|
+
for (const token of allTokens) {
|
|
462
|
+
const baseName = getBasePackageName(token);
|
|
463
|
+
if (allDeps.includes(baseName)) {
|
|
464
|
+
referenced.add(baseName);
|
|
465
|
+
}
|
|
466
|
+
const pkg = binToPackage.get(token);
|
|
467
|
+
if (pkg) {
|
|
468
|
+
referenced.add(pkg);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return referenced;
|
|
472
|
+
}
|
|
473
|
+
|
|
380
474
|
// src/actions/deplint/implicitDevDependencies.ts
|
|
381
|
-
import
|
|
475
|
+
import fs8 from "fs";
|
|
382
476
|
var hasFileWithExtension = (files, extensions) => files.some((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
383
477
|
var tsExtensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
384
478
|
var hasTypescriptFiles = ({ allFiles }) => hasFileWithExtension(allFiles, tsExtensions);
|
|
385
479
|
var decoratorPattern = /^\s*@[a-zA-Z]\w*/m;
|
|
386
480
|
var hasDecorators = ({ allFiles }) => allFiles.filter((f) => tsExtensions.some((ext) => f.endsWith(ext))).some((file) => {
|
|
387
481
|
try {
|
|
388
|
-
const content =
|
|
482
|
+
const content = fs8.readFileSync(file, "utf8");
|
|
389
483
|
return decoratorPattern.test(content);
|
|
390
484
|
} catch {
|
|
391
485
|
return false;
|
|
@@ -398,7 +492,7 @@ function hasImportPlugin({ location, allDependencies }) {
|
|
|
398
492
|
const pkgPath = findDepPackageJson(location, dep);
|
|
399
493
|
if (!pkgPath) continue;
|
|
400
494
|
try {
|
|
401
|
-
const pkg = JSON.parse(
|
|
495
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf8"));
|
|
402
496
|
const transitiveDeps = [
|
|
403
497
|
...Object.keys(pkg.dependencies ?? {}),
|
|
404
498
|
...Object.keys(pkg.peerDependencies ?? {})
|
|
@@ -450,10 +544,11 @@ var allExternalImports = ({
|
|
|
450
544
|
...externalDistTypeImports
|
|
451
545
|
]);
|
|
452
546
|
};
|
|
453
|
-
function isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs) {
|
|
547
|
+
function isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs, cliRefs) {
|
|
454
548
|
if (implicitDeps.has(dep)) return true;
|
|
455
549
|
if (requiredPeers.has(dep)) return true;
|
|
456
550
|
if (scriptRefs.has(dep)) return true;
|
|
551
|
+
if (cliRefs.has(dep)) return true;
|
|
457
552
|
if (dep.startsWith("@types/")) {
|
|
458
553
|
const baseName = dep.replace(/^@types\//, "");
|
|
459
554
|
return allImports.has(baseName) || allImports.has(dep) || implicitDeps.has(baseName);
|
|
@@ -464,7 +559,7 @@ function getUnusedDevDependencies({ name, location }, {
|
|
|
464
559
|
devDependencies,
|
|
465
560
|
dependencies,
|
|
466
561
|
peerDependencies
|
|
467
|
-
}, sourceParams, fileContext) {
|
|
562
|
+
}, sourceParams, fileContext, exclude) {
|
|
468
563
|
const allImports = allExternalImports(sourceParams);
|
|
469
564
|
const allDeps = [...dependencies, ...devDependencies, ...peerDependencies];
|
|
470
565
|
const implicitDeps = getImplicitDevDependencies({
|
|
@@ -474,10 +569,12 @@ function getUnusedDevDependencies({ name, location }, {
|
|
|
474
569
|
});
|
|
475
570
|
const requiredPeers = getRequiredPeerDependencies(location, allDeps);
|
|
476
571
|
const scriptRefs = getScriptReferencedPackages(location, allDeps);
|
|
572
|
+
const cliRefs = getCliReferencedPackagesFromFiles(fileContext.allFiles, location, allDeps);
|
|
477
573
|
let unusedDevDependencies = 0;
|
|
478
574
|
for (const dep of devDependencies) {
|
|
575
|
+
if (exclude?.has(dep)) continue;
|
|
479
576
|
if (dependencies.includes(dep) || peerDependencies.includes(dep)) continue;
|
|
480
|
-
if (!isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs)) {
|
|
577
|
+
if (!isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs, cliRefs)) {
|
|
481
578
|
unusedDevDependencies++;
|
|
482
579
|
console.log(`[${chalk4.blue(name)}] Unused devDependency in package.json: ${chalk4.red(dep)}`);
|
|
483
580
|
}
|
|
@@ -492,9 +589,10 @@ function getUnusedDevDependencies({ name, location }, {
|
|
|
492
589
|
|
|
493
590
|
// src/actions/deplint/checkPackage/getUnusedPeerDependencies.ts
|
|
494
591
|
import chalk5 from "chalk";
|
|
495
|
-
function getUnusedPeerDependencies({ name, location }, { peerDependencies, dependencies }, { externalDistImports, externalDistTypeImports }) {
|
|
592
|
+
function getUnusedPeerDependencies({ name, location }, { peerDependencies, dependencies }, { externalDistImports, externalDistTypeImports }, exclude) {
|
|
496
593
|
let unusedDependencies = 0;
|
|
497
594
|
for (const dep of peerDependencies) {
|
|
595
|
+
if (exclude?.has(dep)) continue;
|
|
498
596
|
if (!externalDistImports.includes(dep) && !externalDistImports.includes(dep.replace(/^@types\//, "")) && !externalDistTypeImports.includes(dep) && !externalDistTypeImports.includes(dep.replace(/^@types\//, ""))) {
|
|
499
597
|
unusedDependencies++;
|
|
500
598
|
if (dependencies.includes(dep)) {
|
|
@@ -531,6 +629,7 @@ function checkPackage({
|
|
|
531
629
|
location,
|
|
532
630
|
deps = false,
|
|
533
631
|
devDeps = false,
|
|
632
|
+
exclude,
|
|
534
633
|
peerDeps = false,
|
|
535
634
|
verbose = false
|
|
536
635
|
}) {
|
|
@@ -549,11 +648,11 @@ function checkPackage({
|
|
|
549
648
|
});
|
|
550
649
|
const packageParams = getDependenciesFromPackageJson(`${location}/package.json`);
|
|
551
650
|
const unlistedDependencies = checkDeps ? getUnlistedDependencies({ name, location }, packageParams, sourceParams) : 0;
|
|
552
|
-
const unusedDependencies = checkDeps ? getUnusedDependencies({ name, location }, packageParams, sourceParams) : 0;
|
|
651
|
+
const unusedDependencies = checkDeps ? getUnusedDependencies({ name, location }, packageParams, sourceParams, exclude) : 0;
|
|
553
652
|
const unlistedDevDependencies = checkDevDeps ? getUnlistedDevDependencies({ name, location }, packageParams, sourceParams) : 0;
|
|
554
653
|
const fileContext = { allFiles, distFiles };
|
|
555
|
-
const unusedDevDependencies = checkDevDeps ? getUnusedDevDependencies({ name, location }, packageParams, sourceParams, fileContext) : 0;
|
|
556
|
-
const unusedPeerDependencies = checkPeerDeps ? getUnusedPeerDependencies({ name, location }, packageParams, sourceParams) : 0;
|
|
654
|
+
const unusedDevDependencies = checkDevDeps ? getUnusedDevDependencies({ name, location }, packageParams, sourceParams, fileContext, exclude) : 0;
|
|
655
|
+
const unusedPeerDependencies = checkPeerDeps ? getUnusedPeerDependencies({ name, location }, packageParams, sourceParams, exclude) : 0;
|
|
557
656
|
const totalErrors = unlistedDependencies + unlistedDevDependencies + unusedDependencies + unusedDevDependencies + unusedPeerDependencies;
|
|
558
657
|
return totalErrors;
|
|
559
658
|
}
|