@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.
@@ -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
+ }
@@ -170,7 +170,7 @@ function main() {
170
170
  const data = readJsonValue();
171
171
  const updated = processHookPayload(data);
172
172
  if (!updated) return 0;
173
- if (updated.decision === "deny") {
173
+ if (updated.decision === "block") {
174
174
  process.stdout.write(JSON.stringify(updated));
175
175
  return 0;
176
176
  }
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStart hook: inject the code-only folder map as additionalContext.
4
+ * Logs { rows, tokens } to .tmp/grunt/sessions/<sid>/map.json for tuning.
5
+ * Fail-open: any error, no git, or an empty map → empty stdout, exit 0.
6
+ */
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { folderMap } from "./folder-map.mjs";
11
+
12
+ export const HEADER =
13
+ "Folder map (code folders only; `…` = more inside, run `node scripts/folder-map.mjs <dir>`):";
14
+
15
+ function readStdin() {
16
+ try {
17
+ const v = JSON.parse(fs.readFileSync(0, "utf8"));
18
+ return v && typeof v === "object" ? v : {};
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+
24
+ function workspaceRootOf(data) {
25
+ return (
26
+ process.env.GROK_WORKSPACE_ROOT ||
27
+ data.workspaceRoot ||
28
+ data.workspace_root ||
29
+ data.cwd ||
30
+ process.env.CLAUDE_PROJECT_DIR ||
31
+ process.cwd()
32
+ );
33
+ }
34
+
35
+ function logSize(root, data, out) {
36
+ const sid = String(data.session_id || data.sessionId || "").replace(/[^\w-]/g, "");
37
+ if (!sid) return;
38
+ const dir = path.join(root, ".tmp", "grunt", "sessions", sid);
39
+ fs.mkdirSync(dir, { recursive: true });
40
+ fs.writeFileSync(path.join(dir, "map.json"), `${JSON.stringify({ rows: out.rows, tokens: out.tokens })}\n`);
41
+ }
42
+
43
+ function main() {
44
+ try {
45
+ const data = readStdin();
46
+ const root = workspaceRootOf(data);
47
+ const out = folderMap({ root });
48
+ if (!out || !out.text) return 0;
49
+ try {
50
+ logSize(root, data, out);
51
+ } catch {
52
+ // Logging is best-effort; the map still ships.
53
+ }
54
+ process.stdout.write(
55
+ JSON.stringify({
56
+ hookSpecificOutput: {
57
+ hookEventName: "SessionStart",
58
+ additionalContext: `${HEADER}\n${out.text}`,
59
+ },
60
+ }),
61
+ );
62
+ } catch {
63
+ // Fail open.
64
+ }
65
+ return 0;
66
+ }
67
+
68
+ const thisFile = fileURLToPath(import.meta.url);
69
+ const invoked = process.argv[1] ? path.resolve(process.argv[1]) : "";
70
+ if (invoked === thisFile || import.meta.url === pathToFileURL(invoked).href) {
71
+ process.exit(main());
72
+ }