@lovrozagar/grunt 0.6.2 → 0.8.0
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/.agents/skills/su/SKILL.md +11 -0
- package/.claude/settings.json +11 -0
- package/.claude/skills/su/SKILL.md +11 -0
- package/.codex/hooks.json +11 -0
- package/.grok/hooks/orchestrate-parent.js +3 -8
- package/.grok/skills/su/SKILL.md +11 -0
- package/.rulesync/hooks.jsonc +8 -0
- package/.rulesync/reference/INDEX.md +3 -2
- package/.rulesync/reference/hooks.md +1 -1
- package/.rulesync/reference/law.md +1 -1
- package/.rulesync/reference/refs-map.md +1 -1
- package/.rulesync/reference/skills-map.md +1 -0
- package/.rulesync/rules/overview.md +2 -0
- package/.rulesync/skills/su/SKILL.md +11 -0
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +28 -0
- package/README.md +57 -23
- package/cli/grunt.mjs +99 -22
- package/cli/grunt.test.ts +168 -12
- package/cli/init.mjs +102 -18
- package/cli/init.test.ts +281 -2
- package/package.json +4 -1
- package/scripts/check-globals.mjs +13 -5
- package/scripts/doctor.mjs +62 -12
- package/scripts/folder-map.mjs +300 -0
- package/scripts/gate-fat-tools.mjs +9 -5
- package/scripts/hooks-union.mjs +15 -4
- package/scripts/package-manager.mjs +150 -0
- package/scripts/scrub-spawn-prompt.mjs +1 -1
- package/scripts/session-map.mjs +72 -0
package/cli/grunt.mjs
CHANGED
|
@@ -4,6 +4,15 @@ import path from "node:path"
|
|
|
4
4
|
import { fileURLToPath } from "node:url"
|
|
5
5
|
import { destAlreadyInited, init, RESERVED_SKILLS, toGruntScriptName } from "./init.mjs"
|
|
6
6
|
import { confirm, isInteractive, select, spinner } from "./prompt.mjs"
|
|
7
|
+
import {
|
|
8
|
+
PACKAGE_MANAGER_ASK,
|
|
9
|
+
PACKAGE_MANAGER_OPTIONS,
|
|
10
|
+
UNKNOWN_PACKAGE_MANAGER,
|
|
11
|
+
detectPackageManager,
|
|
12
|
+
isPackageManager,
|
|
13
|
+
runScriptArgs,
|
|
14
|
+
} from "../scripts/package-manager.mjs"
|
|
15
|
+
import { mapCommand } from "../scripts/folder-map.mjs"
|
|
7
16
|
|
|
8
17
|
const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
9
18
|
|
|
@@ -12,14 +21,15 @@ const USAGE = `Usage: grunt [command]
|
|
|
12
21
|
Default (no command): TTY menu; else init — full setup
|
|
13
22
|
|
|
14
23
|
Commands:
|
|
15
|
-
init Full setup: merge SoT,
|
|
16
|
-
generate
|
|
17
|
-
check
|
|
18
|
-
sync-globals
|
|
19
|
-
purge-mcps
|
|
20
|
-
doctor
|
|
21
|
-
setup
|
|
24
|
+
init Full setup: merge SoT, install, grunt:rulesync:generate, grunt:sync:globals:apply, grunt:rulesync:check
|
|
25
|
+
generate run grunt:rulesync:generate
|
|
26
|
+
check run grunt:rulesync:check
|
|
27
|
+
sync-globals run grunt:sync:globals (dry-run; --apply to write)
|
|
28
|
+
purge-mcps run grunt:purge:global-mcps (dry-run; --apply to write)
|
|
29
|
+
doctor run grunt:doctor
|
|
30
|
+
setup run grunt:setup — handheld keys/OAuth (speak, listen, google-workspace, browser)
|
|
22
31
|
upgrade Re-init: copy owned files, prune retired grunt-owned names, print reserved skills
|
|
32
|
+
map [dir] Code-only folder map (git-tracked, no files); dir for depth
|
|
23
33
|
help Show this help
|
|
24
34
|
version Print package version
|
|
25
35
|
|
|
@@ -29,6 +39,7 @@ Flags:
|
|
|
29
39
|
--non-interactive Same as --yes
|
|
30
40
|
--apply Write for sync-globals / purge-mcps
|
|
31
41
|
--host <id> sync-globals host
|
|
42
|
+
--pm <name> npm | yarn | pnpm | bun (else lockfile, then how grunt was launched, then ask)
|
|
32
43
|
`
|
|
33
44
|
|
|
34
45
|
const MENU_OPTIONS = [
|
|
@@ -45,26 +56,41 @@ const MENU_OPTIONS = [
|
|
|
45
56
|
]
|
|
46
57
|
|
|
47
58
|
const YES_FLAGS = new Set(["--yes", "-y", "--non-interactive"])
|
|
59
|
+
const NEEDS_PM = new Set([
|
|
60
|
+
"init",
|
|
61
|
+
"generate",
|
|
62
|
+
"check",
|
|
63
|
+
"sync-globals",
|
|
64
|
+
"purge-mcps",
|
|
65
|
+
"doctor",
|
|
66
|
+
"setup",
|
|
67
|
+
"upgrade",
|
|
68
|
+
])
|
|
48
69
|
|
|
49
70
|
function pkgVersion() {
|
|
50
71
|
const pkg = JSON.parse(readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"))
|
|
51
72
|
return pkg.version
|
|
52
73
|
}
|
|
53
74
|
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
execFileSync("npm", args, { cwd: process.cwd(), stdio: "inherit" })
|
|
75
|
+
function runScript(pm, script, extra = []) {
|
|
76
|
+
execFileSync(pm, runScriptArgs(pm, script, extra), { cwd: process.cwd(), stdio: "inherit" })
|
|
57
77
|
}
|
|
58
78
|
|
|
59
79
|
function hostValueOk(v) {
|
|
60
80
|
return v != null && v !== "" && !String(v).startsWith("-")
|
|
61
81
|
}
|
|
62
82
|
|
|
83
|
+
function pmValueOk(v) {
|
|
84
|
+
return v != null && v !== "" && isPackageManager(v)
|
|
85
|
+
}
|
|
86
|
+
|
|
63
87
|
export function parseArgv(argv) {
|
|
64
88
|
let skipGlobals = false
|
|
65
89
|
let apply = false
|
|
66
90
|
let host
|
|
67
91
|
let hostError = false
|
|
92
|
+
let pm
|
|
93
|
+
let pmError = false
|
|
68
94
|
const positionals = []
|
|
69
95
|
for (let i = 0; i < argv.length; i++) {
|
|
70
96
|
const a = argv[i]
|
|
@@ -92,9 +118,25 @@ export function parseArgv(argv) {
|
|
|
92
118
|
if (!hostValueOk(host)) hostError = true
|
|
93
119
|
continue
|
|
94
120
|
}
|
|
121
|
+
if (a === "--pm") {
|
|
122
|
+
const v = argv[i + 1]
|
|
123
|
+
if (!pmValueOk(v)) {
|
|
124
|
+
pmError = true
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
pm = String(v).toLowerCase()
|
|
128
|
+
i += 1
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
if (typeof a === "string" && a.startsWith("--pm=")) {
|
|
132
|
+
pm = a.slice("--pm=".length)
|
|
133
|
+
if (!pmValueOk(pm)) pmError = true
|
|
134
|
+
else pm = pm.toLowerCase()
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
95
137
|
positionals.push(a)
|
|
96
138
|
}
|
|
97
|
-
return { cmd: positionals[0], args: positionals.slice(1), skipGlobals, apply, host, hostError }
|
|
139
|
+
return { cmd: positionals[0], args: positionals.slice(1), skipGlobals, apply, host, hostError, pm, pmError }
|
|
98
140
|
}
|
|
99
141
|
|
|
100
142
|
function hostExtra(host) {
|
|
@@ -111,9 +153,23 @@ function bindSpinner() {
|
|
|
111
153
|
|
|
112
154
|
export const APPLY_GLOBALS_CONFIRM = "Apply global prompt optimizations? (recommended)"
|
|
113
155
|
|
|
114
|
-
async function
|
|
156
|
+
export async function resolveCliPackageManager({ cwd, env, override, interactive }) {
|
|
157
|
+
const detected = detectPackageManager({ cwd, env, override })
|
|
158
|
+
if (detected.manager) return detected.manager
|
|
159
|
+
if (interactive) {
|
|
160
|
+
return await select({
|
|
161
|
+
message: PACKAGE_MANAGER_ASK,
|
|
162
|
+
options: PACKAGE_MANAGER_OPTIONS,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
process.stdout.write(`${UNKNOWN_PACKAGE_MANAGER}\n`)
|
|
166
|
+
process.exitCode = 1
|
|
167
|
+
return null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function runInit(cwd, { skipGlobals, interactive, packageManager }) {
|
|
115
171
|
if (!interactive) {
|
|
116
|
-
init(cwd, { skipGlobals })
|
|
172
|
+
init(cwd, { skipGlobals, packageManager })
|
|
117
173
|
return
|
|
118
174
|
}
|
|
119
175
|
if (destAlreadyInited(cwd)) {
|
|
@@ -131,10 +187,11 @@ async function runInit(cwd, { skipGlobals, interactive }) {
|
|
|
131
187
|
skipGlobals: !applyGlobals,
|
|
132
188
|
applyGlobals,
|
|
133
189
|
onPhase: bindSpinner(),
|
|
190
|
+
packageManager,
|
|
134
191
|
})
|
|
135
192
|
}
|
|
136
193
|
|
|
137
|
-
async function dispatch(cmd, flags, interactive) {
|
|
194
|
+
async function dispatch(cmd, flags, interactive, pm) {
|
|
138
195
|
if (cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
139
196
|
process.stdout.write(USAGE)
|
|
140
197
|
return
|
|
@@ -147,38 +204,44 @@ async function dispatch(cmd, flags, interactive) {
|
|
|
147
204
|
await runInit(process.cwd(), {
|
|
148
205
|
skipGlobals: flags.skipGlobals,
|
|
149
206
|
interactive: interactive && (!cmd || cmd === "init"),
|
|
207
|
+
packageManager: pm,
|
|
150
208
|
})
|
|
151
209
|
return
|
|
152
210
|
}
|
|
153
211
|
if (cmd === "generate") {
|
|
154
|
-
|
|
212
|
+
runScript(pm, toGruntScriptName("rulesync:generate"))
|
|
155
213
|
return
|
|
156
214
|
}
|
|
157
215
|
if (cmd === "check") {
|
|
158
|
-
|
|
216
|
+
runScript(pm, toGruntScriptName("rulesync:check"))
|
|
159
217
|
return
|
|
160
218
|
}
|
|
161
219
|
if (cmd === "sync-globals") {
|
|
162
220
|
const script = flags.apply ? toGruntScriptName("sync:globals:apply") : toGruntScriptName("sync:globals")
|
|
163
|
-
|
|
221
|
+
runScript(pm, script, hostExtra(flags.host))
|
|
164
222
|
return
|
|
165
223
|
}
|
|
166
224
|
if (cmd === "purge-mcps") {
|
|
167
|
-
|
|
225
|
+
runScript(pm, flags.apply ? toGruntScriptName("purge:global-mcps:apply") : toGruntScriptName("purge:global-mcps"))
|
|
168
226
|
return
|
|
169
227
|
}
|
|
170
228
|
if (cmd === "doctor") {
|
|
171
|
-
|
|
229
|
+
runScript(pm, toGruntScriptName("doctor"))
|
|
172
230
|
return
|
|
173
231
|
}
|
|
174
232
|
if (cmd === "setup") {
|
|
175
|
-
|
|
233
|
+
runScript(pm, toGruntScriptName("setup"), flags.args)
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
if (cmd === "map") {
|
|
237
|
+
process.stdout.write(mapCommand(process.cwd(), flags.args[0] || ""))
|
|
176
238
|
return
|
|
177
239
|
}
|
|
178
240
|
if (cmd === "upgrade") {
|
|
179
241
|
await runInit(process.cwd(), {
|
|
180
242
|
skipGlobals: flags.skipGlobals,
|
|
181
243
|
interactive: false,
|
|
244
|
+
packageManager: pm,
|
|
182
245
|
})
|
|
183
246
|
process.stdout.write(`reserved: ${RESERVED_SKILLS.join(" ")}\n`)
|
|
184
247
|
return
|
|
@@ -187,9 +250,13 @@ async function dispatch(cmd, flags, interactive) {
|
|
|
187
250
|
process.exitCode = 1
|
|
188
251
|
}
|
|
189
252
|
|
|
253
|
+
function commandNeedsPackageManager(cmd) {
|
|
254
|
+
return !cmd || NEEDS_PM.has(cmd)
|
|
255
|
+
}
|
|
256
|
+
|
|
190
257
|
export async function start() {
|
|
191
258
|
const flags = parseArgv(process.argv.slice(2))
|
|
192
|
-
if (flags.hostError) {
|
|
259
|
+
if (flags.hostError || flags.pmError) {
|
|
193
260
|
process.stdout.write(USAGE)
|
|
194
261
|
process.exitCode = 1
|
|
195
262
|
return
|
|
@@ -205,5 +272,15 @@ export async function start() {
|
|
|
205
272
|
if (choice === "quit") return
|
|
206
273
|
cmd = choice
|
|
207
274
|
}
|
|
208
|
-
|
|
275
|
+
let pm
|
|
276
|
+
if (commandNeedsPackageManager(cmd)) {
|
|
277
|
+
pm = await resolveCliPackageManager({
|
|
278
|
+
cwd: process.cwd(),
|
|
279
|
+
env: process.env,
|
|
280
|
+
override: flags.pm,
|
|
281
|
+
interactive,
|
|
282
|
+
})
|
|
283
|
+
if (!pm) return
|
|
284
|
+
}
|
|
285
|
+
await dispatch(cmd, flags, interactive, pm)
|
|
209
286
|
}
|
package/cli/grunt.test.ts
CHANGED
|
@@ -10,6 +10,8 @@ const toGruntScriptName = vi.hoisted(
|
|
|
10
10
|
() => (k) => (String(k).startsWith("grunt:") ? k : `grunt:${k}`),
|
|
11
11
|
);
|
|
12
12
|
const execFileSync = vi.hoisted(() => vi.fn());
|
|
13
|
+
const detectPackageManager = vi.hoisted(() => vi.fn());
|
|
14
|
+
const actualDetect = vi.hoisted(() => ({ fn: null as null | ((...args: unknown[]) => unknown) }));
|
|
13
15
|
const isInteractive = vi.hoisted(() => vi.fn(() => false));
|
|
14
16
|
const select = vi.hoisted(() => vi.fn());
|
|
15
17
|
const confirm = vi.hoisted(() => vi.fn());
|
|
@@ -17,6 +19,7 @@ const spinner = vi.hoisted(() =>
|
|
|
17
19
|
vi.fn(() => ({ start: vi.fn(), stop: vi.fn() })),
|
|
18
20
|
);
|
|
19
21
|
const bailIfCancel = vi.hoisted(() => vi.fn((v) => v));
|
|
22
|
+
const mapCommand = vi.hoisted(() => vi.fn((_cwd: string, dir: string) => `map:${dir}\n`));
|
|
20
23
|
|
|
21
24
|
vi.mock("./init.mjs", () => ({
|
|
22
25
|
init,
|
|
@@ -27,6 +30,17 @@ vi.mock("./init.mjs", () => ({
|
|
|
27
30
|
GRUNT_NPM_PREFIX: "grunt:",
|
|
28
31
|
}));
|
|
29
32
|
vi.mock("node:child_process", () => ({ execFileSync }));
|
|
33
|
+
vi.mock("../scripts/folder-map.mjs", () => ({ mapCommand }));
|
|
34
|
+
vi.mock("../scripts/package-manager.mjs", async (importOriginal) => {
|
|
35
|
+
const actual = await importOriginal<typeof import("../scripts/package-manager.mjs")>();
|
|
36
|
+
actualDetect.fn = actual.detectPackageManager;
|
|
37
|
+
detectPackageManager.mockImplementation((...args: unknown[]) =>
|
|
38
|
+
actual.detectPackageManager(
|
|
39
|
+
...(args as Parameters<typeof actual.detectPackageManager>),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
return { ...actual, detectPackageManager };
|
|
43
|
+
});
|
|
30
44
|
vi.mock("./prompt.mjs", () => ({
|
|
31
45
|
isInteractive,
|
|
32
46
|
select,
|
|
@@ -35,6 +49,10 @@ vi.mock("./prompt.mjs", () => ({
|
|
|
35
49
|
bailIfCancel,
|
|
36
50
|
}));
|
|
37
51
|
|
|
52
|
+
import {
|
|
53
|
+
PACKAGE_MANAGER_ASK,
|
|
54
|
+
UNKNOWN_PACKAGE_MANAGER,
|
|
55
|
+
} from "../scripts/package-manager.mjs";
|
|
38
56
|
import { APPLY_GLOBALS_CONFIRM, parseArgv, start } from "./grunt.mjs";
|
|
39
57
|
|
|
40
58
|
const USAGE = `Usage: grunt [command]
|
|
@@ -42,14 +60,15 @@ const USAGE = `Usage: grunt [command]
|
|
|
42
60
|
Default (no command): TTY menu; else init — full setup
|
|
43
61
|
|
|
44
62
|
Commands:
|
|
45
|
-
init Full setup: merge SoT,
|
|
46
|
-
generate
|
|
47
|
-
check
|
|
48
|
-
sync-globals
|
|
49
|
-
purge-mcps
|
|
50
|
-
doctor
|
|
51
|
-
setup
|
|
63
|
+
init Full setup: merge SoT, install, grunt:rulesync:generate, grunt:sync:globals:apply, grunt:rulesync:check
|
|
64
|
+
generate run grunt:rulesync:generate
|
|
65
|
+
check run grunt:rulesync:check
|
|
66
|
+
sync-globals run grunt:sync:globals (dry-run; --apply to write)
|
|
67
|
+
purge-mcps run grunt:purge:global-mcps (dry-run; --apply to write)
|
|
68
|
+
doctor run grunt:doctor
|
|
69
|
+
setup run grunt:setup — handheld keys/OAuth (speak, listen, google-workspace, browser)
|
|
52
70
|
upgrade Re-init: copy owned files, prune retired grunt-owned names, print reserved skills
|
|
71
|
+
map [dir] Code-only folder map (git-tracked, no files); dir for depth
|
|
53
72
|
help Show this help
|
|
54
73
|
version Print package version
|
|
55
74
|
|
|
@@ -59,6 +78,7 @@ Flags:
|
|
|
59
78
|
--non-interactive Same as --yes
|
|
60
79
|
--apply Write for sync-globals / purge-mcps
|
|
61
80
|
--host <id> sync-globals host
|
|
81
|
+
--pm <name> npm | yarn | pnpm | bun (else lockfile, then how grunt was launched, then ask)
|
|
62
82
|
`;
|
|
63
83
|
|
|
64
84
|
const pkgRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -88,6 +108,10 @@ describe("start", () => {
|
|
|
88
108
|
shouldAutoSkipGlobals.mockReset();
|
|
89
109
|
shouldAutoSkipGlobals.mockReturnValue(false);
|
|
90
110
|
execFileSync.mockReset();
|
|
111
|
+
detectPackageManager.mockReset();
|
|
112
|
+
detectPackageManager.mockImplementation((...args: unknown[]) =>
|
|
113
|
+
(actualDetect.fn as (...a: unknown[]) => unknown)(...args),
|
|
114
|
+
);
|
|
91
115
|
isInteractive.mockReset();
|
|
92
116
|
isInteractive.mockReturnValue(false);
|
|
93
117
|
select.mockReset();
|
|
@@ -125,7 +149,10 @@ describe("start", () => {
|
|
|
125
149
|
process.argv = ["node", "grunt"];
|
|
126
150
|
await start();
|
|
127
151
|
expect(init).toHaveBeenCalledOnce();
|
|
128
|
-
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
152
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
153
|
+
skipGlobals: false,
|
|
154
|
+
packageManager: "npm",
|
|
155
|
+
});
|
|
129
156
|
expect(execFileSync).not.toHaveBeenCalled();
|
|
130
157
|
expect(select).not.toHaveBeenCalled();
|
|
131
158
|
});
|
|
@@ -134,7 +161,10 @@ describe("start", () => {
|
|
|
134
161
|
process.argv = ["node", "grunt", "init"];
|
|
135
162
|
await start();
|
|
136
163
|
expect(init).toHaveBeenCalledOnce();
|
|
137
|
-
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
164
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
165
|
+
skipGlobals: false,
|
|
166
|
+
packageManager: "npm",
|
|
167
|
+
});
|
|
138
168
|
});
|
|
139
169
|
|
|
140
170
|
it.each([
|
|
@@ -145,7 +175,10 @@ describe("start", () => {
|
|
|
145
175
|
process.argv = argv;
|
|
146
176
|
await start();
|
|
147
177
|
expect(init).toHaveBeenCalledOnce();
|
|
148
|
-
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
178
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
179
|
+
skipGlobals: true,
|
|
180
|
+
packageManager: "npm",
|
|
181
|
+
});
|
|
149
182
|
});
|
|
150
183
|
|
|
151
184
|
it.each(["--yes", "-y", "--non-interactive"])(
|
|
@@ -156,7 +189,10 @@ describe("start", () => {
|
|
|
156
189
|
expect(isInteractive).toHaveBeenCalledOnce();
|
|
157
190
|
expect(select).not.toHaveBeenCalled();
|
|
158
191
|
expect(init).toHaveBeenCalledOnce();
|
|
159
|
-
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
192
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
193
|
+
skipGlobals: false,
|
|
194
|
+
packageManager: "npm",
|
|
195
|
+
});
|
|
160
196
|
},
|
|
161
197
|
);
|
|
162
198
|
|
|
@@ -280,11 +316,30 @@ describe("start", () => {
|
|
|
280
316
|
process.argv = ["node", "grunt", "upgrade"];
|
|
281
317
|
await start();
|
|
282
318
|
expect(init).toHaveBeenCalledOnce();
|
|
283
|
-
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
319
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
320
|
+
skipGlobals: false,
|
|
321
|
+
packageManager: "npm",
|
|
322
|
+
});
|
|
284
323
|
expect(chunks.join("")).toBe("reserved: browser tmp write-plan\n");
|
|
285
324
|
expect(select).not.toHaveBeenCalled();
|
|
286
325
|
});
|
|
287
326
|
|
|
327
|
+
it("map prints the folder map without a package manager", async () => {
|
|
328
|
+
process.argv = ["node", "grunt", "map"];
|
|
329
|
+
await start();
|
|
330
|
+
expect(mapCommand).toHaveBeenLastCalledWith(process.cwd(), "");
|
|
331
|
+
expect(chunks.join("")).toBe("map:\n");
|
|
332
|
+
expect(detectPackageManager).not.toHaveBeenCalled();
|
|
333
|
+
expect(execFileSync).not.toHaveBeenCalled();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("map <dir> scopes the folder map", async () => {
|
|
337
|
+
process.argv = ["node", "grunt", "map", "packages/web"];
|
|
338
|
+
await start();
|
|
339
|
+
expect(mapCommand).toHaveBeenLastCalledWith(process.cwd(), "packages/web");
|
|
340
|
+
expect(chunks.join("")).toBe("map:packages/web\n");
|
|
341
|
+
});
|
|
342
|
+
|
|
288
343
|
it("unknown writes usage and exitCode 1", async () => {
|
|
289
344
|
process.argv = ["node", "grunt", "nope"];
|
|
290
345
|
await start();
|
|
@@ -408,6 +463,7 @@ describe("start", () => {
|
|
|
408
463
|
expect.objectContaining({
|
|
409
464
|
skipGlobals: false,
|
|
410
465
|
applyGlobals: true,
|
|
466
|
+
packageManager: "npm",
|
|
411
467
|
onPhase: expect.any(Function),
|
|
412
468
|
}),
|
|
413
469
|
);
|
|
@@ -418,6 +474,84 @@ describe("start", () => {
|
|
|
418
474
|
expect(spin.stop).toHaveBeenCalledWith("install");
|
|
419
475
|
});
|
|
420
476
|
|
|
477
|
+
it("init --pm pnpm passes packageManager", async () => {
|
|
478
|
+
process.argv = ["node", "grunt", "init", "--pm", "pnpm"];
|
|
479
|
+
await start();
|
|
480
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
481
|
+
skipGlobals: false,
|
|
482
|
+
packageManager: "pnpm",
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it("generate --pm pnpm runs pnpm", async () => {
|
|
487
|
+
process.argv = ["node", "grunt", "generate", "--pm", "pnpm"];
|
|
488
|
+
await start();
|
|
489
|
+
expect(execFileSync).toHaveBeenCalledWith("pnpm", ["run", "grunt:rulesync:generate"], {
|
|
490
|
+
cwd: process.cwd(),
|
|
491
|
+
stdio: "inherit",
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("setup --pm=yarn extra args omit npm --", async () => {
|
|
496
|
+
process.argv = ["node", "grunt", "setup", "--pm=yarn", "speak"];
|
|
497
|
+
await start();
|
|
498
|
+
expect(execFileSync).toHaveBeenCalledWith("yarn", ["run", "grunt:setup", "speak"], {
|
|
499
|
+
cwd: process.cwd(),
|
|
500
|
+
stdio: "inherit",
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("invalid --pm writes usage exit 1", async () => {
|
|
505
|
+
process.argv = ["node", "grunt", "generate", "--pm", "deno"];
|
|
506
|
+
await start();
|
|
507
|
+
expect(chunks.join("")).toBe(USAGE);
|
|
508
|
+
expect(process.exitCode).toBe(1);
|
|
509
|
+
expect(execFileSync).not.toHaveBeenCalled();
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("TTY menu then unknown manager asks", async () => {
|
|
513
|
+
detectPackageManager.mockReturnValue({ manager: null, source: null });
|
|
514
|
+
isInteractive.mockReturnValue(true);
|
|
515
|
+
select.mockResolvedValueOnce("generate").mockResolvedValueOnce("pnpm");
|
|
516
|
+
process.argv = ["node", "grunt"];
|
|
517
|
+
await start();
|
|
518
|
+
expect(select).toHaveBeenCalledTimes(2);
|
|
519
|
+
expect(execFileSync).toHaveBeenCalledWith("pnpm", ["run", "grunt:rulesync:generate"], {
|
|
520
|
+
cwd: process.cwd(),
|
|
521
|
+
stdio: "inherit",
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("TTY unknown manager asks then runs the choice", async () => {
|
|
526
|
+
detectPackageManager.mockReturnValue({ manager: null, source: null });
|
|
527
|
+
isInteractive.mockReturnValue(true);
|
|
528
|
+
select.mockResolvedValue("bun");
|
|
529
|
+
process.argv = ["node", "grunt", "generate"];
|
|
530
|
+
await start();
|
|
531
|
+
expect(select).toHaveBeenCalledWith({
|
|
532
|
+
message: PACKAGE_MANAGER_ASK,
|
|
533
|
+
options: [
|
|
534
|
+
{ value: "npm", label: "npm" },
|
|
535
|
+
{ value: "yarn", label: "yarn" },
|
|
536
|
+
{ value: "pnpm", label: "pnpm" },
|
|
537
|
+
{ value: "bun", label: "bun" },
|
|
538
|
+
],
|
|
539
|
+
});
|
|
540
|
+
expect(execFileSync).toHaveBeenCalledWith("bun", ["run", "grunt:rulesync:generate"], {
|
|
541
|
+
cwd: process.cwd(),
|
|
542
|
+
stdio: "inherit",
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it("non-interactive unknown manager writes hint and skips run", async () => {
|
|
547
|
+
detectPackageManager.mockReturnValue({ manager: null, source: null });
|
|
548
|
+
process.argv = ["node", "grunt", "generate"];
|
|
549
|
+
await start();
|
|
550
|
+
expect(chunks.join("")).toBe(`${UNKNOWN_PACKAGE_MANAGER}\n`);
|
|
551
|
+
expect(process.exitCode).toBe(1);
|
|
552
|
+
expect(execFileSync).not.toHaveBeenCalled();
|
|
553
|
+
});
|
|
554
|
+
|
|
421
555
|
it("TTY init --skip-globals default confirm false maps to skip", async () => {
|
|
422
556
|
isInteractive.mockReturnValue(true);
|
|
423
557
|
confirm.mockResolvedValue(false);
|
|
@@ -432,6 +566,7 @@ describe("start", () => {
|
|
|
432
566
|
expect.objectContaining({
|
|
433
567
|
skipGlobals: true,
|
|
434
568
|
applyGlobals: false,
|
|
569
|
+
packageManager: "npm",
|
|
435
570
|
onPhase: expect.any(Function),
|
|
436
571
|
}),
|
|
437
572
|
);
|
|
@@ -462,3 +597,24 @@ describe("parseArgv --host", () => {
|
|
|
462
597
|
});
|
|
463
598
|
});
|
|
464
599
|
});
|
|
600
|
+
|
|
601
|
+
describe("parseArgv --pm", () => {
|
|
602
|
+
it("pair and equals", () => {
|
|
603
|
+
expect(parseArgv(["generate", "--pm", "PNPM"])).toMatchObject({
|
|
604
|
+
cmd: "generate",
|
|
605
|
+
pm: "pnpm",
|
|
606
|
+
pmError: false,
|
|
607
|
+
});
|
|
608
|
+
expect(parseArgv(["generate", "--pm=Yarn"])).toMatchObject({
|
|
609
|
+
pm: "yarn",
|
|
610
|
+
pmError: false,
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it("missing, empty, flag, and unknown are pmError", () => {
|
|
615
|
+
expect(parseArgv(["generate", "--pm"])).toMatchObject({ pmError: true });
|
|
616
|
+
expect(parseArgv(["generate", "--pm="])).toMatchObject({ pmError: true });
|
|
617
|
+
expect(parseArgv(["generate", "--pm", "--yes"])).toMatchObject({ pmError: true });
|
|
618
|
+
expect(parseArgv(["generate", "--pm", "deno"])).toMatchObject({ pmError: true });
|
|
619
|
+
});
|
|
620
|
+
});
|