Skip to content

Commit 87c4e46

Browse files
committed
better bun support
1 parent 613583e commit 87c4e46

8 files changed

Lines changed: 152 additions & 28 deletions

File tree

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ xpm update # Updates lockfile
120120
### TODO
121121
- [ ] **pnpm patch support** - Support for pnpm patch files and patch packages
122122
- [ ] **Auto-install package managers** - Prompt user to install missing package managers (for all supported package managers)
123-
- [ ] **Exclude bun built-in commands** - Exclude `bun test`, `bun build`, and `bun update` as these are Bun runtime commands, not package manager commands
124123

125124
## License
126125

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@assistant-ui/xpm",
3-
"version": "0.0.9",
3+
"version": "0.1.0",
44
"description": "Universal package manager wrapper that automatically detects and uses the right package manager",
55
"main": "dist/index.js",
66
"bin": {

src/command-mapper.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ export function mapCommand(
1919
): { command: string; args: string[] } {
2020
const normalized = normalizeCommand(command);
2121

22-
// Handle dev dependencies
23-
const hasDevFlag = args.some(arg => ['-D', '--save-dev', '--dev'].includes(arg));
24-
const filteredArgs = args.filter(arg => !['-D', '--save-dev', '--dev'].includes(arg));
22+
// Only handle dev dependencies for install-like commands
23+
const isInstallCommand = normalized === 'install';
24+
const hasDevFlag = isInstallCommand && args.some(arg => ['-D', '--save-dev', '--dev'].includes(arg));
25+
const filteredArgs = isInstallCommand
26+
? args.filter(arg => !['-D', '--save-dev', '--dev'].includes(arg))
27+
: args; // Keep all args for non-install commands
2528

2629
// Use the package manager's mapCommand method for specific mappings
2730
const mapped = packageManager.mapCommand(normalized, filteredArgs, {

src/package-manager-registry.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
NpmPackageManager,
44
YarnPackageManager,
55
PnpmPackageManager,
6-
BunPackageManager
6+
BunPackageManager,
7+
DenoPackageManager
78
} from './package-managers/javascript';
89
import {
910
PipPackageManager,
@@ -17,7 +18,7 @@ import * as path from 'path';
1718

1819
export type Ecosystem = 'javascript' | 'python' | 'rust' | 'ruby' | 'php';
1920
export type PackageManagerName =
20-
| 'npm' | 'yarn' | 'pnpm' | 'bun'
21+
| 'npm' | 'yarn' | 'pnpm' | 'bun' | 'deno'
2122
| 'pip' | 'pipenv' | 'poetry' | 'uv' | 'conda';
2223

2324
export class PackageManagerRegistry {
@@ -34,6 +35,7 @@ export class PackageManagerRegistry {
3435
this.register(new YarnPackageManager());
3536
this.register(new PnpmPackageManager());
3637
this.register(new BunPackageManager());
38+
this.register(new DenoPackageManager());
3739

3840
// Python package managers
3941
this.register(new PipPackageManager());
@@ -106,7 +108,9 @@ export class PackageManagerRegistry {
106108
detectEcosystem(dir: string): Ecosystem | undefined {
107109
// JavaScript ecosystem detection
108110
if (fs.existsSync(path.join(dir, 'package.json')) ||
109-
fs.existsSync(path.join(dir, 'node_modules'))) {
111+
fs.existsSync(path.join(dir, 'node_modules')) ||
112+
fs.existsSync(path.join(dir, 'deno.json')) ||
113+
fs.existsSync(path.join(dir, 'deno.jsonc'))) {
110114
return 'javascript';
111115
}
112116

src/package-managers/javascript.ts

Lines changed: 114 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ export class NpmPackageManager extends BasePackageManager {
5151
return { command: 'run', args: [command, ...args] };
5252
}
5353

54+
// Handle dev dependencies for install command
55+
if (command === 'install' && options?.dev) {
56+
return { command: 'install', args: ['--save-dev', ...args] };
57+
}
58+
5459
return { command, args };
5560
}
5661

@@ -89,8 +94,17 @@ export class YarnPackageManager extends BasePackageManager {
8994
});
9095
}
9196

92-
mapCommand(command: string, args: string[]): { command: string; args: string[] } {
97+
mapCommand(command: string, args: string[], options?: any): { command: string; args: string[] } {
98+
// Handle script commands
99+
if (options?.hasScript && !['install', 'add', 'remove', 'uninstall', 'update', 'upgrade'].includes(command)) {
100+
return { command, args };
101+
}
102+
93103
if (command === 'install' && args.length > 0) {
104+
// Handle dev dependencies
105+
if (options?.dev) {
106+
return { command: 'add', args: ['--dev', ...args] };
107+
}
94108
return { command: 'add', args };
95109
}
96110
if (command === 'uninstall') {
@@ -138,8 +152,17 @@ export class PnpmPackageManager extends BasePackageManager {
138152
});
139153
}
140154

141-
mapCommand(command: string, args: string[]): { command: string; args: string[] } {
155+
mapCommand(command: string, args: string[], options?: any): { command: string; args: string[] } {
156+
// Handle script commands
157+
if (options?.hasScript && !['install', 'add', 'remove', 'uninstall', 'update'].includes(command)) {
158+
return { command, args };
159+
}
160+
142161
if (command === 'install' && args.length > 0) {
162+
// Handle dev dependencies
163+
if (options?.dev) {
164+
return { command: 'add', args: ['--save-dev', ...args] };
165+
}
143166
return { command: 'add', args };
144167
}
145168
if (command === 'uninstall') {
@@ -183,8 +206,18 @@ export class BunPackageManager extends BasePackageManager {
183206
});
184207
}
185208

186-
mapCommand(command: string, args: string[]): { command: string; args: string[] } {
209+
mapCommand(command: string, args: string[], options?: any): { command: string; args: string[] } {
210+
// Handle script commands - if it's a script in package.json, use 'run' prefix
211+
// This includes Bun's built-in commands (test, build, update) when they're defined as scripts
212+
if (options?.hasScript && !['install', 'add', 'remove', 'uninstall', 'exec'].includes(command)) {
213+
return { command: 'run', args: [command, ...args] };
214+
}
215+
187216
if (command === 'install' && args.length > 0) {
217+
// Handle dev dependencies
218+
if (options?.dev) {
219+
return { command: 'add', args: ['-d', ...args] };
220+
}
188221
return { command: 'add', args };
189222
}
190223
if (command === 'uninstall') {
@@ -194,10 +227,7 @@ export class BunPackageManager extends BasePackageManager {
194227
return { command: 'run', args };
195228
}
196229

197-
const devFlags = ['-D', '--save-dev', '--dev'];
198-
const mappedArgs = args.map(arg => devFlags.includes(arg) ? '-d' : arg);
199-
200-
return { command, args: mappedArgs };
230+
return { command, args };
201231
}
202232

203233
detectVersion(cwd: string): string | undefined {
@@ -208,4 +238,81 @@ export class BunPackageManager extends BasePackageManager {
208238
return undefined;
209239
}
210240
}
241+
}
242+
243+
const DENO_JSON = {
244+
name: 'deno.json',
245+
parse: (content: string) => {
246+
const jsonWithComments = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//gm, '');
247+
return JSON.parse(jsonWithComments);
248+
},
249+
stringify: (data: any) => JSON.stringify(data, null, 2)
250+
};
251+
252+
export class DenoPackageManager extends BasePackageManager {
253+
constructor() {
254+
super({
255+
name: 'deno',
256+
ecosystem: 'javascript',
257+
packageFile: DENO_JSON,
258+
lockFile: { names: ['deno.lock'] },
259+
commands: {
260+
install: 'install',
261+
add: 'add',
262+
installDev: 'add --dev',
263+
remove: 'remove',
264+
update: 'update',
265+
list: 'info',
266+
outdated: 'outdated',
267+
run: 'task',
268+
exec: 'run',
269+
ci: 'install',
270+
globalFlag: '--global'
271+
},
272+
installDir: 'node_modules',
273+
detectFiles: ['deno.json', 'deno.jsonc'],
274+
workspaceSupport: true,
275+
workspaceConfigFiles: ['deno.json', 'deno.jsonc']
276+
});
277+
}
278+
279+
mapCommand(command: string, args: string[], options?: any): { command: string; args: string[] } {
280+
// Handle script commands - Deno uses 'task' for scripts
281+
if (options?.hasScript && !['install', 'add', 'remove', 'uninstall', 'update', 'exec'].includes(command)) {
282+
return { command: 'task', args: [command, ...args] };
283+
}
284+
285+
if (command === 'install' && args.length > 0) {
286+
// Handle dev dependencies
287+
if (options?.dev) {
288+
return { command: 'add', args: ['--dev', ...args] };
289+
}
290+
return { command: 'add', args };
291+
}
292+
if (command === 'uninstall') {
293+
return { command: 'remove', args };
294+
}
295+
if (command === 'run' && args.length > 0) {
296+
return { command: 'task', args };
297+
}
298+
if (command === 'exec') {
299+
return { command: 'run', args };
300+
}
301+
302+
return { command, args };
303+
}
304+
305+
detectVersion(cwd: string): string | undefined {
306+
try {
307+
const result = spawnSync('deno', ['--version'], { cwd, encoding: 'utf8' });
308+
const output = result.stdout?.trim();
309+
if (output) {
310+
const match = output.match(/deno ([\d.]+)/i);
311+
return match ? match[1] : undefined;
312+
}
313+
return undefined;
314+
} catch {
315+
return undefined;
316+
}
317+
}
211318
}

src/package-managers/python.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,12 @@ export class PipenvPackageManager extends BasePackageManager {
9696
});
9797
}
9898

99-
mapCommand(command: string, args: string[]): { command: string; args: string[] } {
100-
if (command === 'add') {
99+
mapCommand(command: string, args: string[], options?: any): { command: string; args: string[] } {
100+
if (command === 'add' || (command === 'install' && args.length > 0)) {
101+
// Handle dev dependencies
102+
if (options?.dev) {
103+
return { command: 'install', args: ['--dev', ...args] };
104+
}
101105
return { command: 'install', args };
102106
}
103107
if (command === 'remove') {
@@ -142,8 +146,12 @@ export class PoetryPackageManager extends BasePackageManager {
142146
});
143147
}
144148

145-
mapCommand(command: string, args: string[]): { command: string; args: string[] } {
149+
mapCommand(command: string, args: string[], options?: any): { command: string; args: string[] } {
146150
if (command === 'install' && args.length > 0) {
151+
// Handle dev dependencies
152+
if (options?.dev) {
153+
return { command: 'add', args: ['--dev', ...args] };
154+
}
147155
return { command: 'add', args };
148156
}
149157
if (command === 'uninstall') {
@@ -188,11 +196,15 @@ export class UvPackageManager extends BasePackageManager {
188196
});
189197
}
190198

191-
mapCommand(command: string, args: string[]): { command: string; args: string[] } {
199+
mapCommand(command: string, args: string[], options?: any): { command: string; args: string[] } {
192200
if (command === 'install') {
193201
if (args.length === 0) {
194202
return { command: 'sync', args: [] };
195203
}
204+
// Handle dev dependencies
205+
if (options?.dev) {
206+
return { command: 'add', args: ['--dev', ...args] };
207+
}
196208
return { command: 'add', args };
197209
}
198210
if (command === 'uninstall') {

src/xpm.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,13 @@ Supported package managers:
151151
try {
152152
const { packageManager, projectRoot, isWorkspace, workspaceRoot } = detectPackageManager();
153153

154-
// Auto-sync dependencies unless it's an install-like command
155-
// Always sync if lockfile is missing, even for scripts
154+
// Auto-sync dependencies unless it's an install-like command or a script
156155
// Only for JavaScript projects currently
157156
const isScript = command && packageManager.ecosystem === 'javascript' && hasScript(command, projectRoot);
158157
const shouldAutoSync = command &&
159158
!SKIP_SYNC_COMMANDS.includes(command as any) &&
160159
packageManager.ecosystem === 'javascript' &&
161-
(!isScript || require('./dependency-synchronizer').checkDependencies(packageManager, projectRoot, workspaceRoot));
160+
!isScript;
162161

163162
if (shouldAutoSync) {
164163
synchronizeDependencies({ packageManager, projectRoot, workspaceRoot, dryRun: this.dryRun });

test/test-suite.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ const tests = [
2929
commands: [
3030
{ cmd: 'install', expected: 'npm install' },
3131
{ cmd: 'install express', expected: 'npm install express' },
32-
{ cmd: 'install -D eslint', expected: 'npm install -D eslint' },
32+
{ cmd: 'install -D eslint', expected: 'npm install --save-dev eslint' },
3333
{ cmd: 'remove express', expected: 'npm uninstall express' },
3434
{ cmd: 'update', expected: 'npm update' },
35-
{ cmd: 'test', expected: 'npm run test' },
35+
{ cmd: 'test', expected: 'npm test' },
3636
{ cmd: 'build', expected: 'npm run build' }
3737
]
3838
},
@@ -43,7 +43,7 @@ const tests = [
4343
commands: [
4444
{ cmd: 'install', expected: 'yarn install' },
4545
{ cmd: 'install express', expected: 'yarn add express' },
46-
{ cmd: 'install -D eslint', expected: 'yarn add -D eslint' },
46+
{ cmd: 'install -D eslint', expected: 'yarn add --dev eslint' },
4747
{ cmd: 'remove express', expected: 'yarn remove express' },
4848
{ cmd: 'update', expected: 'yarn upgrade' },
4949
{ cmd: 'test', expected: 'yarn test' },
@@ -57,7 +57,7 @@ const tests = [
5757
commands: [
5858
{ cmd: 'install', expected: 'pnpm install' },
5959
{ cmd: 'install express', expected: 'pnpm add express' },
60-
{ cmd: 'install -D eslint', expected: 'pnpm add -D eslint' },
60+
{ cmd: 'install -D eslint', expected: 'pnpm add --save-dev eslint' },
6161
{ cmd: 'remove express', expected: 'pnpm remove express' },
6262
{ cmd: 'update', expected: 'pnpm update' },
6363
{ cmd: 'test', expected: 'pnpm test' },
@@ -74,8 +74,8 @@ const tests = [
7474
{ cmd: 'install -D eslint', expected: 'bun add -d eslint' },
7575
{ cmd: 'remove express', expected: 'bun remove express' },
7676
{ cmd: 'update', expected: 'bun update' },
77-
{ cmd: 'test', expected: 'bun test' },
78-
{ cmd: 'build', expected: 'bun build' }
77+
{ cmd: 'test', expected: 'bun run test' },
78+
{ cmd: 'build', expected: 'bun run build' }
7979
]
8080
},
8181
{

0 commit comments

Comments
 (0)