@primitive.ai/prim 0.1.0-alpha.93 → 0.1.0-alpha.94
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 +50 -8
- package/SKILL.md +5 -2
- package/dist/{chunk-X64HTHUF.js → chunk-AXQQMX2U.js} +200 -8
- package/dist/chunk-FXF4JWK2.js +162 -0
- package/dist/{chunk-C3VAA5CG.js → chunk-IMK4JKTA.js} +1 -1
- package/dist/{chunk-36TWVNBE.js → chunk-O7YAW7G6.js} +1 -1
- package/dist/{chunk-DCJN6LZH.js → chunk-WUXFLE7X.js} +684 -365
- package/dist/hook-runtime/hooks/post-commit.js +154 -38
- package/dist/hook-runtime/hooks/post-tool-use.js +55 -5
- package/dist/hook-runtime/hooks/pre-tool-use.js +55 -5
- package/dist/hook-runtime/hooks/prim-hook.js +55 -5
- package/dist/hook-runtime/hooks/session-end.js +53 -5
- package/dist/hook-runtime/hooks/session-start.js +843 -277
- package/dist/hook-runtime/index.js +2709 -1880
- package/dist/hooks/post-commit.js +4 -0
- package/dist/hooks/post-tool-use.js +3 -3
- package/dist/hooks/pre-tool-use.js +3 -3
- package/dist/hooks/prim-hook.js +3 -3
- package/dist/hooks/session-end.js +2 -2
- package/dist/hooks/session-start.js +28 -14
- package/dist/index.js +553 -339
- package/package.json +1 -1
|
@@ -11,17 +11,25 @@ import {
|
|
|
11
11
|
getClient
|
|
12
12
|
} from "./chunk-G2VMA2QO.js";
|
|
13
13
|
import {
|
|
14
|
+
GIT_HOOK_CONTRACT_VERSION,
|
|
14
15
|
binFile,
|
|
16
|
+
blockMarkers,
|
|
17
|
+
canonicalHookBlock,
|
|
15
18
|
compareSemver,
|
|
16
19
|
cursorConfigDirectory,
|
|
20
|
+
hookBlockContractVersion,
|
|
21
|
+
inspectGitHookEntrypoint,
|
|
22
|
+
managedHookBlock,
|
|
23
|
+
pinnedHookCommand,
|
|
17
24
|
pinnedNpxCommand
|
|
18
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-AXQQMX2U.js";
|
|
19
26
|
import {
|
|
20
27
|
gitToplevel,
|
|
21
28
|
githubRepositoryFullName
|
|
22
29
|
} from "./chunk-H4UNYFUC.js";
|
|
23
30
|
import {
|
|
24
31
|
atomicWriteFile,
|
|
32
|
+
primConfigDirectory,
|
|
25
33
|
withFileLock
|
|
26
34
|
} from "./chunk-2GP5UFV7.js";
|
|
27
35
|
|
|
@@ -560,8 +568,8 @@ function applyBlock(existing, block, eol) {
|
|
|
560
568
|
return existing.slice(0, b) + block + existing.slice(e + SKILL_END.length);
|
|
561
569
|
}
|
|
562
570
|
if (existing.length === 0) return `${block}${eol}`;
|
|
563
|
-
const
|
|
564
|
-
return `${existing}${
|
|
571
|
+
const sep3 = existing.endsWith(eol) ? "" : eol;
|
|
572
|
+
return `${existing}${sep3}${block}${eol}`;
|
|
565
573
|
}
|
|
566
574
|
function removeBlock(existing) {
|
|
567
575
|
const b = existing.indexOf(SKILL_BEGIN);
|
|
@@ -701,60 +709,28 @@ function registerSkillCommands(program) {
|
|
|
701
709
|
});
|
|
702
710
|
}
|
|
703
711
|
|
|
704
|
-
// src/lib/
|
|
705
|
-
import {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
var PRIM_POST_REWRITE_BLOCK_END = "# <<< prim post-rewrite hook <<<";
|
|
727
|
-
var PRIM_POST_REWRITE_CREATED_MARK = "prim-created-post-rewrite-hook";
|
|
728
|
-
var LEGACY_PRIM_CREATED_MARK = "prim-created-hook";
|
|
729
|
-
var LEGACY_PRIM_OWNED_HEADER = "# prim post-commit hook \u2014 installed by: prim hooks install (prim-managed-hook)";
|
|
730
|
-
var LEGACY_GLOBAL_OWNED_HEADER = "# prim global post-commit hook (core.hooksPath) \u2014 managed by prim; do not edit.";
|
|
731
|
-
var MAX_HOOK_BYTES = 1048576;
|
|
732
|
-
var GIT_TIMEOUT_MS = 1e3;
|
|
733
|
-
var SUPPORTED_SHEBANGS = /* @__PURE__ */ new Set([
|
|
734
|
-
"#!/bin/sh",
|
|
735
|
-
"#!/usr/bin/env sh",
|
|
736
|
-
"#!/bin/bash",
|
|
737
|
-
"#!/usr/bin/env bash",
|
|
738
|
-
"#!/bin/zsh",
|
|
739
|
-
"#!/usr/bin/env zsh"
|
|
740
|
-
]);
|
|
741
|
-
var HUSKY_V9_DISPATCHERS = /* @__PURE__ */ new Set([
|
|
742
|
-
'#!/usr/bin/env sh\n. "${0%/*}/h"',
|
|
743
|
-
'#!/usr/bin/env sh\n. "$(dirname "$0")/h"'
|
|
744
|
-
]);
|
|
745
|
-
var HUSKY_DIRECT_DISPATCHER = '#!/bin/sh\nexec sh "$(dirname "$0")/../post-commit"\n';
|
|
746
|
-
var HUSKY_V9_RUNTIME_HASHES = /* @__PURE__ */ new Set([
|
|
747
|
-
"d6aaa6f3f7c11008c525e649161df4a93152f1e55d0887fa1d7b8d622735d380",
|
|
748
|
-
"b02cb91cab7125a9e906be723a1b85297fc435a8307d429ae845546b4ee0ecde",
|
|
749
|
-
"9db5d129c4eb822a773157d98cd8fb8f1b156b85e3193117fefe527ca6d45e91",
|
|
750
|
-
"3b2cb21335e544b4ebce658ca47af4a69d2c77026cc4d3f8b2227c34d062b207",
|
|
751
|
-
"548990e1c11285694096184993d907b258d41235461a7ca2551e3b53ff9d3a38",
|
|
752
|
-
"c029a943acea5d6e2ddbaa271a1fb22c2da55d56b5c6fb59ca804ac9a4018708",
|
|
753
|
-
"ae3413a8fe2b39372de48bdc9691f42055f089c0c7529834b2fb07a131585b6a",
|
|
754
|
-
"70200b200ca709b0622784f93839a5b2872333a917a09afddefd7dc2d8cdc680"
|
|
755
|
-
]);
|
|
756
|
-
function currentPostCommitBlock() {
|
|
757
|
-
return `${PRIM_POST_COMMIT_BLOCK_START}
|
|
712
|
+
// src/lib/git-hook-legacy.ts
|
|
713
|
+
import { isAbsolute as isAbsolute2 } from "path";
|
|
714
|
+
var PRIM_MANAGED_MARK = "prim-managed-hook";
|
|
715
|
+
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
716
|
+
var POST_COMMIT = { hookName: "post-commit", binName: "prim-post-commit" };
|
|
717
|
+
var POST_REWRITE = { hookName: "post-rewrite", binName: "prim-post-rewrite" };
|
|
718
|
+
function blockMarkers2(spec) {
|
|
719
|
+
return {
|
|
720
|
+
start: `# >>> prim ${spec.hookName} hook >>>`,
|
|
721
|
+
end: `# <<< prim ${spec.hookName} hook <<<`
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
function hookShim(binName) {
|
|
725
|
+
return `{ ${pinnedHookCommand(binName)}; } || true`;
|
|
726
|
+
}
|
|
727
|
+
function gatedShim(binName) {
|
|
728
|
+
return `if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
729
|
+
${hookShim(binName)}
|
|
730
|
+
fi`;
|
|
731
|
+
}
|
|
732
|
+
function legacyPostCommitBlock() {
|
|
733
|
+
return `# >>> prim post-commit hook >>>
|
|
758
734
|
if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
759
735
|
prim_commit_sha=$(git rev-parse --verify HEAD 2>/dev/null) || prim_commit_sha=
|
|
760
736
|
case "$prim_commit_sha" in *[!0-9a-f]*|"") prim_commit_sha= ;; esac
|
|
@@ -783,13 +759,10 @@ if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
|
783
759
|
fi
|
|
784
760
|
unset prim_commit_sha
|
|
785
761
|
fi
|
|
786
|
-
|
|
762
|
+
# <<< prim post-commit hook <<<`;
|
|
787
763
|
}
|
|
788
|
-
function
|
|
789
|
-
return
|
|
790
|
-
}
|
|
791
|
-
function postRewriteHookBlock() {
|
|
792
|
-
return `${PRIM_POST_REWRITE_BLOCK_START}
|
|
764
|
+
function legacyPostRewriteBlock() {
|
|
765
|
+
return `# >>> prim post-rewrite hook >>>
|
|
793
766
|
if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
794
767
|
case "$1" in amend|rebase) prim_rewrite_source="$1" ;; *) prim_rewrite_source= ;; esac
|
|
795
768
|
if [ -n "$prim_rewrite_source" ]; then
|
|
@@ -828,22 +801,227 @@ if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
|
828
801
|
fi
|
|
829
802
|
unset prim_rewrite_source
|
|
830
803
|
fi
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
hookName: "
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
804
|
+
# <<< prim post-rewrite hook <<<`;
|
|
805
|
+
}
|
|
806
|
+
function legacyGlobalHookScript(spec) {
|
|
807
|
+
const chainExit = spec.hookName === PRE_COMMIT.hookName ? "|| exit $?" : "|| true";
|
|
808
|
+
const managedBlock = spec.hookName === POST_COMMIT.hookName ? legacyPostCommitBlock() : spec.hookName === POST_REWRITE.hookName ? legacyPostRewriteBlock() : void 0;
|
|
809
|
+
const invocation = managedBlock ?? gatedShim(spec.binName);
|
|
810
|
+
const managedRepoGuard = managedBlock ? ` && ! grep -Fq '${blockMarkers2(spec).start.slice(0, -3)}'">>>" "$repo_hook" 2>/dev/null` : "";
|
|
811
|
+
const beforeComments = managedBlock ? `${invocation}
|
|
812
|
+
` : "";
|
|
813
|
+
const afterComments = managedBlock ? "" : `${invocation}
|
|
814
|
+
`;
|
|
815
|
+
return `#!/bin/sh
|
|
816
|
+
${beforeComments}# prim global ${spec.hookName} hook (core.hooksPath) \u2014 managed by prim; do not edit.
|
|
817
|
+
# Install/uninstall: prim hooks install|uninstall --scope user
|
|
818
|
+
# Runs prim only where activated \u2014 'prim enable' (this repo) or
|
|
819
|
+
# 'git config --global prim.active true' (every repo). Chains to the repo's own
|
|
820
|
+
# hook regardless, so inactive repos are unaffected.
|
|
821
|
+
${afterComments}common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || exit 0
|
|
822
|
+
repo_hook="$common_dir/hooks/${spec.hookName}"
|
|
823
|
+
if [ -x "$repo_hook" ] && ! grep -q '${PRIM_MANAGED_MARK}' "$repo_hook" 2>/dev/null${managedRepoGuard}; then
|
|
824
|
+
"$repo_hook" "$@" ${chainExit}
|
|
825
|
+
fi
|
|
826
|
+
exit 0
|
|
827
|
+
`;
|
|
828
|
+
}
|
|
829
|
+
var PRE_COMMIT_GATE = 'if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then';
|
|
830
|
+
function preCommitLadder(fallback, failSoft) {
|
|
831
|
+
const soft = failSoft ? " || true" : "";
|
|
832
|
+
return `if command -v prim-pre-commit >/dev/null 2>&1; then
|
|
833
|
+
prim-pre-commit${soft}
|
|
834
|
+
elif [ -f "./node_modules/.bin/prim-pre-commit" ]; then
|
|
835
|
+
./node_modules/.bin/prim-pre-commit${soft}
|
|
836
|
+
else
|
|
837
|
+
${fallback} 2>/dev/null || true
|
|
838
|
+
fi`;
|
|
839
|
+
}
|
|
840
|
+
var LADDER_ALPHA_2 = preCommitLadder("npx --yes @primitive.ai/prim pre-commit-hook", false);
|
|
841
|
+
var LADDER_ALPHA_9 = preCommitLadder("npx --yes -p @primitive.ai/prim prim-pre-commit", false);
|
|
842
|
+
var LADDER_ALPHA_35 = preCommitLadder("npx --yes -p @primitive.ai/prim prim-pre-commit", true);
|
|
843
|
+
var PINNED_NPX_PRE_COMMIT = String.raw`npx --yes(?: --ignore-scripts)? -p @primitive\.ai/prim@[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)? prim-pre-commit`;
|
|
844
|
+
var PINNED_PRE_COMMIT_RE = new RegExp(
|
|
845
|
+
String.raw`^\{ (?:${PINNED_NPX_PRE_COMMIT}|if \[ -x (?<node>'.*') \] && \[ -f (?<entry>'.*') \]; then \k<node> \k<entry>; else ${PINNED_NPX_PRE_COMMIT}; fi); \} \|\| true$`,
|
|
846
|
+
"u"
|
|
847
|
+
);
|
|
848
|
+
var SHELL_QUOTED_RE = /^'(?:[^']|'"'"')*'$/u;
|
|
849
|
+
function decodePinnedPath(token) {
|
|
850
|
+
if (!token || !SHELL_QUOTED_RE.test(token)) return void 0;
|
|
851
|
+
const value = token.slice(1, -1).replaceAll(`'"'"'`, "'");
|
|
852
|
+
if (!isAbsolute2(value) || value.includes("\0") || value.includes("\n") || value.includes("\r")) {
|
|
853
|
+
return void 0;
|
|
854
|
+
}
|
|
855
|
+
return value;
|
|
856
|
+
}
|
|
857
|
+
function isPinnedPreCommitInvocation(line) {
|
|
858
|
+
const match = PINNED_PRE_COMMIT_RE.exec(line);
|
|
859
|
+
if (!match) return false;
|
|
860
|
+
if (match.groups?.node === void 0) return true;
|
|
861
|
+
return Boolean(
|
|
862
|
+
decodePinnedPath(match.groups.node) && decodePinnedPath(match.groups.entry)?.endsWith("/dist/hooks/pre-commit.js")
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
function isLegacyPreCommitBody(body) {
|
|
866
|
+
if (body === LADDER_ALPHA_2 || body === LADDER_ALPHA_9 || body === LADDER_ALPHA_35) return true;
|
|
867
|
+
if (isPinnedPreCommitInvocation(body)) return true;
|
|
868
|
+
const prefix = `${PRE_COMMIT_GATE}
|
|
869
|
+
`;
|
|
870
|
+
if (!body.startsWith(prefix) || !body.endsWith("\nfi")) return false;
|
|
871
|
+
const inner = body.slice(prefix.length, -"\nfi".length);
|
|
872
|
+
return inner === LADDER_ALPHA_35 || isPinnedPreCommitInvocation(inner);
|
|
873
|
+
}
|
|
874
|
+
function isLegacyOwnedPreCommitScript(content) {
|
|
875
|
+
const original = (ladder) => `#!/bin/sh
|
|
876
|
+
# prim pre-commit hook \u2014 auto-syncs affected specs on commit
|
|
877
|
+
# Installed by: prim hooks install
|
|
878
|
+
|
|
879
|
+
# Find the nearest node_modules/.bin with prim, or use npx
|
|
880
|
+
${ladder}
|
|
881
|
+
`;
|
|
882
|
+
if (content === original(LADDER_ALPHA_2) || content === original(LADDER_ALPHA_9)) return true;
|
|
883
|
+
if (content === `#!/bin/sh
|
|
884
|
+
# prim pre-commit hook \u2014 installed by: prim hooks install
|
|
885
|
+
|
|
886
|
+
${LADDER_ALPHA_9}
|
|
887
|
+
`) {
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
const prefix = `#!/bin/sh
|
|
891
|
+
# prim pre-commit hook \u2014 installed by: prim hooks install (${PRIM_MANAGED_MARK})
|
|
892
|
+
|
|
893
|
+
`;
|
|
894
|
+
if (!content.startsWith(prefix) || !content.endsWith("\n")) return false;
|
|
895
|
+
const body = content.slice(prefix.length, -1);
|
|
896
|
+
return body === LADDER_ALPHA_35 || isPinnedPreCommitInvocation(body);
|
|
897
|
+
}
|
|
898
|
+
var PINNED_PACKAGE_VERSION_RE = /@primitive\.ai\/prim@[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?/gu;
|
|
899
|
+
function normalizeOwnedGlobalHook(content, hookName) {
|
|
900
|
+
if (hookName === PRE_COMMIT.hookName) {
|
|
901
|
+
const lines = content.split("\n");
|
|
902
|
+
const invocationIndexes = lines.flatMap(
|
|
903
|
+
(line, index) => line.startsWith("{ ") && line.endsWith("; } || true") ? [index] : []
|
|
904
|
+
);
|
|
905
|
+
if (invocationIndexes.length !== 1) return null;
|
|
906
|
+
const invocationIndex = invocationIndexes[0];
|
|
907
|
+
if (!isPinnedPreCommitInvocation(lines[invocationIndex])) return null;
|
|
908
|
+
lines[invocationIndex] = "{ <recognized Prim pre-commit invocation>; } || true";
|
|
909
|
+
return lines.join("\n");
|
|
910
|
+
}
|
|
911
|
+
return content.replace(PINNED_PACKAGE_VERSION_RE, "@primitive.ai/prim@<version>");
|
|
912
|
+
}
|
|
913
|
+
function isLegacyOwnedGlobalHook(content, hookName) {
|
|
914
|
+
const spec = [PRE_COMMIT, POST_COMMIT, POST_REWRITE].find((s) => s.hookName === hookName);
|
|
915
|
+
if (!spec) return false;
|
|
916
|
+
const expected = legacyGlobalHookScript(spec);
|
|
917
|
+
if (content === expected) return true;
|
|
918
|
+
if (spec === PRE_COMMIT && content === expected.replace(gatedShim(spec.binName), `${PRE_COMMIT_GATE}
|
|
919
|
+
${LADDER_ALPHA_35}
|
|
920
|
+
fi`)) {
|
|
921
|
+
return true;
|
|
922
|
+
}
|
|
923
|
+
const normalized = normalizeOwnedGlobalHook(content, hookName);
|
|
924
|
+
return normalized !== null && normalized === normalizeOwnedGlobalHook(expected, hookName);
|
|
925
|
+
}
|
|
926
|
+
function legacyInlineHookBlock(hookName) {
|
|
927
|
+
return hookName === "post-commit" ? legacyPostCommitBlock() : legacyPostRewriteBlock();
|
|
928
|
+
}
|
|
929
|
+
function isRecognizedLegacyBlock(hookName, block) {
|
|
930
|
+
if (hookName === "pre-commit") {
|
|
931
|
+
const { start, end } = blockMarkers2(PRE_COMMIT);
|
|
932
|
+
const prefix = `${start}
|
|
933
|
+
`;
|
|
934
|
+
const suffix = `
|
|
935
|
+
${end}`;
|
|
936
|
+
if (!block.startsWith(prefix) || !block.endsWith(suffix)) return false;
|
|
937
|
+
return isLegacyPreCommitBody(block.slice(prefix.length, -suffix.length));
|
|
938
|
+
}
|
|
939
|
+
const expected = legacyInlineHookBlock(hookName);
|
|
940
|
+
const normalize = (text) => text.replace(PINNED_PACKAGE_VERSION_RE, "@primitive.ai/prim@<version>");
|
|
941
|
+
return normalize(block) === normalize(expected);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// src/lib/git-hooks.ts
|
|
945
|
+
import { execFileSync } from "child_process";
|
|
946
|
+
import { createHash as createHash3, randomUUID } from "crypto";
|
|
947
|
+
import {
|
|
948
|
+
constants as constants3,
|
|
949
|
+
closeSync as closeSync3,
|
|
950
|
+
existsSync as existsSync3,
|
|
951
|
+
fchmodSync,
|
|
952
|
+
fsyncSync,
|
|
953
|
+
lstatSync as lstatSync3,
|
|
954
|
+
mkdirSync as mkdirSync3,
|
|
955
|
+
openSync as openSync3,
|
|
956
|
+
readFileSync as readFileSync3,
|
|
957
|
+
realpathSync as realpathSync2,
|
|
958
|
+
renameSync,
|
|
959
|
+
unlinkSync as unlinkSync2,
|
|
960
|
+
writeFileSync
|
|
961
|
+
} from "fs";
|
|
962
|
+
import { basename, dirname as dirname3, isAbsolute as isAbsolute3, join as join4, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
963
|
+
var LEGACY_PRIM_CREATED_MARK = "prim-created-hook";
|
|
964
|
+
var LEGACY_PRIM_OWNED_HEADER = "# prim post-commit hook \u2014 installed by: prim hooks install (prim-managed-hook)";
|
|
965
|
+
var LEGACY_GLOBAL_OWNED_HEADER = "# prim global post-commit hook (core.hooksPath) \u2014 managed by prim; do not edit.";
|
|
966
|
+
var MAX_HOOK_BYTES = 1048576;
|
|
967
|
+
var GIT_TIMEOUT_MS = 1e3;
|
|
968
|
+
var SUPPORTED_SHEBANGS = /* @__PURE__ */ new Set([
|
|
969
|
+
"#!/bin/sh",
|
|
970
|
+
"#!/usr/bin/env sh",
|
|
971
|
+
"#!/bin/bash",
|
|
972
|
+
"#!/usr/bin/env bash",
|
|
973
|
+
"#!/bin/zsh",
|
|
974
|
+
"#!/usr/bin/env zsh"
|
|
975
|
+
]);
|
|
976
|
+
var HUSKY_V9_DISPATCHERS = /* @__PURE__ */ new Set([
|
|
977
|
+
'#!/usr/bin/env sh\n. "${0%/*}/h"',
|
|
978
|
+
'#!/usr/bin/env sh\n. "$(dirname "$0")/h"'
|
|
979
|
+
]);
|
|
980
|
+
var HUSKY_DIRECT_DISPATCHER = '#!/bin/sh\nexec sh "$(dirname "$0")/../post-commit"\n';
|
|
981
|
+
var HUSKY_V9_RUNTIME_HASHES = /* @__PURE__ */ new Set([
|
|
982
|
+
"d6aaa6f3f7c11008c525e649161df4a93152f1e55d0887fa1d7b8d622735d380",
|
|
983
|
+
"b02cb91cab7125a9e906be723a1b85297fc435a8307d429ae845546b4ee0ecde",
|
|
984
|
+
"9db5d129c4eb822a773157d98cd8fb8f1b156b85e3193117fefe527ca6d45e91",
|
|
985
|
+
"3b2cb21335e544b4ebce658ca47af4a69d2c77026cc4d3f8b2227c34d062b207",
|
|
986
|
+
"548990e1c11285694096184993d907b258d41235461a7ca2551e3b53ff9d3a38",
|
|
987
|
+
"c029a943acea5d6e2ddbaa271a1fb22c2da55d56b5c6fb59ca804ac9a4018708",
|
|
988
|
+
"ae3413a8fe2b39372de48bdc9691f42055f089c0c7529834b2fb07a131585b6a",
|
|
989
|
+
"70200b200ca709b0622784f93839a5b2872333a917a09afddefd7dc2d8cdc680"
|
|
990
|
+
]);
|
|
991
|
+
function managedHookSpecFor(hookName) {
|
|
992
|
+
const { start, end } = blockMarkers(hookName);
|
|
993
|
+
return {
|
|
994
|
+
hookName,
|
|
995
|
+
blockStart: start,
|
|
996
|
+
blockEnd: end,
|
|
997
|
+
createdMark: `prim-created-${hookName}-hook`
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
var MANAGED_HOOK_SPECS = {
|
|
1001
|
+
"pre-commit": managedHookSpecFor("pre-commit"),
|
|
1002
|
+
"post-commit": managedHookSpecFor("post-commit"),
|
|
1003
|
+
"post-rewrite": managedHookSpecFor("post-rewrite")
|
|
846
1004
|
};
|
|
1005
|
+
function managedHookSpec(hookName) {
|
|
1006
|
+
return MANAGED_HOOK_SPECS[hookName];
|
|
1007
|
+
}
|
|
1008
|
+
function gitHooksMode(options = {}) {
|
|
1009
|
+
try {
|
|
1010
|
+
const value = execFileSync(
|
|
1011
|
+
"git",
|
|
1012
|
+
["config", ...options.global ? ["--global"] : [], "--get", "prim.gitHooks"],
|
|
1013
|
+
{
|
|
1014
|
+
cwd: options.cwd,
|
|
1015
|
+
encoding: "utf8",
|
|
1016
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1017
|
+
timeout: GIT_TIMEOUT_MS
|
|
1018
|
+
}
|
|
1019
|
+
).trim();
|
|
1020
|
+
return value === "manual" ? "manual" : "auto";
|
|
1021
|
+
} catch {
|
|
1022
|
+
return "auto";
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
847
1025
|
function legacyFloatingInvocation() {
|
|
848
1026
|
return `if command -v prim-post-commit >/dev/null 2>&1; then
|
|
849
1027
|
prim-post-commit || true
|
|
@@ -860,15 +1038,6 @@ fi
|
|
|
860
1038
|
`);
|
|
861
1039
|
}
|
|
862
1040
|
var PINNED_INVOCATION_RE = /^\{ if \[ -x (?<node>'.*') \] && \[ -f (?<entry>'.*') \]; then \k<node> \k<entry>; else npx --yes -p @primitive\.ai\/prim@(?<version>[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?) prim-post-commit; fi; \} \|\| true$/u;
|
|
863
|
-
var SHELL_QUOTED_RE = /^'(?:[^']|'"'"')*'$/u;
|
|
864
|
-
function decodePinnedPath(token) {
|
|
865
|
-
if (!token || !SHELL_QUOTED_RE.test(token)) return void 0;
|
|
866
|
-
const value = token.slice(1, -1).replaceAll(`'"'"'`, "'");
|
|
867
|
-
if (!isAbsolute2(value) || value.includes("\0") || value.includes("\n") || value.includes("\r")) {
|
|
868
|
-
return void 0;
|
|
869
|
-
}
|
|
870
|
-
return value;
|
|
871
|
-
}
|
|
872
1041
|
function isPinnedInvocation(value) {
|
|
873
1042
|
const match = PINNED_INVOCATION_RE.exec(value);
|
|
874
1043
|
const node = decodePinnedPath(match?.groups?.node);
|
|
@@ -901,48 +1070,149 @@ function isKnownLegacyGlobalGate(value) {
|
|
|
901
1070
|
}
|
|
902
1071
|
function createdScaffold(spec) {
|
|
903
1072
|
return Buffer.from(`#!/bin/sh
|
|
904
|
-
${spec.
|
|
1073
|
+
${managedHookBlock(spec.hookName)}
|
|
905
1074
|
# ${spec.createdMark}
|
|
906
1075
|
`);
|
|
907
1076
|
}
|
|
1077
|
+
function oldCreatedPrefixes(spec) {
|
|
1078
|
+
return [
|
|
1079
|
+
Buffer.from(`#!/bin/sh
|
|
1080
|
+
# ${spec.createdMark}
|
|
1081
|
+
|
|
1082
|
+
`),
|
|
1083
|
+
Buffer.from(`#!/bin/sh
|
|
1084
|
+
# ${LEGACY_PRIM_CREATED_MARK}
|
|
1085
|
+
|
|
1086
|
+
`)
|
|
1087
|
+
];
|
|
1088
|
+
}
|
|
1089
|
+
function isOwnedStandalonePreCommit(content) {
|
|
1090
|
+
return content === "#!/bin/sh\nprim-pre-commit\n" || isLegacyOwnedPreCommitScript(content);
|
|
1091
|
+
}
|
|
1092
|
+
function realpathLoose(path) {
|
|
1093
|
+
const missing = [];
|
|
1094
|
+
let current = resolve3(path);
|
|
1095
|
+
for (; ; ) {
|
|
1096
|
+
try {
|
|
1097
|
+
return join4(realpathSync2.native(current), ...missing);
|
|
1098
|
+
} catch {
|
|
1099
|
+
const parent = dirname3(current);
|
|
1100
|
+
if (parent === current) return resolve3(path);
|
|
1101
|
+
missing.unshift(basename(current));
|
|
1102
|
+
current = parent;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
function isWithin(path, directory) {
|
|
1107
|
+
const rel = relative2(directory, path);
|
|
1108
|
+
return rel === "" || !isAbsolute3(rel) && rel !== ".." && !rel.startsWith(`..${sep2}`);
|
|
1109
|
+
}
|
|
1110
|
+
function primGitHooksDirectory() {
|
|
1111
|
+
try {
|
|
1112
|
+
return join4(primConfigDirectory(), "git-hooks");
|
|
1113
|
+
} catch {
|
|
1114
|
+
return void 0;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
function otherWorktreeRoots(gitRoot) {
|
|
1118
|
+
try {
|
|
1119
|
+
const self = realpathLoose(gitRoot);
|
|
1120
|
+
return execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
1121
|
+
cwd: gitRoot,
|
|
1122
|
+
encoding: "utf8",
|
|
1123
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1124
|
+
timeout: GIT_TIMEOUT_MS
|
|
1125
|
+
}).split("\n").flatMap((line) => line.startsWith("worktree ") ? [realpathLoose(line.slice(9))] : []).filter((root) => root !== self);
|
|
1126
|
+
} catch {
|
|
1127
|
+
return [];
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function hookLocation(hookPath, dirs) {
|
|
1131
|
+
const path = realpathLoose(hookPath);
|
|
1132
|
+
if (isWithin(path, realpathLoose(dirs.gitDir)) || isWithin(path, realpathLoose(dirs.commonDir))) {
|
|
1133
|
+
return "repository";
|
|
1134
|
+
}
|
|
1135
|
+
const primDir = primGitHooksDirectory();
|
|
1136
|
+
if (primDir !== void 0 && isWithin(path, realpathLoose(primDir))) return "prim";
|
|
1137
|
+
const root = realpathLoose(dirs.gitRoot);
|
|
1138
|
+
if (!isWithin(path, root)) return "external";
|
|
1139
|
+
const nested = otherWorktreeRoots(dirs.gitRoot).filter((other) => isWithin(other, root));
|
|
1140
|
+
return nested.some((other) => isWithin(path, other)) ? "external" : "worktree";
|
|
1141
|
+
}
|
|
1142
|
+
function hooksPathScope(cwd) {
|
|
1143
|
+
try {
|
|
1144
|
+
const scope = execFileSync("git", ["config", "--show-scope", "--get", "core.hooksPath"], {
|
|
1145
|
+
cwd,
|
|
1146
|
+
encoding: "utf8",
|
|
1147
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1148
|
+
timeout: GIT_TIMEOUT_MS
|
|
1149
|
+
}).split(" ")[0];
|
|
1150
|
+
return scope === "local" || scope === "worktree" || scope === "global" || scope === "system" || scope === "command" ? scope : void 0;
|
|
1151
|
+
} catch {
|
|
1152
|
+
return void 0;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
function externalHookRemedy(hookName, cwd) {
|
|
1156
|
+
const snippet = `place \`prim hooks snippet ${hookName}\` there yourself`;
|
|
1157
|
+
switch (hooksPathScope(cwd)) {
|
|
1158
|
+
case "global":
|
|
1159
|
+
return `your global core.hooksPath runs every repository's hooks: ${snippet}, or have prim add it with \`prim hooks install --scope user --global-hooks-path\` (it changes every repository's hooks: an agent must ask the user first)`;
|
|
1160
|
+
case "system":
|
|
1161
|
+
return `the system core.hooksPath is shared by every user on this machine: ${snippet}, or ask its owner`;
|
|
1162
|
+
case "local":
|
|
1163
|
+
case "worktree":
|
|
1164
|
+
return `this repository's core.hooksPath points outside it: ${snippet}, or point core.hooksPath inside the repository`;
|
|
1165
|
+
default:
|
|
1166
|
+
return snippet;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
908
1169
|
function safeGitPath(root, value) {
|
|
909
1170
|
if (value.length === 0 || value !== value.trim() || value.includes("\0") || value.includes("\n") || value.includes("\r")) {
|
|
910
1171
|
throw new Error("Git returned an unsafe hooks path");
|
|
911
1172
|
}
|
|
912
|
-
return
|
|
1173
|
+
return isAbsolute3(value) ? resolve3(value) : resolve3(root, value);
|
|
913
1174
|
}
|
|
914
|
-
function
|
|
1175
|
+
function resolveEffectiveGitHook(hookName, cwd = process.cwd()) {
|
|
915
1176
|
const gitRoot = gitToplevel(cwd);
|
|
916
1177
|
if (!gitRoot) throw new Error("not a git repository");
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1178
|
+
const lines = execFileSync(
|
|
1179
|
+
"git",
|
|
1180
|
+
["rev-parse", "--git-path", "hooks", "--absolute-git-dir", "--git-common-dir"],
|
|
1181
|
+
{
|
|
1182
|
+
cwd: gitRoot,
|
|
1183
|
+
encoding: "utf8",
|
|
1184
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1185
|
+
timeout: GIT_TIMEOUT_MS
|
|
1186
|
+
}
|
|
1187
|
+
).replace(/\r?\n$/u, "").split(/\r?\n/u);
|
|
1188
|
+
if (lines.length !== 3) throw new Error("Git returned unexpected repository paths");
|
|
1189
|
+
const [hooksValue, gitDirValue, commonDirValue] = lines;
|
|
1190
|
+
const hooksDir = safeGitPath(gitRoot, hooksValue);
|
|
1191
|
+
const dirs = {
|
|
1192
|
+
gitRoot,
|
|
1193
|
+
gitDir: safeGitPath(gitRoot, gitDirValue),
|
|
1194
|
+
commonDir: safeGitPath(gitRoot, commonDirValue)
|
|
1195
|
+
};
|
|
924
1196
|
if (basename(hooksDir) === "_" && basename(dirname3(hooksDir)) === ".husky") {
|
|
1197
|
+
const hookPath2 = resolve3(dirname3(hooksDir), hookName);
|
|
925
1198
|
return {
|
|
926
1199
|
gitRoot,
|
|
927
1200
|
hooksDir,
|
|
928
|
-
hookPath:
|
|
1201
|
+
hookPath: hookPath2,
|
|
929
1202
|
kind: "husky_v9",
|
|
930
|
-
dispatcherPath: resolve3(hooksDir,
|
|
1203
|
+
dispatcherPath: resolve3(hooksDir, hookName),
|
|
1204
|
+
location: hookLocation(hookPath2, dirs)
|
|
931
1205
|
};
|
|
932
1206
|
}
|
|
1207
|
+
const hookPath = resolve3(hooksDir, hookName);
|
|
933
1208
|
return {
|
|
934
1209
|
gitRoot,
|
|
935
1210
|
hooksDir,
|
|
936
|
-
hookPath
|
|
937
|
-
kind: "direct"
|
|
1211
|
+
hookPath,
|
|
1212
|
+
kind: "direct",
|
|
1213
|
+
location: hookLocation(hookPath, dirs)
|
|
938
1214
|
};
|
|
939
1215
|
}
|
|
940
|
-
function resolveEffectivePostCommitHook(cwd = process.cwd()) {
|
|
941
|
-
return resolveEffectiveManagedHook(POST_COMMIT_MANAGED_HOOK, cwd);
|
|
942
|
-
}
|
|
943
|
-
function resolveEffectivePostRewriteHook(cwd = process.cwd()) {
|
|
944
|
-
return resolveEffectiveManagedHook(POST_REWRITE_MANAGED_HOOK, cwd);
|
|
945
|
-
}
|
|
946
1216
|
function assertSafeFile(path, spec) {
|
|
947
1217
|
if (!existsSync3(path)) return void 0;
|
|
948
1218
|
const stat = lstatSync3(path);
|
|
@@ -1024,16 +1294,107 @@ function blockRange(content, spec) {
|
|
|
1024
1294
|
}
|
|
1025
1295
|
const start = starts[0];
|
|
1026
1296
|
const end = ends[0] + endMarker.length;
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1297
|
+
const text = content.subarray(start, end).toString("utf8");
|
|
1298
|
+
if (canonicalHookBlock(text) === canonicalHookBlock(managedHookBlock(spec.hookName))) {
|
|
1299
|
+
return { kind: "current", start, end };
|
|
1300
|
+
}
|
|
1301
|
+
const version = hookBlockContractVersion(text);
|
|
1302
|
+
if (version !== void 0 && version > GIT_HOOK_CONTRACT_VERSION) {
|
|
1303
|
+
return { kind: "newer", start, end };
|
|
1304
|
+
}
|
|
1305
|
+
return { kind: "stale", start, end };
|
|
1306
|
+
}
|
|
1307
|
+
var HUSKY_V8_SOURCE_RE = /^\.[ \t]+"\$\(dirname(?:[ \t]+--)?[ \t]+"\$0"\)\/_\/husky\.sh"[ \t]*$/mu;
|
|
1308
|
+
function blockInsertionPoint(content, allowShebangless, spec, huskyV8) {
|
|
1309
|
+
const shebangEnd = shellInsertionPoint(content, allowShebangless, spec);
|
|
1310
|
+
if (!huskyV8) return shebangEnd;
|
|
1311
|
+
const rest = content.subarray(shebangEnd).toString("utf8");
|
|
1312
|
+
const match = HUSKY_V8_SOURCE_RE.exec(rest);
|
|
1313
|
+
if (!match) return shebangEnd;
|
|
1314
|
+
const lineEnd = match.index + match[0].length;
|
|
1315
|
+
const afterLine = rest[lineEnd] === "\n" ? lineEnd + 1 : lineEnd;
|
|
1316
|
+
return shebangEnd + Buffer.byteLength(rest.slice(0, afterLine), "utf8");
|
|
1317
|
+
}
|
|
1318
|
+
var HUSKY_SPEC_MAJOR_RE = /^\s*[~^]?=?\s*v?([0-9]+)(?:\.(?:[0-9]+|[xX*])){0,2}(?:-[0-9A-Za-z.-]+)?\s*$/u;
|
|
1319
|
+
function declaredHuskyMajor(dir) {
|
|
1320
|
+
for (let current = resolve3(dir); ; ) {
|
|
1321
|
+
const manifest = join4(current, "package.json");
|
|
1322
|
+
if (existsSync3(manifest)) {
|
|
1323
|
+
try {
|
|
1324
|
+
if (lstatSync3(manifest).size > MAX_HOOK_BYTES) return void 0;
|
|
1325
|
+
const parsed = JSON.parse(readFileSync3(manifest, "utf8"));
|
|
1326
|
+
const range = parsed.devDependencies?.husky ?? parsed.dependencies?.husky;
|
|
1327
|
+
const major = typeof range === "string" ? HUSKY_SPEC_MAJOR_RE.exec(range)?.[1] : void 0;
|
|
1328
|
+
return major === void 0 ? void 0 : Number(major);
|
|
1329
|
+
} catch {
|
|
1330
|
+
return void 0;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
const parent = dirname3(current);
|
|
1334
|
+
if (parent === current || existsSync3(join4(current, ".git"))) return void 0;
|
|
1335
|
+
current = parent;
|
|
1336
|
+
}
|
|
1029
1337
|
}
|
|
1030
|
-
function
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1338
|
+
function isHuskyV8Hook(hookPath) {
|
|
1339
|
+
const runtime = join4(dirname3(hookPath), "_", "husky.sh");
|
|
1340
|
+
try {
|
|
1341
|
+
const stat = lstatSync3(runtime);
|
|
1342
|
+
return stat.isFile() && stat.size <= MAX_HOOK_BYTES && readFileSync3(runtime, "utf8").includes("husky_skip_init");
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
if (errorCode(error) !== "ENOENT") return false;
|
|
1345
|
+
}
|
|
1346
|
+
const major = declaredHuskyMajor(dirname3(dirname3(hookPath)));
|
|
1347
|
+
return major !== void 0 && major <= 8;
|
|
1348
|
+
}
|
|
1349
|
+
var TERMINAL_LINE_RE = /^(?:exit|return)(?:\s|;|$)/u;
|
|
1350
|
+
var REDIRECT_OPERATOR_RE = /^[0-9]*(?:<<?|>>?|<&|>&|<>|>\|)$/u;
|
|
1351
|
+
var REDIRECT_WORD_RE = /^[0-9]*(?:<|>)/u;
|
|
1352
|
+
function isReplacingExec(line) {
|
|
1353
|
+
if (!/^exec(?:\s|$)/u.test(line)) return false;
|
|
1354
|
+
const words = line.slice(4).trim().split(/\s+/u).filter(Boolean);
|
|
1355
|
+
for (let index = 0; index < words.length; index += 1) {
|
|
1356
|
+
const word = words[index];
|
|
1357
|
+
if (word.startsWith("#")) break;
|
|
1358
|
+
if (REDIRECT_OPERATOR_RE.test(word)) {
|
|
1359
|
+
index += 1;
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
if (!REDIRECT_WORD_RE.test(word)) return true;
|
|
1363
|
+
}
|
|
1364
|
+
return false;
|
|
1365
|
+
}
|
|
1366
|
+
function terminatesBefore(content, from, offset) {
|
|
1367
|
+
const lines = content.subarray(from, offset).toString("utf8").split("\n");
|
|
1368
|
+
return lines.some((line) => TERMINAL_LINE_RE.test(line) || isReplacingExec(line));
|
|
1369
|
+
}
|
|
1370
|
+
function mayTerminateBefore(content, from, offset) {
|
|
1371
|
+
return content.subarray(from, offset).toString("utf8").split(/\n|;|&&|\|\||\||[{()]|\b(?:then|do|else)\b/u).some((part) => {
|
|
1372
|
+
const command = part.trim();
|
|
1373
|
+
return TERMINAL_LINE_RE.test(command) || isReplacingExec(command);
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
function withoutRange(content, start, end) {
|
|
1377
|
+
const suffix = content.subarray(end);
|
|
1378
|
+
return Buffer.concat([
|
|
1379
|
+
content.subarray(0, start),
|
|
1380
|
+
suffix.at(0) === 10 ? suffix.subarray(1) : suffix
|
|
1381
|
+
]);
|
|
1382
|
+
}
|
|
1383
|
+
function insertBlock(content, at, spec) {
|
|
1384
|
+
return Buffer.concat([
|
|
1385
|
+
content.subarray(0, at),
|
|
1386
|
+
Buffer.from(managedHookBlock(spec.hookName)),
|
|
1387
|
+
Buffer.from("\n"),
|
|
1388
|
+
content.subarray(at)
|
|
1389
|
+
]);
|
|
1390
|
+
}
|
|
1391
|
+
function migratedLegacyContent(existing, allowShebangless, spec, huskyV8) {
|
|
1392
|
+
const text = existing.toString("utf8");
|
|
1393
|
+
if (spec.hookName === "pre-commit" && isOwnedStandalonePreCommit(text)) {
|
|
1033
1394
|
return createdScaffold(spec);
|
|
1034
1395
|
}
|
|
1035
|
-
|
|
1036
|
-
if (
|
|
1396
|
+
if (spec.hookName !== "post-commit") return void 0;
|
|
1397
|
+
if (text.startsWith(`#!/bin/sh
|
|
1037
1398
|
${LEGACY_GLOBAL_OWNED_HEADER}
|
|
1038
1399
|
`)) {
|
|
1039
1400
|
const gateAt = existing.indexOf(Buffer.from('if [ "$(git config --get prim.active'));
|
|
@@ -1048,15 +1409,13 @@ ${LEGACY_GLOBAL_OWNED_HEADER}
|
|
|
1048
1409
|
existing.subarray(0, gateAt),
|
|
1049
1410
|
existing.subarray(chainAt)
|
|
1050
1411
|
]);
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
withoutLegacyGate
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
withoutLegacyGate.subarray(shebangEnd2)
|
|
1057
|
-
]);
|
|
1412
|
+
return insertBlock(
|
|
1413
|
+
withoutLegacyGate,
|
|
1414
|
+
blockInsertionPoint(withoutLegacyGate, allowShebangless, spec, huskyV8),
|
|
1415
|
+
spec
|
|
1416
|
+
);
|
|
1058
1417
|
}
|
|
1059
|
-
if (
|
|
1418
|
+
if (text.startsWith(`#!/bin/sh
|
|
1060
1419
|
${LEGACY_PRIM_OWNED_HEADER}
|
|
1061
1420
|
`)) {
|
|
1062
1421
|
const prefixLength = legacyProjectPrefixLength(existing);
|
|
@@ -1065,42 +1424,59 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
1065
1424
|
}
|
|
1066
1425
|
return Buffer.concat([createdScaffold(spec), existing.subarray(prefixLength)]);
|
|
1067
1426
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
];
|
|
1079
|
-
const oldCreated = oldCreatedPrefixes.some(
|
|
1427
|
+
return void 0;
|
|
1428
|
+
}
|
|
1429
|
+
function mergedContent(existing, allowShebangless, spec, relocate, huskyV8) {
|
|
1430
|
+
if (!existing) return { content: createdScaffold(spec), migrates: false };
|
|
1431
|
+
const range = blockRange(existing, spec);
|
|
1432
|
+
if (range.kind === "absent") {
|
|
1433
|
+
const migrated = migratedLegacyContent(existing, allowShebangless, spec, huskyV8);
|
|
1434
|
+
if (migrated) return { content: migrated, migrates: true };
|
|
1435
|
+
} else if (range.kind !== "newer") {
|
|
1436
|
+
const oldCreated = oldCreatedPrefixes(spec).some(
|
|
1080
1437
|
(prefix) => range.start === prefix.length && existing.subarray(0, prefix.length).equals(prefix)
|
|
1081
1438
|
);
|
|
1082
1439
|
if (oldCreated) {
|
|
1083
1440
|
const suffix = existing.subarray(range.end);
|
|
1084
1441
|
const tail = suffix.at(0) === 10 ? suffix.subarray(1) : suffix;
|
|
1085
|
-
return
|
|
1442
|
+
return {
|
|
1443
|
+
content: Buffer.concat([createdScaffold(spec), tail]),
|
|
1444
|
+
migrates: range.kind === "stale"
|
|
1445
|
+
};
|
|
1086
1446
|
}
|
|
1087
1447
|
}
|
|
1088
1448
|
const shebangEnd = shellInsertionPoint(existing, allowShebangless, spec);
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
existing
|
|
1095
|
-
|
|
1096
|
-
|
|
1449
|
+
const insertion = blockInsertionPoint(existing, allowShebangless, spec, huskyV8);
|
|
1450
|
+
switch (range.kind) {
|
|
1451
|
+
case "newer":
|
|
1452
|
+
return { content: existing, migrates: false };
|
|
1453
|
+
case "absent":
|
|
1454
|
+
return { content: insertBlock(existing, insertion, spec), migrates: false };
|
|
1455
|
+
case "current":
|
|
1456
|
+
case "stale": {
|
|
1457
|
+
const misplaced = terminatesBefore(existing, shebangEnd, range.start) || range.start < insertion;
|
|
1458
|
+
if (relocate && misplaced) {
|
|
1459
|
+
const without = withoutRange(existing, range.start, range.end);
|
|
1460
|
+
return {
|
|
1461
|
+
content: insertBlock(
|
|
1462
|
+
without,
|
|
1463
|
+
blockInsertionPoint(without, allowShebangless, spec, huskyV8),
|
|
1464
|
+
spec
|
|
1465
|
+
),
|
|
1466
|
+
migrates: range.kind === "stale"
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
if (range.kind === "current") return { content: existing, migrates: false };
|
|
1470
|
+
return {
|
|
1471
|
+
content: Buffer.concat([
|
|
1472
|
+
existing.subarray(0, range.start),
|
|
1473
|
+
Buffer.from(managedHookBlock(spec.hookName)),
|
|
1474
|
+
existing.subarray(range.end)
|
|
1475
|
+
]),
|
|
1476
|
+
migrates: true
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1097
1479
|
}
|
|
1098
|
-
return Buffer.concat([
|
|
1099
|
-
withoutBlock.subarray(0, shebangEnd),
|
|
1100
|
-
block,
|
|
1101
|
-
Buffer.from("\n"),
|
|
1102
|
-
withoutBlock.subarray(shebangEnd)
|
|
1103
|
-
]);
|
|
1104
1480
|
}
|
|
1105
1481
|
function errorCode(error) {
|
|
1106
1482
|
return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
@@ -1160,137 +1536,143 @@ function unlinkHookUnchanged(path, expected, stat, spec) {
|
|
|
1160
1536
|
assertTargetUnchanged(path, expected, spec, stat);
|
|
1161
1537
|
unlinkSync2(path);
|
|
1162
1538
|
}
|
|
1163
|
-
function
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1539
|
+
function workingLegacyBlock(content, target, spec) {
|
|
1540
|
+
try {
|
|
1541
|
+
const range = blockRange(content, spec);
|
|
1542
|
+
if (range.kind !== "stale") return false;
|
|
1543
|
+
const block = content.subarray(range.start, range.end).toString("utf8");
|
|
1544
|
+
const from = shellInsertionPoint(content, target.kind === "husky_v9", spec);
|
|
1545
|
+
return isRecognizedLegacyBlock(spec.hookName, block) && !mayTerminateBefore(content, from, range.start);
|
|
1546
|
+
} catch {
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1170
1549
|
}
|
|
1171
|
-
|
|
1550
|
+
var AMBIENT_LOCATIONS = ["repository", "prim"];
|
|
1551
|
+
var EXPLICIT_LOCATIONS = ["repository", "prim", "worktree"];
|
|
1552
|
+
function ensureTarget(target, spec, policy) {
|
|
1553
|
+
const result = { hookName: spec.hookName, path: target.hookPath, kind: target.kind };
|
|
1554
|
+
const skip = (outcome) => ({
|
|
1555
|
+
...result,
|
|
1556
|
+
changed: false,
|
|
1557
|
+
outcome
|
|
1558
|
+
});
|
|
1172
1559
|
assertUsableHuskyDispatcher(target, spec);
|
|
1173
1560
|
const stat = assertSafeFile(target.hookPath, spec);
|
|
1174
1561
|
if (stat && target.kind === "direct" && (stat.mode & 64) === 0) {
|
|
1175
1562
|
throw new Error(`existing ${spec.hookName} hook is not executable: ${target.hookPath}`);
|
|
1176
1563
|
}
|
|
1177
1564
|
const existing = stat ? readHookFile(target.hookPath) : void 0;
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1565
|
+
if (policy.repairOnly && (!existing || blockRange(existing, spec).kind === "absent")) {
|
|
1566
|
+
return skip("skipped");
|
|
1567
|
+
}
|
|
1568
|
+
const huskyV8 = isHuskyV8Hook(target.hookPath);
|
|
1569
|
+
const next = mergedContent(existing, target.kind === "husky_v9", spec, policy.relocate, huskyV8);
|
|
1570
|
+
if (existing && next.content.equals(existing)) return skip("unchanged");
|
|
1571
|
+
if (!policy.writable(target.location)) {
|
|
1572
|
+
if (target.location === "worktree") return skip("deferred");
|
|
1573
|
+
return skip(existing && workingLegacyBlock(existing, target, spec) ? "kept" : "external");
|
|
1574
|
+
}
|
|
1575
|
+
if (next.migrates && inspectGitHookEntrypoint() !== "ready") return skip("runtime_missing");
|
|
1576
|
+
rewriteHookAtomically(target.hookPath, next.content, existing, spec, stat);
|
|
1577
|
+
return { ...result, changed: true, outcome: existing ? "updated" : "created" };
|
|
1578
|
+
}
|
|
1579
|
+
function ensureEffectiveGitHook(hookName, cwd = process.cwd(), options = {}) {
|
|
1580
|
+
const target = resolveEffectiveGitHook(hookName, cwd);
|
|
1581
|
+
if (gitHooksMode({ cwd: target.gitRoot }) === "manual") {
|
|
1582
|
+
return {
|
|
1583
|
+
hookName,
|
|
1584
|
+
path: target.hookPath,
|
|
1585
|
+
kind: target.kind,
|
|
1586
|
+
changed: false,
|
|
1587
|
+
outcome: "manual"
|
|
1588
|
+
};
|
|
1189
1589
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
hooksDir: dirname3(absolute),
|
|
1198
|
-
hookPath: absolute,
|
|
1199
|
-
kind: "direct"
|
|
1200
|
-
},
|
|
1201
|
-
POST_COMMIT_MANAGED_HOOK,
|
|
1202
|
-
initialContent
|
|
1203
|
-
);
|
|
1204
|
-
return { ...result, kind: "direct" };
|
|
1590
|
+
const explicit = (options.context ?? "explicit") === "explicit";
|
|
1591
|
+
const allowed = explicit ? EXPLICIT_LOCATIONS : AMBIENT_LOCATIONS;
|
|
1592
|
+
return ensureTarget(target, managedHookSpec(hookName), {
|
|
1593
|
+
writable: (location) => allowed.includes(location),
|
|
1594
|
+
relocate: explicit,
|
|
1595
|
+
repairOnly: options.repairOnly === true
|
|
1596
|
+
});
|
|
1205
1597
|
}
|
|
1206
|
-
function
|
|
1598
|
+
function explicitTarget(hookPath, options) {
|
|
1207
1599
|
const absolute = resolve3(hookPath);
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
return { ...result, kind: "direct" };
|
|
1600
|
+
return {
|
|
1601
|
+
gitRoot: dirname3(dirname3(absolute)),
|
|
1602
|
+
hooksDir: dirname3(absolute),
|
|
1603
|
+
hookPath: absolute,
|
|
1604
|
+
// A `.husky/<hook>` file is run by Husky's dispatcher via `sh`, so it needs
|
|
1605
|
+
// neither a shebang nor the executable bit.
|
|
1606
|
+
kind: options.husky ? "husky_v9" : "direct",
|
|
1607
|
+
// The caller chose this exact file (a --target, or a consented global dir).
|
|
1608
|
+
location: "external"
|
|
1609
|
+
};
|
|
1219
1610
|
}
|
|
1220
|
-
function
|
|
1221
|
-
|
|
1611
|
+
function hasCurrentHookBlock(hookName, hookPath) {
|
|
1612
|
+
try {
|
|
1613
|
+
const kind = blockRange(readHookFile(hookPath), managedHookSpec(hookName)).kind;
|
|
1614
|
+
return kind === "current" || kind === "newer";
|
|
1615
|
+
} catch {
|
|
1616
|
+
return false;
|
|
1617
|
+
}
|
|
1222
1618
|
}
|
|
1223
|
-
function
|
|
1224
|
-
return
|
|
1619
|
+
function ensureGitHookAtPath(hookName, hookPath, options = {}) {
|
|
1620
|
+
return ensureTarget(explicitTarget(hookPath, options), managedHookSpec(hookName), {
|
|
1621
|
+
writable: () => true,
|
|
1622
|
+
relocate: true,
|
|
1623
|
+
repairOnly: false
|
|
1624
|
+
});
|
|
1225
1625
|
}
|
|
1226
|
-
function
|
|
1227
|
-
const
|
|
1626
|
+
function inspectEffectiveGitHook(hookName, cwd = process.cwd()) {
|
|
1627
|
+
const spec = managedHookSpec(hookName);
|
|
1628
|
+
const target = resolveEffectiveGitHook(hookName, cwd);
|
|
1629
|
+
const base = {
|
|
1630
|
+
...target,
|
|
1631
|
+
hookName,
|
|
1632
|
+
mode: gitHooksMode({ cwd: target.gitRoot }),
|
|
1633
|
+
entrypoint: inspectGitHookEntrypoint()
|
|
1634
|
+
};
|
|
1635
|
+
const uncovered = (reason2, executable2 = false) => ({ ...base, covered: false, executable: executable2, current: false, reason: reason2 });
|
|
1228
1636
|
try {
|
|
1229
1637
|
assertUsableHuskyDispatcher(target, spec);
|
|
1230
1638
|
} catch (error) {
|
|
1231
1639
|
const missing = error instanceof Error && (error.message.includes("missing or not executable") || error.message.includes("unsafe"));
|
|
1232
|
-
return
|
|
1233
|
-
...target,
|
|
1234
|
-
covered: false,
|
|
1235
|
-
executable: false,
|
|
1236
|
-
current: false,
|
|
1237
|
-
reason: missing ? "husky_dispatcher_missing" : "husky_dispatcher_invalid"
|
|
1238
|
-
};
|
|
1640
|
+
return uncovered(missing ? "husky_dispatcher_missing" : "husky_dispatcher_invalid");
|
|
1239
1641
|
}
|
|
1240
1642
|
let stat;
|
|
1241
1643
|
try {
|
|
1242
1644
|
stat = assertSafeFile(target.hookPath, spec);
|
|
1243
1645
|
} catch {
|
|
1244
|
-
return
|
|
1245
|
-
...target,
|
|
1246
|
-
covered: false,
|
|
1247
|
-
executable: false,
|
|
1248
|
-
current: false,
|
|
1249
|
-
reason: "unsafe_target"
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
|
-
if (!stat) {
|
|
1253
|
-
return {
|
|
1254
|
-
...target,
|
|
1255
|
-
covered: false,
|
|
1256
|
-
executable: false,
|
|
1257
|
-
current: false,
|
|
1258
|
-
reason: "missing"
|
|
1259
|
-
};
|
|
1646
|
+
return uncovered("unsafe_target");
|
|
1260
1647
|
}
|
|
1648
|
+
if (!stat) return uncovered("missing");
|
|
1261
1649
|
const executable = target.kind === "husky_v9" || (stat.mode & 64) !== 0;
|
|
1650
|
+
let range;
|
|
1651
|
+
let content;
|
|
1652
|
+
let insertion;
|
|
1262
1653
|
try {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
const positioned = range.kind !== "absent" && range.start === shebangEnd;
|
|
1267
|
-
const current = range.kind === "current" && positioned;
|
|
1268
|
-
return {
|
|
1269
|
-
...target,
|
|
1270
|
-
covered: current && executable,
|
|
1271
|
-
executable,
|
|
1272
|
-
current,
|
|
1273
|
-
reason: range.kind === "current" && !positioned ? "unreachable_block" : !current ? "stale_block" : executable ? void 0 : "not_executable"
|
|
1274
|
-
};
|
|
1654
|
+
content = readHookFile(target.hookPath);
|
|
1655
|
+
insertion = shellInsertionPoint(content, target.kind === "husky_v9", spec);
|
|
1656
|
+
range = blockRange(content, spec);
|
|
1275
1657
|
} catch (error) {
|
|
1276
1658
|
const message = error instanceof Error ? error.message : "";
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
executable,
|
|
1282
|
-
current: false,
|
|
1283
|
-
reason
|
|
1284
|
-
};
|
|
1659
|
+
return uncovered(
|
|
1660
|
+
message.includes("binary") ? "binary" : message.includes("unsupported") ? "unsupported_interpreter" : "malformed_markers",
|
|
1661
|
+
executable
|
|
1662
|
+
);
|
|
1285
1663
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1664
|
+
const huskyV8 = isHuskyV8Hook(target.hookPath);
|
|
1665
|
+
const preferred = blockInsertionPoint(content, target.kind === "husky_v9", spec, huskyV8);
|
|
1666
|
+
const current = range.kind === "current" || range.kind === "newer";
|
|
1667
|
+
const legacy = range.kind === "stale" && workingLegacyBlock(content, target, spec);
|
|
1668
|
+
const reason = range.kind === "absent" ? "missing_block" : range.kind === "stale" && !legacy ? "stale_block" : terminatesBefore(content, insertion, range.start) ? "unreachable_block" : !executable ? "not_executable" : legacy ? "legacy_block" : base.entrypoint !== "ready" ? "entrypoint_missing" : range.start < preferred ? "misplaced_block" : void 0;
|
|
1669
|
+
return {
|
|
1670
|
+
...base,
|
|
1671
|
+
covered: reason === void 0,
|
|
1672
|
+
executable,
|
|
1673
|
+
current,
|
|
1674
|
+
...reason ? { reason } : {}
|
|
1675
|
+
};
|
|
1294
1676
|
}
|
|
1295
1677
|
function projectHooksPathIsConfigured(gitRoot) {
|
|
1296
1678
|
for (const scope of ["--worktree", "--local"]) {
|
|
@@ -1307,45 +1689,53 @@ function projectHooksPathIsConfigured(gitRoot) {
|
|
|
1307
1689
|
}
|
|
1308
1690
|
return false;
|
|
1309
1691
|
}
|
|
1310
|
-
function
|
|
1311
|
-
const
|
|
1312
|
-
if (
|
|
1313
|
-
|
|
1314
|
-
return uninstallEffectivePostCommitHook(gitRoot);
|
|
1692
|
+
function uninstallProjectGitHook(hookName, cwd = process.cwd()) {
|
|
1693
|
+
const target = projectGitHookTarget(hookName, cwd);
|
|
1694
|
+
if (target.location === "external") {
|
|
1695
|
+
return { path: target.hookPath, changed: false, removedFile: false, skipped: "external" };
|
|
1315
1696
|
}
|
|
1316
|
-
|
|
1317
|
-
cwd: gitRoot,
|
|
1318
|
-
encoding: "utf8",
|
|
1319
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
1320
|
-
timeout: GIT_TIMEOUT_MS
|
|
1321
|
-
}).replace(/\r?\n$/u, "");
|
|
1322
|
-
return uninstallPostCommitHookAtPath(
|
|
1323
|
-
resolve3(safeGitPath(gitRoot, commonDir), "hooks", "post-commit")
|
|
1324
|
-
);
|
|
1697
|
+
return uninstallTarget(target, managedHookSpec(hookName));
|
|
1325
1698
|
}
|
|
1326
|
-
function
|
|
1699
|
+
function projectGitHookTarget(hookName, cwd = process.cwd()) {
|
|
1327
1700
|
const gitRoot = gitToplevel(cwd);
|
|
1328
1701
|
if (!gitRoot) throw new Error("not a git repository");
|
|
1329
|
-
if (projectHooksPathIsConfigured(gitRoot))
|
|
1330
|
-
|
|
1331
|
-
|
|
1702
|
+
if (projectHooksPathIsConfigured(gitRoot)) return resolveEffectiveGitHook(hookName, gitRoot);
|
|
1703
|
+
return {
|
|
1704
|
+
...explicitTarget(resolve3(projectHooksDir(gitRoot), hookName), {}),
|
|
1705
|
+
gitRoot,
|
|
1706
|
+
location: "repository"
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
function projectHooksDir(gitRoot) {
|
|
1332
1710
|
const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
1333
1711
|
cwd: gitRoot,
|
|
1334
1712
|
encoding: "utf8",
|
|
1335
1713
|
stdio: ["ignore", "pipe", "ignore"],
|
|
1336
1714
|
timeout: GIT_TIMEOUT_MS
|
|
1337
1715
|
}).replace(/\r?\n$/u, "");
|
|
1338
|
-
return
|
|
1339
|
-
|
|
1340
|
-
|
|
1716
|
+
return resolve3(safeGitPath(gitRoot, commonDir), "hooks");
|
|
1717
|
+
}
|
|
1718
|
+
function uninstallGitHookAtPath(hookName, hookPath, options = {}) {
|
|
1719
|
+
return uninstallTarget(explicitTarget(hookPath, options), managedHookSpec(hookName));
|
|
1341
1720
|
}
|
|
1342
1721
|
function uninstallTarget(target, spec) {
|
|
1722
|
+
const unchanged = { path: target.hookPath, changed: false, removedFile: false };
|
|
1343
1723
|
const stat = assertSafeFile(target.hookPath, spec);
|
|
1344
|
-
if (!stat) return
|
|
1724
|
+
if (!stat) return unchanged;
|
|
1345
1725
|
const existing = readHookFile(target.hookPath);
|
|
1726
|
+
const remove = () => {
|
|
1727
|
+
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1728
|
+
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1729
|
+
};
|
|
1730
|
+
const rewrite = (next) => {
|
|
1731
|
+
rewriteHookAtomically(target.hookPath, next, existing, spec, stat);
|
|
1732
|
+
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1733
|
+
};
|
|
1346
1734
|
const range = blockRange(existing, spec);
|
|
1347
1735
|
if (range.kind === "absent") {
|
|
1348
|
-
|
|
1736
|
+
const text = existing.toString("utf8");
|
|
1737
|
+
if (spec.hookName === "pre-commit" && isOwnedStandalonePreCommit(text)) return remove();
|
|
1738
|
+
if (spec.hookName === "post-commit" && text.startsWith(`#!/bin/sh
|
|
1349
1739
|
${LEGACY_PRIM_OWNED_HEADER}
|
|
1350
1740
|
`)) {
|
|
1351
1741
|
const prefixLength = legacyProjectPrefixLength(existing);
|
|
@@ -1353,20 +1743,9 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
1353
1743
|
throw new Error("unrecognized legacy Prim post-commit invocation");
|
|
1354
1744
|
}
|
|
1355
1745
|
const tail = existing.subarray(prefixLength);
|
|
1356
|
-
|
|
1357
|
-
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1358
|
-
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1359
|
-
}
|
|
1360
|
-
rewriteHookAtomically(
|
|
1361
|
-
target.hookPath,
|
|
1362
|
-
Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]),
|
|
1363
|
-
existing,
|
|
1364
|
-
spec,
|
|
1365
|
-
stat
|
|
1366
|
-
);
|
|
1367
|
-
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1746
|
+
return tail.length === 0 ? remove() : rewrite(Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]));
|
|
1368
1747
|
}
|
|
1369
|
-
return
|
|
1748
|
+
return unchanged;
|
|
1370
1749
|
}
|
|
1371
1750
|
const shebangEnd = shellInsertionPoint(existing, target.kind === "husky_v9", spec);
|
|
1372
1751
|
const suffix = existing.subarray(range.end);
|
|
@@ -1375,77 +1754,16 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
1375
1754
|
`);
|
|
1376
1755
|
if (range.start === shebangEnd && suffix.subarray(0, createdSuffix.length).equals(createdSuffix)) {
|
|
1377
1756
|
const tail = suffix.subarray(createdSuffix.length);
|
|
1378
|
-
|
|
1379
|
-
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1380
|
-
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1381
|
-
}
|
|
1382
|
-
rewriteHookAtomically(
|
|
1383
|
-
target.hookPath,
|
|
1384
|
-
Buffer.concat([existing.subarray(0, shebangEnd), tail]),
|
|
1385
|
-
existing,
|
|
1386
|
-
spec,
|
|
1387
|
-
stat
|
|
1388
|
-
);
|
|
1389
|
-
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1757
|
+
return tail.length === 0 ? remove() : rewrite(Buffer.concat([existing.subarray(0, shebangEnd), tail]));
|
|
1390
1758
|
}
|
|
1391
|
-
const
|
|
1392
|
-
Buffer.from(`#!/bin/sh
|
|
1393
|
-
# ${spec.createdMark}
|
|
1394
|
-
|
|
1395
|
-
`),
|
|
1396
|
-
...spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName ? [Buffer.from(`#!/bin/sh
|
|
1397
|
-
# ${LEGACY_PRIM_CREATED_MARK}
|
|
1398
|
-
|
|
1399
|
-
`)] : []
|
|
1400
|
-
];
|
|
1401
|
-
const createdPrefix = createdPrefixes.find(
|
|
1759
|
+
const createdPrefix = oldCreatedPrefixes(spec).find(
|
|
1402
1760
|
(prefix) => range.start === prefix.length && existing.subarray(0, prefix.length).equals(prefix)
|
|
1403
1761
|
);
|
|
1404
1762
|
if (createdPrefix) {
|
|
1405
1763
|
const tail = suffix.at(0) === 10 ? suffix.subarray(1) : suffix;
|
|
1406
|
-
|
|
1407
|
-
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1408
|
-
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1409
|
-
}
|
|
1410
|
-
rewriteHookAtomically(
|
|
1411
|
-
target.hookPath,
|
|
1412
|
-
Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]),
|
|
1413
|
-
existing,
|
|
1414
|
-
spec,
|
|
1415
|
-
stat
|
|
1416
|
-
);
|
|
1417
|
-
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1764
|
+
return tail.length === 0 ? remove() : rewrite(Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]));
|
|
1418
1765
|
}
|
|
1419
|
-
|
|
1420
|
-
existing.subarray(0, range.start),
|
|
1421
|
-
range.start === shebangEnd && suffix.at(0) === 10 ? suffix.subarray(1) : suffix
|
|
1422
|
-
]);
|
|
1423
|
-
rewriteHookAtomically(target.hookPath, next, existing, spec, stat);
|
|
1424
|
-
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1425
|
-
}
|
|
1426
|
-
function uninstallPostCommitHookAtPath(hookPath) {
|
|
1427
|
-
const absolute = resolve3(hookPath);
|
|
1428
|
-
return uninstallTarget(
|
|
1429
|
-
{
|
|
1430
|
-
gitRoot: dirname3(dirname3(absolute)),
|
|
1431
|
-
hooksDir: dirname3(absolute),
|
|
1432
|
-
hookPath: absolute,
|
|
1433
|
-
kind: "direct"
|
|
1434
|
-
},
|
|
1435
|
-
POST_COMMIT_MANAGED_HOOK
|
|
1436
|
-
);
|
|
1437
|
-
}
|
|
1438
|
-
function uninstallPostRewriteHookAtPath(hookPath) {
|
|
1439
|
-
const absolute = resolve3(hookPath);
|
|
1440
|
-
return uninstallTarget(
|
|
1441
|
-
{
|
|
1442
|
-
gitRoot: dirname3(dirname3(absolute)),
|
|
1443
|
-
hooksDir: dirname3(absolute),
|
|
1444
|
-
hookPath: absolute,
|
|
1445
|
-
kind: "direct"
|
|
1446
|
-
},
|
|
1447
|
-
POST_REWRITE_MANAGED_HOOK
|
|
1448
|
-
);
|
|
1766
|
+
return rewrite(withoutRange(existing, range.start, range.end));
|
|
1449
1767
|
}
|
|
1450
1768
|
|
|
1451
1769
|
// src/lib/repository-binding.ts
|
|
@@ -1537,18 +1855,19 @@ function kickDaemonEnsure(options = {}) {
|
|
|
1537
1855
|
|
|
1538
1856
|
export {
|
|
1539
1857
|
warmBinCache,
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1858
|
+
isLegacyOwnedGlobalHook,
|
|
1859
|
+
gitHooksMode,
|
|
1860
|
+
isOwnedStandalonePreCommit,
|
|
1861
|
+
externalHookRemedy,
|
|
1862
|
+
resolveEffectiveGitHook,
|
|
1863
|
+
ensureEffectiveGitHook,
|
|
1864
|
+
hasCurrentHookBlock,
|
|
1865
|
+
ensureGitHookAtPath,
|
|
1866
|
+
inspectEffectiveGitHook,
|
|
1867
|
+
uninstallProjectGitHook,
|
|
1868
|
+
projectGitHookTarget,
|
|
1869
|
+
projectHooksDir,
|
|
1870
|
+
uninstallGitHookAtPath,
|
|
1552
1871
|
resolveRepositoryBinding,
|
|
1553
1872
|
bindRepositoryWithClient,
|
|
1554
1873
|
bindRepository,
|