token-goat 2.6.35 → 2.6.36
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/README.md +4 -2
- package/SECURITY.md +3 -1
- package/dist/{token-goat-chunk-DRAIYVUI.mjs → token-goat-chunk-65BKISIS.mjs} +3 -3
- package/dist/{token-goat-chunk-UIZVX3HN.mjs → token-goat-chunk-CNDOJ3ZP.mjs} +2 -2
- package/dist/{token-goat-chunk-Y2NH6DGZ.mjs → token-goat-chunk-FRTBMRP7.mjs} +5 -5
- package/dist/{token-goat-chunk-D5IZEGN2.mjs → token-goat-chunk-IYTVE6KN.mjs} +13 -8
- package/dist/{token-goat-chunk-HW4VUKJ5.mjs → token-goat-chunk-KYFJC37X.mjs} +14 -13
- package/dist/{token-goat-chunk-YF67EWRH.mjs → token-goat-chunk-LN6OUHTV.mjs} +5 -5
- package/dist/{token-goat-hook-chunk-NIPXIAPA.mjs → token-goat-chunk-MGOUYAA2.mjs} +1 -1
- package/dist/{token-goat-chunk-I2KCEGIP.mjs → token-goat-chunk-UFOVM7ZN.mjs} +85 -45
- package/dist/{token-goat-chunk-K7W3P2TJ.mjs → token-goat-chunk-V465YKOR.mjs} +61 -17
- package/dist/{token-goat-chunk-SIHYQCTM.mjs → token-goat-chunk-VYMGEVZS.mjs} +16 -4
- package/dist/{token-goat-chunk-DQIT5JIF.mjs → token-goat-hook-chunk-3ZDBWJDF.mjs} +1 -1
- package/dist/{token-goat-hook-chunk-CRBMPT74.mjs → token-goat-hook-chunk-5UH54CW6.mjs} +16 -4
- package/dist/{token-goat-hook-chunk-VU6VGZBM.mjs → token-goat-hook-chunk-6ODM3MP7.mjs} +3 -3
- package/dist/{token-goat-hook-chunk-XM4W3WCO.mjs → token-goat-hook-chunk-A77A26A7.mjs} +85 -45
- package/dist/{token-goat-hook-chunk-4POU6OHK.mjs → token-goat-hook-chunk-BDR6C6IE.mjs} +5 -5
- package/dist/{token-goat-hook-chunk-3AAH72SO.mjs → token-goat-hook-chunk-C6GIABOX.mjs} +14 -13
- package/dist/{token-goat-hook-chunk-D6UJEFHX.mjs → token-goat-hook-chunk-E257IGSN.mjs} +2 -2
- package/dist/{token-goat-hook-chunk-NXVT4F3S.mjs → token-goat-hook-chunk-MW5HPEGD.mjs} +13 -8
- package/dist/{token-goat-hook-chunk-J4HKWUWZ.mjs → token-goat-hook-chunk-QSCYNJ2B.mjs} +5 -5
- package/dist/{token-goat-hook-chunk-2O7P4Z6Q.mjs → token-goat-hook-chunk-Y2WHX2P3.mjs} +61 -17
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +10 -9
- package/scripts/install-git-hooks.mjs +55 -0
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
function resolveVersion() {
|
|
10
10
|
if (true) {
|
|
11
|
-
return "2.6.
|
|
11
|
+
return "2.6.36";
|
|
12
12
|
}
|
|
13
13
|
const require2 = createRequire(import.meta.url);
|
|
14
14
|
const pkg = require2("../package.json");
|
|
@@ -742,6 +742,9 @@ function countContentLines(content) {
|
|
|
742
742
|
const n = content.split("\n").length;
|
|
743
743
|
return content.endsWith("\n") ? n - 1 : n;
|
|
744
744
|
}
|
|
745
|
+
function pushAll(target, items) {
|
|
746
|
+
for (const item of items) target.push(item);
|
|
747
|
+
}
|
|
745
748
|
function escapeRegExp(s) {
|
|
746
749
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
747
750
|
}
|
|
@@ -2436,7 +2439,6 @@ var ENV_KEYS2 = [
|
|
|
2436
2439
|
"TOKEN_GOAT_OVERFLOW_MAX_TOKENS",
|
|
2437
2440
|
"TOKEN_GOAT_HINT_JSON_SIDECAR",
|
|
2438
2441
|
"TOKEN_GOAT_LARGE_READ_BYTES",
|
|
2439
|
-
"TOKEN_GOAT_CURATOR",
|
|
2440
2442
|
"TOKEN_GOAT_HINT_BUDGET",
|
|
2441
2443
|
"TOKEN_GOAT_HOOK_WATCHDOG_MS",
|
|
2442
2444
|
"TOKEN_GOAT_WEB_COMPRESS",
|
|
@@ -2497,8 +2499,19 @@ var _lastProjectConfigParseError = null;
|
|
|
2497
2499
|
function getLastProjectConfigParseError() {
|
|
2498
2500
|
return _lastProjectConfigParseError;
|
|
2499
2501
|
}
|
|
2500
|
-
var PROJECT_LOCKED_SECTIONS = [
|
|
2501
|
-
|
|
2502
|
+
var PROJECT_LOCKED_SECTIONS = [
|
|
2503
|
+
"injection",
|
|
2504
|
+
"webfetch",
|
|
2505
|
+
"gdrive",
|
|
2506
|
+
"mcp",
|
|
2507
|
+
"network",
|
|
2508
|
+
"screenshot"
|
|
2509
|
+
];
|
|
2510
|
+
var PROJECT_LOCKED_KEYS = [
|
|
2511
|
+
"image_shrink.max_image_pixels",
|
|
2512
|
+
"indexing.cross_project_symbols",
|
|
2513
|
+
"worker.blocked_roots"
|
|
2514
|
+
];
|
|
2502
2515
|
var _lastProjectConfigLockedKeys = [];
|
|
2503
2516
|
function stripLockedProjectKeys(projectRaw) {
|
|
2504
2517
|
const cleaned = {};
|
|
@@ -2629,10 +2642,12 @@ function resolveConfigKeyLayer(key, effectiveValue, cfg, projectInfo) {
|
|
|
2629
2642
|
if (rawValueEquals(rawValue, effectiveValue)) return { layer: "project", path: projectInfo.path };
|
|
2630
2643
|
return { layer: "project-invalid", path: projectInfo.path, rawValue, effectiveValue, reason: rejectionReason(key, rawValue, effectiveValue, cfg) };
|
|
2631
2644
|
}
|
|
2645
|
+
function readConfigSource(p) {
|
|
2646
|
+
return decodeSource(fs5.readFileSync(p));
|
|
2647
|
+
}
|
|
2632
2648
|
function readConfigToml(p) {
|
|
2633
2649
|
try {
|
|
2634
|
-
|
|
2635
|
-
return { raw: parse(text), parseError: null };
|
|
2650
|
+
return { raw: parse(readConfigSource(p)), parseError: null };
|
|
2636
2651
|
} catch (e) {
|
|
2637
2652
|
const code = e.code;
|
|
2638
2653
|
if (code === "ENOENT") return { raw: {}, parseError: null };
|
|
@@ -2641,7 +2656,7 @@ function readConfigToml(p) {
|
|
|
2641
2656
|
}
|
|
2642
2657
|
function readConfigText(p) {
|
|
2643
2658
|
try {
|
|
2644
|
-
return { text:
|
|
2659
|
+
return { text: readConfigSource(p), readError: null };
|
|
2645
2660
|
} catch (e) {
|
|
2646
2661
|
const code = e.code;
|
|
2647
2662
|
if (code === "ENOENT") return { text: null, readError: null };
|
|
@@ -2683,11 +2698,11 @@ function isAutoTriggerMultiplierExplicit() {
|
|
|
2683
2698
|
return ca_raw["auto_trigger_multiplier"] !== void 0;
|
|
2684
2699
|
};
|
|
2685
2700
|
try {
|
|
2686
|
-
if (setsMultiplier(
|
|
2701
|
+
if (setsMultiplier(readConfigSource(configPath()))) return true;
|
|
2687
2702
|
} catch {
|
|
2688
2703
|
}
|
|
2689
2704
|
try {
|
|
2690
|
-
return setsMultiplier(
|
|
2705
|
+
return setsMultiplier(readConfigSource(projectConfigPath(resolveConfigProjectRoot())));
|
|
2691
2706
|
} catch {
|
|
2692
2707
|
return false;
|
|
2693
2708
|
}
|
|
@@ -4166,6 +4181,7 @@ function propagateEndLinesToSymbols(symbols, sections) {
|
|
|
4166
4181
|
}
|
|
4167
4182
|
function findMatchingBraceEndLine(content, openBraceIndex, totalLines, lineIndex, lineCommentPrefix, opts) {
|
|
4168
4183
|
const block = opts?.blockComment;
|
|
4184
|
+
const backtick = opts?.backtickQuote === true;
|
|
4169
4185
|
let depth = 0;
|
|
4170
4186
|
let quote = null;
|
|
4171
4187
|
for (let i = openBraceIndex; i < content.length; i++) {
|
|
@@ -4187,7 +4203,7 @@ function findMatchingBraceEndLine(content, openBraceIndex, totalLines, lineIndex
|
|
|
4187
4203
|
while (i < content.length && content[i] !== "\n") i++;
|
|
4188
4204
|
continue;
|
|
4189
4205
|
}
|
|
4190
|
-
if (ch === '"' || ch === "'") {
|
|
4206
|
+
if (ch === '"' || ch === "'" || backtick && ch === "`") {
|
|
4191
4207
|
quote = ch;
|
|
4192
4208
|
continue;
|
|
4193
4209
|
}
|
|
@@ -4710,7 +4726,7 @@ function purgeDotenvEmbeddings(conn) {
|
|
|
4710
4726
|
var MIGRATIONS = {
|
|
4711
4727
|
// v1 -> v2: adds files.embed_sha, tracked separately from files.sha so embedding freshness can be gated independently of parse freshness (see makeIndexer in worker.ts). A pre-existing v1 database's `files` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has the column from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, so a genuine ALTER TABLE failure is never silently lost.
|
|
4712
4728
|
1: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN embed_sha TEXT"),
|
|
4713
|
-
// v2 -> v3: adds files.retry_count, a durable per-path counter for consecutive transient-read-failure requeues (see MAX_TRANSIENT_RETRIES / requeueDirtyPath /
|
|
4729
|
+
// v2 -> v3: adds files.retry_count, a durable per-path counter for consecutive transient-read-failure requeues (see MAX_TRANSIENT_RETRIES / requeueDirtyPath / clearRetryCount in worker.ts). Previously this counter lived only in an in-memory Map inside worker.ts, which meant the retry-count reset -- run at the time in the short-lived hook CLI process -- could never actually reach the long-lived detached daemon process's own copy of that Map: they are different Node processes with no shared memory, so the reset was a silent no-op in the real deployed topology. Persisting the counter in `files` makes it visible to both processes via the one thing they do share: the index DB. Same swallow-duplicate-column pattern as v1 -> v2 above.
|
|
4714
4730
|
2: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0"),
|
|
4715
4731
|
// v8 -> v9: adds symbols.parent (see SCHEMA_VERSION comment above for why). A pre-existing v8 database's `symbols` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has the column from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, same pattern as v1 -> v2 / v2 -> v3 above.
|
|
4716
4732
|
8: (conn) => alterTableIdempotent(conn, "ALTER TABLE symbols ADD COLUMN parent TEXT NOT NULL DEFAULT ''"),
|
|
@@ -4730,13 +4746,32 @@ function runMigrations(conn, fromVersion, toVersion) {
|
|
|
4730
4746
|
MIGRATIONS[v]?.(conn);
|
|
4731
4747
|
}
|
|
4732
4748
|
}
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4749
|
+
var WAL_SWITCH_DEADLINE_MS = 15e3;
|
|
4750
|
+
function enableWalWithRetry(conn, budgetMs = WAL_SWITCH_DEADLINE_MS) {
|
|
4751
|
+
const deadline = Date.now() + budgetMs;
|
|
4752
|
+
let lastError;
|
|
4753
|
+
for (; ; ) {
|
|
4754
|
+
try {
|
|
4755
|
+
const mode = conn.pragma("journal_mode = WAL", { simple: true });
|
|
4756
|
+
if (String(mode).toLowerCase() === "wal") return;
|
|
4757
|
+
lastError = new Error(`got: ${String(mode)}`);
|
|
4758
|
+
} catch (e) {
|
|
4759
|
+
lastError = e;
|
|
4760
|
+
}
|
|
4761
|
+
try {
|
|
4762
|
+
if (String(conn.pragma("journal_mode", { simple: true })).toLowerCase() === "wal") return;
|
|
4763
|
+
} catch {
|
|
4764
|
+
}
|
|
4765
|
+
if (Date.now() >= deadline) {
|
|
4766
|
+
throw new Error(`db: failed to enable WAL mode (${lastError instanceof Error ? lastError.message : String(lastError)})`);
|
|
4767
|
+
}
|
|
4768
|
+
sleepSync(25);
|
|
4737
4769
|
}
|
|
4738
|
-
|
|
4770
|
+
}
|
|
4771
|
+
function initConnection(conn) {
|
|
4739
4772
|
conn.pragma("busy_timeout = 15000");
|
|
4773
|
+
enableWalWithRetry(conn);
|
|
4774
|
+
conn.pragma("synchronous = NORMAL");
|
|
4740
4775
|
conn.function(
|
|
4741
4776
|
"TG_LOWER",
|
|
4742
4777
|
{ deterministic: true },
|
|
@@ -6093,7 +6128,14 @@ var SECRET_PATTERNS = [
|
|
|
6093
6128
|
// realistic header ('{"alg":' or similar) always starts with "eyJ", so that's the practical
|
|
6094
6129
|
// anchor here -- each of the three dot-separated segments requires a minimum length to avoid
|
|
6095
6130
|
// matching a short, coincidentally dotted token.
|
|
6096
|
-
|
|
6131
|
+
//
|
|
6132
|
+
// The trailing `={0,2}` on each segment is what makes a padded token match. Base64url as the JWT
|
|
6133
|
+
// spec defines it drops the `=` padding, but producers that reach for a plain base64 encoder
|
|
6134
|
+
// emit it anyway, and a `=` in the header or payload segment used to defeat the match outright:
|
|
6135
|
+
// not a partial redaction, but none at all, so the entire token was printed. `=` cannot appear
|
|
6136
|
+
// anywhere except the end of a segment, since it is not one of the characters the segment body
|
|
6137
|
+
// allows, so accepting it here cannot widen the match onto anything else.
|
|
6138
|
+
["jwt", /eyJ[A-Za-z0-9_-]{10,}={0,2}\.[A-Za-z0-9_-]{10,}={0,2}\.[A-Za-z0-9_-]{10,}={0,2}/g],
|
|
6097
6139
|
["npm_token", /npm_[A-Za-z0-9]{36}/g],
|
|
6098
6140
|
// rk_live_ (restricted keys) share the sk_live_/sk_test_ secret-key shape and risk level, so
|
|
6099
6141
|
// one pattern covers all three rather than adding a near-duplicate entry.
|
|
@@ -6284,6 +6326,7 @@ export {
|
|
|
6284
6326
|
countNoun,
|
|
6285
6327
|
excludeTestsHiddenNote,
|
|
6286
6328
|
countContentLines,
|
|
6329
|
+
pushAll,
|
|
6287
6330
|
escapeRegExp,
|
|
6288
6331
|
windowsCmdQuoteArg,
|
|
6289
6332
|
redactUrlQuery,
|
|
@@ -6324,6 +6367,7 @@ export {
|
|
|
6324
6367
|
lastProjectConfigLockedKeys,
|
|
6325
6368
|
getProjectConfigInfo,
|
|
6326
6369
|
resolveConfigKeyLayer,
|
|
6370
|
+
readConfigSource,
|
|
6327
6371
|
isAutoTriggerMultiplierExplicit,
|
|
6328
6372
|
loadConfig,
|
|
6329
6373
|
invalidateConfigCache,
|
package/dist/token-goat-hook.mjs
CHANGED
|
@@ -2,11 +2,11 @@ import { createRequire as __cjsRequire } from 'node:module';
|
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
relayInProcess
|
|
5
|
-
} from "./token-goat-hook-chunk-
|
|
6
|
-
import "./token-goat-hook-chunk-
|
|
7
|
-
import "./token-goat-hook-chunk-
|
|
8
|
-
import "./token-goat-hook-chunk-
|
|
9
|
-
import "./token-goat-hook-chunk-
|
|
5
|
+
} from "./token-goat-hook-chunk-BDR6C6IE.mjs";
|
|
6
|
+
import "./token-goat-hook-chunk-3ZDBWJDF.mjs";
|
|
7
|
+
import "./token-goat-hook-chunk-5UH54CW6.mjs";
|
|
8
|
+
import "./token-goat-hook-chunk-A77A26A7.mjs";
|
|
9
|
+
import "./token-goat-hook-chunk-Y2WHX2P3.mjs";
|
|
10
10
|
import "./token-goat-hook-chunk-BUOCULAM.mjs";
|
|
11
11
|
import "./token-goat-hook-chunk-RFRLWOQH.mjs";
|
|
12
12
|
export {
|
package/dist/token-goat.core.mjs
CHANGED
|
@@ -2,13 +2,13 @@ import { createRequire as __cjsRequire } from 'node:module';
|
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
run
|
|
5
|
-
} from "./token-goat-chunk-
|
|
6
|
-
import "./token-goat-chunk-
|
|
7
|
-
import "./token-goat-chunk-
|
|
8
|
-
import "./token-goat-chunk-
|
|
5
|
+
} from "./token-goat-chunk-KYFJC37X.mjs";
|
|
6
|
+
import "./token-goat-chunk-IYTVE6KN.mjs";
|
|
7
|
+
import "./token-goat-chunk-VYMGEVZS.mjs";
|
|
8
|
+
import "./token-goat-chunk-UFOVM7ZN.mjs";
|
|
9
9
|
import {
|
|
10
10
|
installEpipeGuard
|
|
11
|
-
} from "./token-goat-chunk-
|
|
11
|
+
} from "./token-goat-chunk-V465YKOR.mjs";
|
|
12
12
|
import "./token-goat-chunk-AO2QD2AG.mjs";
|
|
13
13
|
import "./token-goat-chunk-AEX54RUZ.mjs";
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-goat",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.36",
|
|
4
4
|
"description": "Surgical token-reduction companion for Claude Code and other AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/token-goat.mjs",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"dist/",
|
|
52
52
|
"README.md",
|
|
53
53
|
"SECURITY.md",
|
|
54
|
-
"LICENSE"
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"scripts/install-git-hooks.mjs"
|
|
55
56
|
],
|
|
56
57
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
57
58
|
"engines": {
|
|
@@ -59,26 +60,26 @@
|
|
|
59
60
|
},
|
|
60
61
|
"dependencies": {
|
|
61
62
|
"better-sqlite3": "^12.11.1",
|
|
62
|
-
"
|
|
63
|
-
"csv-parse": "^7.0.2",
|
|
64
|
-
"js-yaml": "^5.2.1",
|
|
65
|
-
"jsonc-parser": "^3.3.1",
|
|
66
|
-
"smol-toml": "^1.8.0",
|
|
67
|
-
"zod": "^4.4.3"
|
|
63
|
+
"jsonc-parser": "^3.3.1"
|
|
68
64
|
},
|
|
69
65
|
"devDependencies": {
|
|
70
66
|
"@eslint/js": "^10.0.1",
|
|
71
67
|
"@types/better-sqlite3": "^9.6.0",
|
|
72
68
|
"@types/node": "^26.2.0",
|
|
69
|
+
"commander": "^15.0.0",
|
|
70
|
+
"csv-parse": "^7.0.2",
|
|
73
71
|
"esbuild": "^0.28.2",
|
|
74
72
|
"eslint": "^10.8.1",
|
|
75
73
|
"exceljs": "^4.4.0",
|
|
76
74
|
"html-to-text": "^10.0.1",
|
|
75
|
+
"js-yaml": "^5.2.1",
|
|
77
76
|
"lefthook": "^2.1.10",
|
|
77
|
+
"smol-toml": "^1.8.0",
|
|
78
78
|
"tsx": "^4.23.12",
|
|
79
79
|
"typescript": "^6.0.3",
|
|
80
80
|
"typescript-eslint": "^8.67.0",
|
|
81
|
-
"vitest": "^4.1.11"
|
|
81
|
+
"vitest": "^4.1.11",
|
|
82
|
+
"zod": "^4.4.3"
|
|
82
83
|
},
|
|
83
84
|
"optionalDependencies": {
|
|
84
85
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `prepare` hook: wire up lefthook's git hooks for a development checkout.
|
|
3
|
+
*
|
|
4
|
+
* Runs `lefthook install` when lefthook is actually present, and skips quietly when it isn't.
|
|
5
|
+
*
|
|
6
|
+
* The skip matters because npm runs `prepare` for more than the dev-checkout case it was added
|
|
7
|
+
* for. `npm install -g .` runs it in a context without this project's node_modules/.bin on PATH,
|
|
8
|
+
* so a bare `lefthook install` dies with "'lefthook' is not recognized" and fails the global
|
|
9
|
+
* install that CLAUDE.md prescribes as the dogfood step for every CLI/hook change. Installing
|
|
10
|
+
* token-goat as a dependency has the same shape: no devDependencies, and no git repo to hook.
|
|
11
|
+
*
|
|
12
|
+
* Skipping is deliberately conditioned on lefthook being *absent*, not on the command failing.
|
|
13
|
+
* A developer whose `lefthook install` genuinely breaks still gets a hard error, because these
|
|
14
|
+
* hooks were decorative once already -- configured in lefthook.yml but never installed, so every
|
|
15
|
+
* guard in it silently did nothing -- and swallowing failures here is how that recurs.
|
|
16
|
+
*/
|
|
17
|
+
import { spawnSync } from 'node:child_process'
|
|
18
|
+
import { fileURLToPath } from 'node:url'
|
|
19
|
+
import path from 'node:path'
|
|
20
|
+
import fs from 'node:fs'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Absolute path to the lefthook CLI shim, or null when lefthook isn't installed here.
|
|
24
|
+
*
|
|
25
|
+
* Looks for npm's own `.bin` shim rather than resolving the package, because the shim is what
|
|
26
|
+
* npm creates for a devDependency and it is the exact thing missing in the contexts we skip.
|
|
27
|
+
* `.cmd` first: on Windows the extensionless file is a shell script Node can't spawn directly.
|
|
28
|
+
*/
|
|
29
|
+
function findLefthookBin(startDir) {
|
|
30
|
+
const binDir = path.join(startDir, 'node_modules', '.bin')
|
|
31
|
+
for (const name of ['lefthook.cmd', 'lefthook']) {
|
|
32
|
+
const candidate = path.join(binDir, name)
|
|
33
|
+
if (fs.existsSync(candidate)) return candidate
|
|
34
|
+
}
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
39
|
+
const bin = findLefthookBin(projectRoot)
|
|
40
|
+
|
|
41
|
+
if (bin === null) {
|
|
42
|
+
// Not a dev checkout with lefthook installed: a global install, a published-package install, or
|
|
43
|
+
// a pack/prepare temp dir. Nothing to wire up and nothing wrong -- exit 0 so the install works.
|
|
44
|
+
process.exit(0)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// npm's Windows shim is a .cmd batch file, which Node refuses to spawn without a shell. Quote the
|
|
48
|
+
// path in that case, since `shell: true` re-parses the command string and the path can contain
|
|
49
|
+
// spaces (a checkout under "C:\Program Files", a user profile with a space in the name).
|
|
50
|
+
const needsShell = /\.(cmd|bat)$/i.test(bin)
|
|
51
|
+
const result = spawnSync(needsShell ? `"${bin}"` : bin, ['install'], {
|
|
52
|
+
stdio: 'inherit',
|
|
53
|
+
shell: needsShell,
|
|
54
|
+
})
|
|
55
|
+
process.exit(result.status ?? 1)
|