@lovrozagar/grunt 0.6.2 → 0.7.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/CHANGELOG.md +13 -0
- package/README.md +32 -22
- package/cli/grunt.mjs +93 -22
- package/cli/grunt.test.ts +149 -12
- package/cli/init.mjs +93 -17
- package/cli/init.test.ts +251 -2
- package/package.json +2 -1
- package/scripts/check-globals.mjs +13 -5
- package/scripts/doctor.mjs +62 -12
- package/scripts/package-manager.mjs +150 -0
package/scripts/doctor.mjs
CHANGED
|
@@ -11,6 +11,11 @@ import {
|
|
|
11
11
|
findSkillContentConflicts,
|
|
12
12
|
formatSkillConflictWarn,
|
|
13
13
|
} from "./skill-conflicts.mjs";
|
|
14
|
+
import {
|
|
15
|
+
PACKAGE_MANAGERS,
|
|
16
|
+
detectPackageManager,
|
|
17
|
+
runScriptLine,
|
|
18
|
+
} from "./package-manager.mjs";
|
|
14
19
|
|
|
15
20
|
export const CHROMIUM_BINS = [
|
|
16
21
|
"chromium",
|
|
@@ -28,8 +33,7 @@ const RTK_CURL =
|
|
|
28
33
|
"curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/master/install.sh | sh";
|
|
29
34
|
const RTK_DOCS = "https://www.rtk-ai.app/docs/getting-started/installation/";
|
|
30
35
|
const LP_INSTALL_SH = "curl -fsSL https://pkg.lightpanda.io/install.sh | bash";
|
|
31
|
-
const
|
|
32
|
-
const REQUIRED = ["node", "npm", "git", "rtk", "rulesync", "lightpanda", "chromium"];
|
|
36
|
+
const REQUIRED = ["node", "git", "rtk", "rulesync", "lightpanda", "chromium"];
|
|
33
37
|
export const REQUIRED_MAP_FILES = [
|
|
34
38
|
{ name: "INDEX.md", rel: ".rulesync/reference/INDEX.md" },
|
|
35
39
|
{ name: "skills-map.md", rel: ".rulesync/reference/skills-map.md" },
|
|
@@ -127,7 +131,7 @@ function lookupChromium(pathEnv, platform) {
|
|
|
127
131
|
|
|
128
132
|
export function installHints(id, platform = process.platform) {
|
|
129
133
|
if (id === "all") {
|
|
130
|
-
const keys = ["node", "
|
|
134
|
+
const keys = ["node", "package-manager", "git", "rtk", "rulesync", "lightpanda", "chromium"];
|
|
131
135
|
return keys.flatMap((k) => installHints(k, platform));
|
|
132
136
|
}
|
|
133
137
|
if (id === "node") {
|
|
@@ -139,6 +143,22 @@ export function installHints(id, platform = process.platform) {
|
|
|
139
143
|
if (id === "npm") {
|
|
140
144
|
return ["npm ships with Node ≥22", NODE_URL];
|
|
141
145
|
}
|
|
146
|
+
if (id === "yarn") {
|
|
147
|
+
return ["corepack enable", "npm i -g yarn", "https://yarnpkg.com/getting-started/install"];
|
|
148
|
+
}
|
|
149
|
+
if (id === "pnpm") {
|
|
150
|
+
return ["corepack enable", "npm i -g pnpm", "https://pnpm.io/installation"];
|
|
151
|
+
}
|
|
152
|
+
if (id === "bun") {
|
|
153
|
+
return ["curl -fsSL https://bun.sh/install | bash", "https://bun.sh"];
|
|
154
|
+
}
|
|
155
|
+
if (id === "package-manager") {
|
|
156
|
+
return [
|
|
157
|
+
"npm ships with Node ≥22",
|
|
158
|
+
"corepack enable (yarn / pnpm)",
|
|
159
|
+
"curl -fsSL https://bun.sh/install | bash",
|
|
160
|
+
];
|
|
161
|
+
}
|
|
142
162
|
if (id === "git") {
|
|
143
163
|
if (platform === "win32") return ["winget install Git.Git"];
|
|
144
164
|
if (platform === "darwin") return ["brew install git"];
|
|
@@ -151,7 +171,13 @@ export function installHints(id, platform = process.platform) {
|
|
|
151
171
|
return [RTK_CURL, "brew install rtk"];
|
|
152
172
|
}
|
|
153
173
|
if (id === "rulesync") {
|
|
154
|
-
return [
|
|
174
|
+
return [
|
|
175
|
+
"npm i -D rulesync",
|
|
176
|
+
"yarn add -D rulesync",
|
|
177
|
+
"pnpm add -D rulesync",
|
|
178
|
+
"bun add -D rulesync",
|
|
179
|
+
"npx",
|
|
180
|
+
];
|
|
155
181
|
}
|
|
156
182
|
if (id === "lightpanda") {
|
|
157
183
|
if (platform === "win32") {
|
|
@@ -296,7 +322,17 @@ export function runDoctor({
|
|
|
296
322
|
const major = nodeMajor(ver);
|
|
297
323
|
const nodeOk = Boolean(nodeBin) && major >= 22;
|
|
298
324
|
|
|
299
|
-
const
|
|
325
|
+
const pmBins = Object.fromEntries(
|
|
326
|
+
PACKAGE_MANAGERS.map((name) => [name, whichBin(name, pathEnv, platform)]),
|
|
327
|
+
);
|
|
328
|
+
const detectedPm = detectPackageManager({ cwd, env });
|
|
329
|
+
const wantedPm = detectedPm.manager
|
|
330
|
+
? [detectedPm.manager]
|
|
331
|
+
: detectedPm.candidates?.length
|
|
332
|
+
? detectedPm.candidates
|
|
333
|
+
: PACKAGE_MANAGERS;
|
|
334
|
+
const presentPm = wantedPm.filter((name) => pmBins[name]);
|
|
335
|
+
const pmOk = presentPm.length > 0;
|
|
300
336
|
const git = whichBin("git", pathEnv, platform);
|
|
301
337
|
const rtk = whichBin("rtk", nmPath, platform);
|
|
302
338
|
const rulesync = whichBin("rulesync", nmPath, platform);
|
|
@@ -310,7 +346,6 @@ export function runDoctor({
|
|
|
310
346
|
|
|
311
347
|
const found = {
|
|
312
348
|
node: nodeOk ? nodeBin : "",
|
|
313
|
-
npm,
|
|
314
349
|
git,
|
|
315
350
|
rtk,
|
|
316
351
|
rulesync,
|
|
@@ -326,7 +361,17 @@ export function runDoctor({
|
|
|
326
361
|
} else {
|
|
327
362
|
lines.push(row("node", "missing"));
|
|
328
363
|
}
|
|
329
|
-
|
|
364
|
+
if (pmOk) {
|
|
365
|
+
lines.push(
|
|
366
|
+
row(
|
|
367
|
+
"package-manager",
|
|
368
|
+
"ok",
|
|
369
|
+
presentPm.map((name) => `${name} (${pmBins[name]})`).join(", "),
|
|
370
|
+
),
|
|
371
|
+
);
|
|
372
|
+
} else {
|
|
373
|
+
lines.push(row("package-manager", "missing", wantedPm.join(" | ")));
|
|
374
|
+
}
|
|
330
375
|
lines.push(git ? row("git", "ok", git) : row("git", "missing"));
|
|
331
376
|
lines.push(rtk ? row("rtk", "ok", rtk) : row("rtk", "missing"));
|
|
332
377
|
lines.push(rulesync ? row("rulesync", "ok", rulesync) : row("rulesync", "missing"));
|
|
@@ -348,15 +393,20 @@ export function runDoctor({
|
|
|
348
393
|
lines.push(speak.ok ? row("speak", "ok", speak.extra) : row("speak", "missing (optional)"));
|
|
349
394
|
|
|
350
395
|
const missingRequired = REQUIRED.filter((k) => (k === "node" ? !nodeOk : !found[k]));
|
|
396
|
+
if (!pmOk) missingRequired.push("package-manager");
|
|
351
397
|
if (missingRequired.length) {
|
|
352
398
|
lines.push("");
|
|
353
399
|
lines.push("install (print-only; not run):");
|
|
354
400
|
const seen = new Set();
|
|
355
401
|
for (const k of missingRequired) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
402
|
+
const hintIds =
|
|
403
|
+
k === "package-manager" && wantedPm.length === 1 ? wantedPm : [k];
|
|
404
|
+
for (const id of hintIds) {
|
|
405
|
+
for (const h of installHints(id, platform)) {
|
|
406
|
+
if (seen.has(h)) continue;
|
|
407
|
+
seen.add(h);
|
|
408
|
+
lines.push(h);
|
|
409
|
+
}
|
|
360
410
|
}
|
|
361
411
|
}
|
|
362
412
|
}
|
|
@@ -389,7 +439,7 @@ export function runDoctor({
|
|
|
389
439
|
}
|
|
390
440
|
|
|
391
441
|
lines.push("");
|
|
392
|
-
lines.push(
|
|
442
|
+
lines.push(`rulesync schema doctor: ${runScriptLine(detectedPm.manager, "rulesync:doctor")}`);
|
|
393
443
|
const stdout = `${lines.join("\n")}\n`;
|
|
394
444
|
return { code: missingRequired.length || missingMaps.length ? 1 : 0, stdout, stderr: "" };
|
|
395
445
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** Detect npm | yarn | pnpm | bun. Never default to npm. */
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export const PACKAGE_MANAGERS = ["npm", "yarn", "pnpm", "bun"];
|
|
6
|
+
|
|
7
|
+
export const UNKNOWN_PACKAGE_MANAGER =
|
|
8
|
+
'Cannot tell which package manager to use (npm, yarn, pnpm, bun). Add a lockfile or package.json "packageManager" field, or pass --pm <name>.';
|
|
9
|
+
|
|
10
|
+
export const PACKAGE_MANAGER_ASK = "Which package manager?";
|
|
11
|
+
|
|
12
|
+
export const PACKAGE_MANAGER_OPTIONS = PACKAGE_MANAGERS.map((value) => ({
|
|
13
|
+
value,
|
|
14
|
+
label: value,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const LOCKFILES = [
|
|
18
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
19
|
+
["yarn.lock", "yarn"],
|
|
20
|
+
["bun.lockb", "bun"],
|
|
21
|
+
["bun.lock", "bun"],
|
|
22
|
+
["package-lock.json", "npm"],
|
|
23
|
+
["npm-shrinkwrap.json", "npm"],
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function isFile(abs) {
|
|
27
|
+
try {
|
|
28
|
+
return fs.statSync(abs).isFile();
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readJson(abs) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(fs.readFileSync(abs, "utf8"));
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isPackageManager(name) {
|
|
43
|
+
return PACKAGE_MANAGERS.includes(String(name || "").toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parsePackageManagerField(raw) {
|
|
47
|
+
if (raw && typeof raw === "object" && typeof raw.name === "string") {
|
|
48
|
+
raw = raw.name;
|
|
49
|
+
}
|
|
50
|
+
if (typeof raw !== "string") return null;
|
|
51
|
+
const name = raw.trim().split("@")[0].toLowerCase();
|
|
52
|
+
return isPackageManager(name) ? name : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function fieldFromPkg(pkg) {
|
|
56
|
+
if (!pkg || typeof pkg !== "object") return null;
|
|
57
|
+
const fromField = parsePackageManagerField(pkg.packageManager);
|
|
58
|
+
if (fromField) return fromField;
|
|
59
|
+
return parsePackageManagerField(pkg.devEngines?.packageManager);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function detectFromRepo(cwd) {
|
|
63
|
+
const lockfiles = [];
|
|
64
|
+
const managers = [];
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const add = (name, lock) => {
|
|
67
|
+
if (!isPackageManager(name)) return;
|
|
68
|
+
if (lock) lockfiles.push(lock);
|
|
69
|
+
if (seen.has(name)) return;
|
|
70
|
+
seen.add(name);
|
|
71
|
+
managers.push(name);
|
|
72
|
+
};
|
|
73
|
+
const pkg = readJson(path.join(cwd || "", "package.json"));
|
|
74
|
+
const fromField = fieldFromPkg(pkg);
|
|
75
|
+
if (fromField) add(fromField, null);
|
|
76
|
+
for (const [file, name] of LOCKFILES) {
|
|
77
|
+
if (isFile(path.join(cwd || "", file))) add(name, file);
|
|
78
|
+
}
|
|
79
|
+
return { managers, lockfiles };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function detectFromInvocation(env = process.env, { versions = process.versions } = {}) {
|
|
83
|
+
const ua = String(env?.npm_config_user_agent || "").trim();
|
|
84
|
+
if (ua) {
|
|
85
|
+
const first = ua.split(/[\s/]/)[0].toLowerCase();
|
|
86
|
+
if (isPackageManager(first)) return first;
|
|
87
|
+
}
|
|
88
|
+
const execPath = String(env?.npm_execpath || "").replace(/\\/g, "/");
|
|
89
|
+
const base = execPath.split("/").pop() || "";
|
|
90
|
+
const lower = base.toLowerCase();
|
|
91
|
+
for (const pm of PACKAGE_MANAGERS) {
|
|
92
|
+
if (lower === pm || lower.startsWith(`${pm}.`) || lower.startsWith(`${pm}-`)) return pm;
|
|
93
|
+
}
|
|
94
|
+
if (versions && versions.bun) return "bun";
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function detectPackageManager({
|
|
99
|
+
cwd = "",
|
|
100
|
+
env = process.env,
|
|
101
|
+
override,
|
|
102
|
+
versions = process.versions,
|
|
103
|
+
} = {}) {
|
|
104
|
+
if (override != null && override !== "") {
|
|
105
|
+
const name = String(override).toLowerCase();
|
|
106
|
+
if (!isPackageManager(name)) {
|
|
107
|
+
return { manager: null, source: null, error: "invalid", override: name };
|
|
108
|
+
}
|
|
109
|
+
return { manager: name, source: "flag" };
|
|
110
|
+
}
|
|
111
|
+
const repo = detectFromRepo(cwd);
|
|
112
|
+
if (repo.managers.length === 1) {
|
|
113
|
+
return { manager: repo.managers[0], source: "repo", lockfiles: repo.lockfiles };
|
|
114
|
+
}
|
|
115
|
+
const invoked = detectFromInvocation(env, { versions });
|
|
116
|
+
if (invoked) {
|
|
117
|
+
if (repo.managers.length === 0) {
|
|
118
|
+
return { manager: invoked, source: "invocation", lockfiles: repo.lockfiles };
|
|
119
|
+
}
|
|
120
|
+
if (repo.managers.includes(invoked)) {
|
|
121
|
+
return {
|
|
122
|
+
manager: invoked,
|
|
123
|
+
source: "invocation",
|
|
124
|
+
candidates: repo.managers,
|
|
125
|
+
lockfiles: repo.lockfiles,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
manager: null,
|
|
131
|
+
source: null,
|
|
132
|
+
candidates: repo.managers,
|
|
133
|
+
lockfiles: repo.lockfiles,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function installArgs(_pm) {
|
|
138
|
+
return ["install"];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function runScriptArgs(pm, script, extra = []) {
|
|
142
|
+
if (!extra.length) return ["run", script];
|
|
143
|
+
if (pm === "npm") return ["run", script, "--", ...extra];
|
|
144
|
+
return ["run", script, ...extra];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function runScriptLine(pm, script) {
|
|
148
|
+
if (pm) return `${pm} run ${script}`;
|
|
149
|
+
return `npm|yarn|pnpm|bun run ${script}`;
|
|
150
|
+
}
|