Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a8570b3

Browse files
feat(cli): respect package manager in generated rules and commands (#436)
* feat(cli): respect package manager in generated rules and commands Use the detected package manager (npm/bun/yarn/pnpm) when generating agent rules, hooks, and doctor messages instead of hardcoding `npx`. * Simplify implementation * Fix tests * Create yummy-dots-battle.md --------- Co-authored-by: Hayden Bleasel <[email protected]>
1 parent 180d241 commit a8570b3

13 files changed

Lines changed: 190 additions & 84 deletions

File tree

.changeset/yummy-dots-battle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ultracite": patch
3+
---
4+
5+
respect package manager in generated rules and commands

bun.lock

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/__tests__/agents.test.ts

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ mock.module("node:fs/promises", () => ({
88
mkdir: mock(() => Promise.resolve()),
99
}));
1010

11+
mock.module("nypm", () => ({
12+
detectPackageManager: mock(async () => ({ name: "npm" })),
13+
dlxCommand: mock((pm, pkg) => {
14+
const prefix =
15+
pm === "bun" ? "bunx" : pm === "yarn" ? "yarn dlx" : pm === "pnpm" ? "pnpm dlx" : "npx";
16+
return pkg ? `${prefix} ${pkg}` : prefix;
17+
}),
18+
}));
19+
1120
describe("createAgents", () => {
1221
beforeEach(() => {
1322
mock.restore();
@@ -27,7 +36,7 @@ describe("createAgents", () => {
2736
mkdir: mock(() => Promise.resolve()),
2837
}));
2938

30-
const agents = createAgents("cursor");
39+
const agents = createAgents("cursor", "npm");
3140
const result = await agents.exists();
3241
expect(result).toBe(true);
3342
});
@@ -43,7 +52,7 @@ describe("createAgents", () => {
4352
mkdir: mockMkdir,
4453
}));
4554

46-
const agents = createAgents("cursor");
55+
const agents = createAgents("cursor", "npm");
4756
await agents.create();
4857

4958
expect(mockMkdir).toHaveBeenCalled();
@@ -60,7 +69,7 @@ describe("createAgents", () => {
6069
mkdir: mock(() => Promise.resolve()),
6170
}));
6271

63-
const agents = createAgents("cursor");
72+
const agents = createAgents("cursor", "npm");
6473
await agents.create();
6574

6675
const writeCall = mockWriteFile.mock.calls[0];
@@ -69,6 +78,78 @@ describe("createAgents", () => {
6978
expect(writeCall[1]).toContain("description: Ultracite Rules");
7079
});
7180

