@tanstack/devtools-vite 0.3.5 → 0.3.7
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/esm/inject-plugin.d.ts +21 -0
- package/dist/esm/inject-plugin.js +228 -0
- package/dist/esm/inject-plugin.js.map +1 -0
- package/dist/esm/inject-plugin.test.d.ts +1 -0
- package/dist/esm/package-manager.d.ts +16 -0
- package/dist/esm/package-manager.js +139 -0
- package/dist/esm/package-manager.js.map +1 -0
- package/dist/esm/plugin.d.ts +7 -1
- package/dist/esm/plugin.js +186 -2
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +24 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -1
- package/src/inject-plugin.test.ts +1086 -0
- package/src/inject-plugin.ts +343 -0
- package/src/package-manager.ts +181 -0
- package/src/plugin.ts +242 -3
- package/src/utils.ts +14 -8
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { gen, parse, t, trav } from './babel'
|
|
3
|
+
import type { PluginInjection } from '@tanstack/devtools-client'
|
|
4
|
+
import type { types as Babel } from '@babel/core'
|
|
5
|
+
import type { ParseResult } from '@babel/parser'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Detects if a file imports TanStack devtools packages
|
|
9
|
+
* Handles: import X from '@tanstack/react-devtools'
|
|
10
|
+
* import * as X from '@tanstack/react-devtools'
|
|
11
|
+
* import { TanStackDevtools } from '@tanstack/react-devtools'
|
|
12
|
+
*/
|
|
13
|
+
const detectDevtoolsImport = (code: string): boolean => {
|
|
14
|
+
const devtoolsPackages = [
|
|
15
|
+
'@tanstack/react-devtools',
|
|
16
|
+
'@tanstack/solid-devtools',
|
|
17
|
+
'@tanstack/vue-devtools',
|
|
18
|
+
'@tanstack/svelte-devtools',
|
|
19
|
+
'@tanstack/angular-devtools',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const ast = parse(code, {
|
|
24
|
+
sourceType: 'module',
|
|
25
|
+
plugins: ['jsx', 'typescript'],
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
let hasDevtoolsImport = false
|
|
29
|
+
|
|
30
|
+
trav(ast, {
|
|
31
|
+
ImportDeclaration(path) {
|
|
32
|
+
const importSource = path.node.source.value
|
|
33
|
+
if (devtoolsPackages.includes(importSource)) {
|
|
34
|
+
hasDevtoolsImport = true
|
|
35
|
+
path.stop()
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return hasDevtoolsImport
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Finds the TanStackDevtools component name in the file
|
|
48
|
+
* Handles renamed imports and namespace imports
|
|
49
|
+
*/
|
|
50
|
+
export const findDevtoolsComponentName = (
|
|
51
|
+
ast: ParseResult<Babel.File>,
|
|
52
|
+
): string | null => {
|
|
53
|
+
let componentName: string | null = null
|
|
54
|
+
const devtoolsPackages = [
|
|
55
|
+
'@tanstack/react-devtools',
|
|
56
|
+
'@tanstack/solid-devtools',
|
|
57
|
+
'@tanstack/vue-devtools',
|
|
58
|
+
'@tanstack/svelte-devtools',
|
|
59
|
+
'@tanstack/angular-devtools',
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
trav(ast, {
|
|
63
|
+
ImportDeclaration(path) {
|
|
64
|
+
const importSource = path.node.source.value
|
|
65
|
+
if (devtoolsPackages.includes(importSource)) {
|
|
66
|
+
// Check for: import { TanStackDevtools } from '@tanstack/...'
|
|
67
|
+
const namedImport = path.node.specifiers.find(
|
|
68
|
+
(spec) =>
|
|
69
|
+
t.isImportSpecifier(spec) &&
|
|
70
|
+
t.isIdentifier(spec.imported) &&
|
|
71
|
+
spec.imported.name === 'TanStackDevtools',
|
|
72
|
+
)
|
|
73
|
+
if (namedImport && t.isImportSpecifier(namedImport)) {
|
|
74
|
+
componentName = namedImport.local.name
|
|
75
|
+
path.stop()
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check for: import * as DevtoolsName from '@tanstack/...'
|
|
80
|
+
const namespaceImport = path.node.specifiers.find((spec) =>
|
|
81
|
+
t.isImportNamespaceSpecifier(spec),
|
|
82
|
+
)
|
|
83
|
+
if (namespaceImport && t.isImportNamespaceSpecifier(namespaceImport)) {
|
|
84
|
+
// For namespace imports, we need to look for DevtoolsName.TanStackDevtools
|
|
85
|
+
componentName = `${namespaceImport.local.name}.TanStackDevtools`
|
|
86
|
+
path.stop()
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return componentName
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const transformAndInject = (
|
|
97
|
+
ast: ParseResult<Babel.File>,
|
|
98
|
+
injection: PluginInjection,
|
|
99
|
+
devtoolsComponentName: string,
|
|
100
|
+
) => {
|
|
101
|
+
let didTransform = false
|
|
102
|
+
|
|
103
|
+
// Use pluginImport if provided, otherwise generate from package name
|
|
104
|
+
const importName = injection.pluginImport?.importName
|
|
105
|
+
const pluginType = injection.pluginImport?.type || 'jsx'
|
|
106
|
+
const displayName = injection.pluginName
|
|
107
|
+
|
|
108
|
+
if (!importName) {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
// Handle namespace imports like DevtoolsModule.TanStackDevtools
|
|
112
|
+
const isNamespaceImport = devtoolsComponentName.includes('.')
|
|
113
|
+
|
|
114
|
+
// Find and modify the TanStackDevtools JSX element
|
|
115
|
+
trav(ast, {
|
|
116
|
+
JSXOpeningElement(path) {
|
|
117
|
+
const elementName = path.node.name
|
|
118
|
+
let matches = false
|
|
119
|
+
|
|
120
|
+
if (isNamespaceImport) {
|
|
121
|
+
// Handle <DevtoolsModule.TanStackDevtools />
|
|
122
|
+
if (t.isJSXMemberExpression(elementName)) {
|
|
123
|
+
const fullName = `${t.isJSXIdentifier(elementName.object) ? elementName.object.name : ''}.${t.isJSXIdentifier(elementName.property) ? elementName.property.name : ''}`
|
|
124
|
+
matches = fullName === devtoolsComponentName
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
// Handle <TanStackDevtools /> or <RenamedDevtools />
|
|
128
|
+
matches =
|
|
129
|
+
t.isJSXIdentifier(elementName) &&
|
|
130
|
+
elementName.name === devtoolsComponentName
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (matches) {
|
|
134
|
+
// Find the plugins prop
|
|
135
|
+
const pluginsProp = path.node.attributes.find(
|
|
136
|
+
(attr) =>
|
|
137
|
+
t.isJSXAttribute(attr) &&
|
|
138
|
+
t.isJSXIdentifier(attr.name) &&
|
|
139
|
+
attr.name.name === 'plugins',
|
|
140
|
+
)
|
|
141
|
+
// plugins found
|
|
142
|
+
if (pluginsProp && t.isJSXAttribute(pluginsProp)) {
|
|
143
|
+
// Check if plugins prop has a value
|
|
144
|
+
if (
|
|
145
|
+
pluginsProp.value &&
|
|
146
|
+
t.isJSXExpressionContainer(pluginsProp.value)
|
|
147
|
+
) {
|
|
148
|
+
const expression = pluginsProp.value.expression
|
|
149
|
+
|
|
150
|
+
// If it's an array expression, add our plugin to it
|
|
151
|
+
if (t.isArrayExpression(expression)) {
|
|
152
|
+
// Check if plugin already exists
|
|
153
|
+
const pluginExists = expression.elements.some((element) => {
|
|
154
|
+
if (!element) return false
|
|
155
|
+
|
|
156
|
+
// For function-based plugins, check if the function call exists
|
|
157
|
+
if (pluginType === 'function') {
|
|
158
|
+
return (
|
|
159
|
+
t.isCallExpression(element) &&
|
|
160
|
+
t.isIdentifier(element.callee) &&
|
|
161
|
+
element.callee.name === importName
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// For JSX plugins, check object with name property
|
|
166
|
+
if (!t.isObjectExpression(element)) return false
|
|
167
|
+
|
|
168
|
+
return element.properties.some((prop) => {
|
|
169
|
+
if (
|
|
170
|
+
!t.isObjectProperty(prop) ||
|
|
171
|
+
!t.isIdentifier(prop.key) ||
|
|
172
|
+
prop.key.name !== 'name'
|
|
173
|
+
) {
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
t.isStringLiteral(prop.value) &&
|
|
179
|
+
prop.value.value === displayName
|
|
180
|
+
)
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
if (!pluginExists) {
|
|
185
|
+
// For function-based plugins, add them directly as function calls
|
|
186
|
+
// For JSX plugins, wrap them in objects with name and render
|
|
187
|
+
if (pluginType === 'function') {
|
|
188
|
+
// Add directly: FormDevtoolsPlugin()
|
|
189
|
+
expression.elements.push(
|
|
190
|
+
t.callExpression(t.identifier(importName), []),
|
|
191
|
+
)
|
|
192
|
+
} else {
|
|
193
|
+
// Add as object: { name: "...", render: <Component /> }
|
|
194
|
+
const renderValue = t.jsxElement(
|
|
195
|
+
t.jsxOpeningElement(t.jsxIdentifier(importName), [], true),
|
|
196
|
+
null,
|
|
197
|
+
[],
|
|
198
|
+
true,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
expression.elements.push(
|
|
202
|
+
t.objectExpression([
|
|
203
|
+
t.objectProperty(
|
|
204
|
+
t.identifier('name'),
|
|
205
|
+
t.stringLiteral(displayName),
|
|
206
|
+
),
|
|
207
|
+
t.objectProperty(t.identifier('render'), renderValue),
|
|
208
|
+
]),
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
didTransform = true
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
// No plugins prop exists, create one with our plugin
|
|
218
|
+
// For function-based plugins, add them directly as function calls
|
|
219
|
+
// For JSX plugins, wrap them in objects with name and render
|
|
220
|
+
let pluginElement
|
|
221
|
+
if (pluginType === 'function') {
|
|
222
|
+
// Add directly: plugins={[FormDevtoolsPlugin()]}
|
|
223
|
+
pluginElement = t.callExpression(t.identifier(importName), [])
|
|
224
|
+
} else {
|
|
225
|
+
// Add as object: plugins={[{ name: "...", render: <Component /> }]}
|
|
226
|
+
const renderValue = t.jsxElement(
|
|
227
|
+
t.jsxOpeningElement(t.jsxIdentifier(importName), [], true),
|
|
228
|
+
null,
|
|
229
|
+
[],
|
|
230
|
+
true,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
pluginElement = t.objectExpression([
|
|
234
|
+
t.objectProperty(
|
|
235
|
+
t.identifier('name'),
|
|
236
|
+
t.stringLiteral(displayName),
|
|
237
|
+
),
|
|
238
|
+
t.objectProperty(t.identifier('render'), renderValue),
|
|
239
|
+
])
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
path.node.attributes.push(
|
|
243
|
+
t.jsxAttribute(
|
|
244
|
+
t.jsxIdentifier('plugins'),
|
|
245
|
+
t.jsxExpressionContainer(t.arrayExpression([pluginElement])),
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
didTransform = true
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// Add import at the top of the file if transform happened
|
|
256
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
257
|
+
if (didTransform) {
|
|
258
|
+
const importDeclaration = t.importDeclaration(
|
|
259
|
+
[t.importSpecifier(t.identifier(importName), t.identifier(importName))],
|
|
260
|
+
t.stringLiteral(injection.packageName),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
// Find the last import declaration
|
|
264
|
+
let lastImportIndex = -1
|
|
265
|
+
ast.program.body.forEach((node, index) => {
|
|
266
|
+
if (t.isImportDeclaration(node)) {
|
|
267
|
+
lastImportIndex = index
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
// Insert after the last import or at the beginning
|
|
272
|
+
ast.program.body.splice(lastImportIndex + 1, 0, importDeclaration)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return didTransform
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Detects if a file contains TanStack devtools import
|
|
280
|
+
*/
|
|
281
|
+
export function detectDevtoolsFile(code: string): boolean {
|
|
282
|
+
return detectDevtoolsImport(code)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Injects a plugin into the TanStackDevtools component in a file
|
|
287
|
+
* Reads the file, transforms it, and writes it back
|
|
288
|
+
*/
|
|
289
|
+
export function injectPluginIntoFile(
|
|
290
|
+
filePath: string,
|
|
291
|
+
injection: PluginInjection,
|
|
292
|
+
): { success: boolean; error?: string } {
|
|
293
|
+
try {
|
|
294
|
+
// Read the file
|
|
295
|
+
const code = readFileSync(filePath, 'utf-8')
|
|
296
|
+
|
|
297
|
+
// Parse the code
|
|
298
|
+
const ast = parse(code, {
|
|
299
|
+
sourceType: 'module',
|
|
300
|
+
plugins: ['jsx', 'typescript'],
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
// Find the devtools component name (handles renamed imports)
|
|
304
|
+
const devtoolsComponentName = findDevtoolsComponentName(ast)
|
|
305
|
+
if (!devtoolsComponentName) {
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
error: 'Could not find TanStackDevtools import',
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Transform and inject
|
|
313
|
+
const didTransform = transformAndInject(
|
|
314
|
+
ast,
|
|
315
|
+
injection,
|
|
316
|
+
devtoolsComponentName,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if (!didTransform) {
|
|
320
|
+
return {
|
|
321
|
+
success: false,
|
|
322
|
+
error: 'Plugin already exists or no TanStackDevtools component found',
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Generate the new code
|
|
327
|
+
const result = gen(ast, {
|
|
328
|
+
sourceMaps: false,
|
|
329
|
+
retainLines: false,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
// Write back to file
|
|
333
|
+
writeFileSync(filePath, result.code, 'utf-8')
|
|
334
|
+
|
|
335
|
+
return { success: true }
|
|
336
|
+
} catch (e) {
|
|
337
|
+
console.error('Error injecting plugin:', e)
|
|
338
|
+
return {
|
|
339
|
+
success: false,
|
|
340
|
+
error: e instanceof Error ? e.message : 'Unknown error',
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { exec } from 'node:child_process'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { devtoolsEventClient } from '@tanstack/devtools-client'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
import { readPackageJson, tryParseJson } from './utils'
|
|
7
|
+
import { injectPluginIntoFile } from './inject-plugin'
|
|
8
|
+
import type { OutdatedDeps } from '@tanstack/devtools-client'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Gets the outdated command for the detected package manager
|
|
12
|
+
*/
|
|
13
|
+
const getOutdatedCommand = (packageManager: string): string => {
|
|
14
|
+
switch (packageManager) {
|
|
15
|
+
case 'yarn':
|
|
16
|
+
return 'yarn outdated --json'
|
|
17
|
+
case 'pnpm':
|
|
18
|
+
return 'pnpm outdated --format json'
|
|
19
|
+
case 'bun':
|
|
20
|
+
return 'bun outdated --json'
|
|
21
|
+
case 'npm':
|
|
22
|
+
default:
|
|
23
|
+
return 'npm outdated --json'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Adds a plugin to the devtools configuration file
|
|
29
|
+
*/
|
|
30
|
+
export const addPluginToDevtools = (
|
|
31
|
+
devtoolsFileId: string | null,
|
|
32
|
+
packageName: string,
|
|
33
|
+
pluginName: string,
|
|
34
|
+
pluginImport?: { importName: string; type: 'jsx' | 'function' },
|
|
35
|
+
): { success: boolean; error?: string } => {
|
|
36
|
+
// Check if we found the devtools file
|
|
37
|
+
if (!devtoolsFileId) {
|
|
38
|
+
const error = 'Devtools file not found'
|
|
39
|
+
console.log(
|
|
40
|
+
chalk.yellowBright(
|
|
41
|
+
`[@tanstack/devtools-vite] Could not add plugin. ${error}.`,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
return { success: false, error }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Inject the plugin into the file
|
|
48
|
+
const result = injectPluginIntoFile(devtoolsFileId, {
|
|
49
|
+
packageName,
|
|
50
|
+
pluginName,
|
|
51
|
+
pluginImport,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
if (result.success) {
|
|
55
|
+
console.log(
|
|
56
|
+
chalk.greenBright(
|
|
57
|
+
`[@tanstack/devtools-vite] Successfully added ${packageName} to devtools!`,
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
} else {
|
|
61
|
+
console.log(
|
|
62
|
+
chalk.yellowBright(
|
|
63
|
+
`[@tanstack/devtools-vite] Could not add plugin: ${result.error}`,
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Gets the install command for the detected package manager
|
|
72
|
+
*/
|
|
73
|
+
const getInstallCommand = (
|
|
74
|
+
packageManager: string,
|
|
75
|
+
packageName: string,
|
|
76
|
+
): string => {
|
|
77
|
+
switch (packageManager) {
|
|
78
|
+
case 'yarn':
|
|
79
|
+
return `yarn add -D ${packageName}`
|
|
80
|
+
case 'pnpm':
|
|
81
|
+
return `pnpm add -D ${packageName}`
|
|
82
|
+
case 'bun':
|
|
83
|
+
return `bun add -D ${packageName}`
|
|
84
|
+
case 'npm':
|
|
85
|
+
default:
|
|
86
|
+
return `npm install -D ${packageName}`
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const installPackage = async (
|
|
91
|
+
packageName: string,
|
|
92
|
+
): Promise<{
|
|
93
|
+
success: boolean
|
|
94
|
+
error?: string
|
|
95
|
+
}> => {
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
const packageManager = detectPackageManager()
|
|
98
|
+
const installCommand = getInstallCommand(packageManager, packageName)
|
|
99
|
+
|
|
100
|
+
console.log(
|
|
101
|
+
chalk.blueBright(
|
|
102
|
+
`[@tanstack/devtools-vite] Installing ${packageName}...`,
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
exec(installCommand, async (installError) => {
|
|
107
|
+
if (installError) {
|
|
108
|
+
console.error(
|
|
109
|
+
chalk.redBright(
|
|
110
|
+
`[@tanstack/devtools-vite] Failed to install ${packageName}:`,
|
|
111
|
+
),
|
|
112
|
+
installError.message,
|
|
113
|
+
)
|
|
114
|
+
resolve({
|
|
115
|
+
success: false,
|
|
116
|
+
error: installError.message,
|
|
117
|
+
})
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(
|
|
122
|
+
chalk.greenBright(
|
|
123
|
+
`[@tanstack/devtools-vite] Successfully installed ${packageName}`,
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
// Read the updated package.json and emit the event
|
|
128
|
+
const updatedPackageJson = await readPackageJson()
|
|
129
|
+
devtoolsEventClient.emit('package-json-updated', {
|
|
130
|
+
packageJson: updatedPackageJson,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
resolve({ success: true })
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Detects the package manager used in the project by checking for lock files
|
|
140
|
+
*/
|
|
141
|
+
const detectPackageManager = (): 'npm' | 'yarn' | 'pnpm' | 'bun' => {
|
|
142
|
+
const cwd = process.cwd()
|
|
143
|
+
|
|
144
|
+
// Check for lock files in order of specificity
|
|
145
|
+
if (existsSync(join(cwd, 'bun.lockb')) || existsSync(join(cwd, 'bun.lock'))) {
|
|
146
|
+
return 'bun'
|
|
147
|
+
}
|
|
148
|
+
if (existsSync(join(cwd, 'pnpm-lock.yaml'))) {
|
|
149
|
+
return 'pnpm'
|
|
150
|
+
}
|
|
151
|
+
if (existsSync(join(cwd, 'yarn.lock'))) {
|
|
152
|
+
return 'yarn'
|
|
153
|
+
}
|
|
154
|
+
if (existsSync(join(cwd, 'package-lock.json'))) {
|
|
155
|
+
return 'npm'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Default to pnpm if no lock file is found
|
|
159
|
+
return 'pnpm'
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const emitOutdatedDeps = async () => {
|
|
163
|
+
return await new Promise<OutdatedDeps | null>((resolve) => {
|
|
164
|
+
const packageManager = detectPackageManager()
|
|
165
|
+
const outdatedCommand = getOutdatedCommand(packageManager)
|
|
166
|
+
|
|
167
|
+
exec(outdatedCommand, (_, stdout) => {
|
|
168
|
+
// outdated commands exit with code 1 if there are outdated packages, but still output valid JSON
|
|
169
|
+
if (stdout) {
|
|
170
|
+
const newOutdatedDeps = tryParseJson<OutdatedDeps>(stdout)
|
|
171
|
+
if (!newOutdatedDeps) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
devtoolsEventClient.emit('outdated-deps-read', {
|
|
175
|
+
outdatedDeps: newOutdatedDeps,
|
|
176
|
+
})
|
|
177
|
+
resolve(newOutdatedDeps)
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
}
|