-
-
Notifications
You must be signed in to change notification settings - Fork 425
Expand file tree
/
Copy pathdev-dependency.ts
More file actions
108 lines (95 loc) · 2.65 KB
/
dev-dependency.ts
File metadata and controls
108 lines (95 loc) · 2.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { regExpEscape } from '@li/regexp-escape-polyfill'
export type DevDependencySuggestionReason = 'known-package' | 'readme-hint'
export interface DevDependencySuggestion {
recommended: boolean
reason?: DevDependencySuggestionReason
}
const KNOWN_DEV_DEPENDENCY_PACKAGES = new Set<string>([
'biome',
'chai',
'eslint',
'esbuild',
'husky',
'jest',
'lint-staged',
'mocha',
'oxc',
'oxfmt',
'oxlint',
'playwright',
'prettier',
'rolldown',
'rollup',
'stylelint',
'ts-jest',
'ts-node',
'tsx',
'turbo',
'typescript',
'vite',
'vitest',
'webpack',
])
const KNOWN_DEV_DEPENDENCY_PACKAGE_PREFIXES = [
'@typescript-eslint/',
'eslint-',
'prettier-',
'vite-',
'webpack-',
'babel-',
]
function isKnownDevDependencyPackage(packageName: string): boolean {
const normalized = packageName.toLowerCase()
if (normalized.startsWith('@types/')) {
return true
}
// Match scoped packages by name segment, e.g. @scope/eslint-config
const namePart = normalized.includes('/') ? normalized.split('/').pop() : normalized
if (!namePart) return false
return (
KNOWN_DEV_DEPENDENCY_PACKAGES.has(normalized) ||
KNOWN_DEV_DEPENDENCY_PACKAGES.has(namePart) ||
KNOWN_DEV_DEPENDENCY_PACKAGE_PREFIXES.some(prefix =>
prefix.startsWith('@') ? normalized.startsWith(prefix) : namePart.startsWith(prefix),
)
)
}
function hasReadmeDevInstallHint(packageName: string, readmeContent?: string | null): boolean {
if (!readmeContent) return false
const escapedName = regExpEscape(packageName)
const escapedNpmName = regExpEscape(`npm:${packageName}`)
const packageSpec = `(?:${escapedName}|${escapedNpmName})(?:@[\\w.-]+)?`
const patterns = [
// npm install -D pkg / pnpm add --save-dev pkg
new RegExp(
String.raw`(?:npm|pnpm|yarn|bun|vlt)\s+(?:install|add|i)\s+(?:--save-dev|--dev|-d)\s+${packageSpec}`,
'i',
),
// npm install pkg --save-dev / pnpm add pkg -D
new RegExp(
String.raw`(?:npm|pnpm|yarn|bun|vlt)\s+(?:install|add|i)\s+${packageSpec}\s+(?:--save-dev|--dev|-d)`,
'i',
),
// deno add -D npm:pkg
new RegExp(String.raw`deno\s+add\s+(?:--dev|-D)\s+${packageSpec}`, 'i'),
]
return patterns.some(pattern => pattern.test(readmeContent))
}
export function getDevDependencySuggestion(
packageName: string,
readmeContent?: string | null,
): DevDependencySuggestion {
if (isKnownDevDependencyPackage(packageName)) {
return {
recommended: true,
reason: 'known-package',
}
}
if (hasReadmeDevInstallHint(packageName, readmeContent)) {
return {
recommended: true,
reason: 'readme-hint',
}
}
return { recommended: false }
}