81+
test("uses correct package runner for npm", async () => {
82+
const mockWriteFile = mock(() => Promise.resolve());
83+
84+
mock.module("node:fs/promises", () => ({
85+
access: mock(() => Promise.reject(new Error("ENOENT"))),
86+
readFile: mock(() => Promise.resolve("")),
87+
writeFile: mockWriteFile,
88+
mkdir: mock(() => Promise.resolve()),
89+
}));
90+
91+
const agents = createAgents("cursor", "npm");
92+
await agents.create();
93+
94+
const writeCall = mockWriteFile.mock.calls[0];
95+
expect(writeCall[1]).toContain("`npx ultracite fix`");
96+
expect(writeCall[1]).toContain("`npx ultracite check`");
97+
});
98+
99+
test("uses correct package runner for bun", async () => {
100+
const mockWriteFile = mock(() => Promise.resolve());
101+
102+
mock.module("node:fs/promises", () => ({
103+
access: mock(() => Promise.reject(new Error("ENOENT"))),
104+
readFile: mock(() => Promise.resolve("")),
105+
writeFile: mockWriteFile,
106+
mkdir: mock(() => Promise.resolve()),
107+
}));
108+
109+
const agents = createAgents("cursor", "bun");
110+
await agents.create();
111+
112+
const writeCall = mockWriteFile.mock.calls[0];
113+
expect(writeCall[1]).toContain("`bunx ultracite fix`");
114+
expect(writeCall[1]).toContain("`bunx ultracite check`");
115+
});
116+
117+
test("uses correct package runner for yarn", async () => {
118+
const mockWriteFile = mock(() => Promise.resolve());
119+
120+
mock.module("node:fs/promises", () => ({
121+
access: mock(() => Promise.reject(new Error("ENOENT"))),
122+
readFile: mock(() => Promise.resolve("")),
123+
writeFile: mockWriteFile,
124+
mkdir: mock(() => Promise.resolve()),
125+
}));
126+
127+
const agents = createAgents("cursor", "yarn");
128+
await agents.create();
129+
130+
const writeCall = mockWriteFile.mock.calls[0];
131+
expect(writeCall[1]).toContain("`yarn dlx ultracite fix`");
132+
expect(writeCall[1]).toContain("`yarn dlx ultracite check`");
133+
});
134+
135+
test("uses correct package runner for pnpm", async () => {
136+
const mockWriteFile = mock(() => Promise.resolve());
137+
138+
mock.module("node:fs/promises", () => ({
139+
access: mock(() => Promise.reject(new Error("ENOENT"))),
140+
readFile: mock(() => Promise.resolve("")),
141+
writeFile: mockWriteFile,
142+
mkdir: mock(() => Promise.resolve()),
143+
}));
144+
145+
const agents = createAgents("cursor", "pnpm");
146+
await agents.create();
147+
148+
const writeCall = mockWriteFile.mock.calls[0];
149+
expect(writeCall[1]).toContain("`pnpm dlx ultracite fix`");
150+
expect(writeCall[1]).toContain("`pnpm dlx ultracite check`");
151+
});
152+
72153
test("update overwrites rules file (not in append mode)", async () => {
73154
const mockWriteFile = mock(() => Promise.resolve());
74155

@@ -79,7 +160,7 @@ describe("createAgents", () => {
79160
mkdir: mock(() => Promise.resolve()),
80161
}));
81162

82-
const agents = createAgents("cursor");
163+
const agents = createAgents("cursor", "npm");
83164
await agents.update();
84165

85166
// Cursor is not in append mode, so it always overwrites
@@ -100,7 +181,7 @@ describe("createAgents", () => {
100181
mkdir: mock(() => Promise.resolve()),
101182
}));
102183

103-
const agents = createAgents("windsurf");
184+
const agents = createAgents("windsurf", "npm");
104185
await agents.create();
105186

106187
expect(mockWriteFile).toHaveBeenCalled();
@@ -120,7 +201,7 @@ describe("createAgents", () => {
120201
mkdir: mock(() => Promise.resolve()),
121202
}));
122203

123-
const agents = createAgents("vscode-copilot");
204+
const agents = createAgents("vscode-copilot", "npm");
124205
await agents.create();
125206

126207
expect(mockWriteFile).toHaveBeenCalled();
@@ -140,7 +221,7 @@ describe("createAgents", () => {
140221
mkdir: mock(() => Promise.resolve()),
141222
}));
142223

143-
const agents = createAgents("vscode-copilot");
224+
const agents = createAgents("vscode-copilot", "npm");
144225
await agents.update();
145226

146227
const writeCall = mockWriteFile.mock.calls[0];
@@ -159,7 +240,7 @@ describe("createAgents", () => {
159240
mkdir: mock(() => Promise.resolve()),
160241
}));
161242

162-
const agents = createAgents("zed");
243+
const agents = createAgents("zed", "npm");
163244
await agents.create();
164245

165246
expect(mockWriteFile).toHaveBeenCalled();
@@ -178,7 +259,7 @@ describe("createAgents", () => {
178259
mkdir: mock(() => Promise.resolve()),
179260
}));
180261

181-
const agents = createAgents("zed");
262+
const agents = createAgents("zed", "npm");
182263
await agents.update();
183264

184265
const writeCall = mockWriteFile.mock.calls[0];
@@ -198,7 +279,7 @@ describe("createAgents", () => {
198279
mkdir: mock(() => Promise.resolve()),
199280
}));
200281

201-
const agents = createAgents("zed");
282+
const agents = createAgents("zed", "npm");
202283
await agents.update();
203284

204285
expect(mockWriteFile).toHaveBeenCalled();
@@ -219,7 +300,7 @@ describe("createAgents", () => {
219300
mkdir: mock(() => Promise.resolve()),
220301
}));
221302

222-
const agents = createAgents("claude");
303+
const agents = createAgents("claude", "npm");
223304
await agents.create();
224305

225306
expect(mockWriteFile).toHaveBeenCalled();
@@ -239,7 +320,7 @@ describe("createAgents", () => {
239320
mkdir: mockMkdir,
240321
}));
241322

242-
const agents = createAgents("windsurf");
323+
const agents = createAgents("windsurf", "npm");
243324
await agents.create();
244325

245326
expect(mockMkdir).toHaveBeenCalled();
@@ -257,7 +338,7 @@ describe("createAgents", () => {
257338
mkdir: mockMkdir,
258339
}));
259340

260-
const agents = createAgents("codex");
341+
const agents = createAgents("codex", "npm");
261342
await agents.create();
262343

263344
// Should not be called for root-level AGENTS.md

packages/cli/__tests__/check.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ mock.module("node:child_process", () => ({
2828

2929
mock.module("nypm", () => ({
3030
detectPackageManager: mock(async () => ({ name: "npm" })),
31-
dlxCommand: mock((_pm, pkg, opts) => `npx ${pkg} ${opts.args.join(" ")}`),
31+
dlxCommand: mock((_pm, pkg, opts) => `npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`),
3232
}));
3333

3434
describe("check", () => {
@@ -47,7 +47,7 @@ describe("check", () => {
4747
}));
4848
mock.module("nypm", () => ({
4949
detectPackageManager: mock(async () => ({ name: "npm" })),
50-
dlxCommand: mock((_pm, pkg, opts) => `npx ${pkg} ${opts.args.join(" ")}`),
50+
dlxCommand: mock((_pm, pkg, opts) => `npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`),
5151
}));
5252

5353
await check(undefined);
@@ -71,7 +71,7 @@ describe("check", () => {
7171
}));
7272
mock.module("nypm", () => ({
7373
detectPackageManager: mock(async () => ({ name: "npm" })),
74-
dlxCommand: mock((_pm, pkg, opts) => `npx ${pkg} ${opts.args.join(" ")}`),
74+
dlxCommand: mock((_pm, pkg, opts) => `npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`),
7575
}));
7676

7777
await check([["src/index.ts", "src/test.ts"], {}]);
@@ -93,7 +93,7 @@ describe("check", () => {
9393
}));
9494
mock.module("nypm", () => ({
9595
detectPackageManager: mock(async () => ({ name: "npm" })),
96-
dlxCommand: mock((_pm, pkg, opts) => `npx ${pkg} ${opts.args.join(" ")}`),
96+
dlxCommand: mock((_pm, pkg, opts) => `npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`),
9797
}));
9898

9999
await check([[], { "diagnostic-level": "error" }]);
@@ -114,7 +114,7 @@ describe("check", () => {
114114
}));
115115
mock.module("nypm", () => ({
116116
detectPackageManager: mock(async () => ({ name: "npm" })),
117-
dlxCommand: mock((_pm, pkg, opts) => `npx ${pkg} ${opts.args.join(" ")}`),
117+
dlxCommand: mock((_pm, pkg, opts) => `npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`),
118118
}));
119119

120120
await check([["src/my file.ts"], {}]);
@@ -136,7 +136,7 @@ describe("check", () => {
136136
}));
137137
mock.module("nypm", () => ({
138138
detectPackageManager: mock(async () => ({ name: "npm" })),
139-
dlxCommand: mock((_pm, pkg, opts) => `npx ${pkg} ${opts.args.join(" ")}`),
139+
dlxCommand: mock((_pm, pkg, opts) => `npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`),
140140
}));
141141

142142
const result = await check(undefined);
@@ -155,7 +155,7 @@ describe("check", () => {
155155
}));
156156
mock.module("nypm", () => ({
157157
detectPackageManager: mock(async () => ({ name: "npm" })),
158-
dlxCommand: mock((_pm, pkg, opts) => `npx ${pkg} ${opts.args.join(" ")}`),
158+
dlxCommand: mock((_pm, pkg, opts) => `npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`),
159159
}));
160160

161161
await expect(check(undefined)).rejects.toThrow(

0 commit comments

Comments
 (0)