@primitive.ai/prim 0.1.0-alpha.64 → 0.1.0-alpha.66
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 +5 -4
- package/dist/{chunk-VYGY3OOX.js → chunk-BDGQEJA5.js} +1 -1
- package/dist/{chunk-7X7SFOPJ.js → chunk-CPSE3Y3T.js} +63 -1
- package/dist/{chunk-LNU44OJV.js → chunk-CZ24W5QU.js} +2 -1
- package/dist/{chunk-MCPHIB4W.js → chunk-DR66XM5L.js} +250 -96
- package/dist/{chunk-XTNMSS4C.js → chunk-TDRXHBEX.js} +15 -0
- package/dist/hooks/post-commit.js +2 -2
- package/dist/hooks/post-rewrite.js +194 -0
- package/dist/hooks/post-tool-use.js +7 -7
- package/dist/hooks/pre-tool-use.js +2 -2
- package/dist/hooks/prim-hook.js +17 -17
- package/dist/hooks/session-start.js +8 -3
- package/dist/index.js +156 -70
- package/package.json +5 -4
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
clearRepoSyncId,
|
|
2
3
|
gitToplevel,
|
|
3
4
|
githubRepositoryFullName,
|
|
4
5
|
isValidRepoSyncId,
|
|
5
6
|
setRepoSyncId
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TDRXHBEX.js";
|
|
7
8
|
import {
|
|
9
|
+
HttpError,
|
|
8
10
|
getClient,
|
|
9
11
|
withFileLock
|
|
10
12
|
} from "./chunk-CPX3BUOC.js";
|
|
@@ -614,6 +616,9 @@ import { basename, dirname as dirname3, isAbsolute as isAbsolute2, resolve as re
|
|
|
614
616
|
var PRIM_POST_COMMIT_BLOCK_START = "# >>> prim post-commit hook >>>";
|
|
615
617
|
var PRIM_POST_COMMIT_BLOCK_END = "# <<< prim post-commit hook <<<";
|
|
616
618
|
var PRIM_CREATED_MARK = "prim-created-post-commit-hook";
|
|
619
|
+
var PRIM_POST_REWRITE_BLOCK_START = "# >>> prim post-rewrite hook >>>";
|
|
620
|
+
var PRIM_POST_REWRITE_BLOCK_END = "# <<< prim post-rewrite hook <<<";
|
|
621
|
+
var PRIM_POST_REWRITE_CREATED_MARK = "prim-created-post-rewrite-hook";
|
|
617
622
|
var LEGACY_PRIM_CREATED_MARK = "prim-created-hook";
|
|
618
623
|
var LEGACY_PRIM_OWNED_HEADER = "# prim post-commit hook \u2014 installed by: prim hooks install (prim-managed-hook)";
|
|
619
624
|
var LEGACY_GLOBAL_OWNED_HEADER = "# prim global post-commit hook (core.hooksPath) \u2014 managed by prim; do not edit.";
|
|
@@ -682,6 +687,67 @@ ${PRIM_POST_COMMIT_BLOCK_END}`;
|
|
|
682
687
|
function postCommitHookBlock() {
|
|
683
688
|
return currentPostCommitBlock();
|
|
684
689
|
}
|
|
690
|
+
function postRewriteHookBlock() {
|
|
691
|
+
return `${PRIM_POST_REWRITE_BLOCK_START}
|
|
692
|
+
if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
693
|
+
case "$1" in amend|rebase) prim_rewrite_source="$1" ;; *) prim_rewrite_source= ;; esac
|
|
694
|
+
if [ -n "$prim_rewrite_source" ]; then
|
|
695
|
+
prim_rewrite_pairs_file=$(mktemp "\${TMPDIR:-/tmp}/prim-post-rewrite-pairs.XXXXXXXX" 2>/dev/null) || prim_rewrite_pairs_file=
|
|
696
|
+
if [ -n "$prim_rewrite_pairs_file" ] && ! chmod 600 "$prim_rewrite_pairs_file" 2>/dev/null; then
|
|
697
|
+
rm -f "$prim_rewrite_pairs_file"
|
|
698
|
+
prim_rewrite_pairs_file=
|
|
699
|
+
fi
|
|
700
|
+
if [ -n "$prim_rewrite_pairs_file" ]; then
|
|
701
|
+
if cat > "$prim_rewrite_pairs_file"; then
|
|
702
|
+
exec < "$prim_rewrite_pairs_file"
|
|
703
|
+
prim_rewrite_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_rewrite_branch=
|
|
704
|
+
prim_cache_dir="\${XDG_CACHE_HOME:-$HOME/.cache}/prim/bin"
|
|
705
|
+
prim_post_rewrite_ran=0
|
|
706
|
+
if [ "\${PRIM_BIN_CACHE:-1}" != "0" ] && [ -f "$prim_cache_dir/prim-post-rewrite" ] && [ -f "$prim_cache_dir/node" ] && [ -n "$(find "$prim_cache_dir/prim-post-rewrite" -mmin "-\${PRIM_BIN_CACHE_TTL_MIN:-1440}" 2>/dev/null)" ]; then
|
|
707
|
+
prim_node=$(cat "$prim_cache_dir/node")
|
|
708
|
+
prim_entry=$(cat "$prim_cache_dir/prim-post-rewrite")
|
|
709
|
+
if [ -x "$prim_node" ] && [ -f "$prim_entry" ]; then
|
|
710
|
+
( trap '' HUP; trap 'if [ -n "$prim_rewrite_pairs_file" ]; then rm -f "$prim_rewrite_pairs_file"; fi' 0; export PRIM_REWRITE_SOURCE="$prim_rewrite_source" PRIM_REWRITE_BRANCH="$prim_rewrite_branch" PRIM_REWRITE_PAIRS_FILE="$prim_rewrite_pairs_file"; "$prim_node" "$prim_entry" ) </dev/null >/dev/null 2>&1 &
|
|
711
|
+
prim_post_rewrite_ran=1
|
|
712
|
+
fi
|
|
713
|
+
fi
|
|
714
|
+
if [ "$prim_post_rewrite_ran" -eq 0 ]; then
|
|
715
|
+
if [ -x "./node_modules/.bin/prim-post-rewrite" ]; then
|
|
716
|
+
( trap '' HUP; trap 'if [ -n "$prim_rewrite_pairs_file" ]; then rm -f "$prim_rewrite_pairs_file"; fi' 0; export PRIM_REWRITE_SOURCE="$prim_rewrite_source" PRIM_REWRITE_BRANCH="$prim_rewrite_branch" PRIM_REWRITE_PAIRS_FILE="$prim_rewrite_pairs_file"; ./node_modules/.bin/prim-post-rewrite ) </dev/null >/dev/null 2>&1 &
|
|
717
|
+
prim_post_rewrite_ran=1
|
|
718
|
+
elif command -v npx >/dev/null 2>&1; then
|
|
719
|
+
( trap '' HUP; trap 'if [ -n "$prim_rewrite_pairs_file" ]; then rm -f "$prim_rewrite_pairs_file"; fi' 0; export PRIM_REWRITE_SOURCE="$prim_rewrite_source" PRIM_REWRITE_BRANCH="$prim_rewrite_branch" PRIM_REWRITE_PAIRS_FILE="$prim_rewrite_pairs_file"; npx --yes -p @primitive.ai/prim@latest prim-post-rewrite ) </dev/null >/dev/null 2>&1 &
|
|
720
|
+
prim_post_rewrite_ran=1
|
|
721
|
+
fi
|
|
722
|
+
fi
|
|
723
|
+
if [ "$prim_post_rewrite_ran" -eq 0 ]; then
|
|
724
|
+
rm -f "$prim_rewrite_pairs_file"
|
|
725
|
+
fi
|
|
726
|
+
unset prim_cache_dir prim_post_rewrite_ran prim_node prim_entry prim_rewrite_branch
|
|
727
|
+
else
|
|
728
|
+
rm -f "$prim_rewrite_pairs_file"
|
|
729
|
+
fi
|
|
730
|
+
unset prim_rewrite_pairs_file
|
|
731
|
+
fi
|
|
732
|
+
fi
|
|
733
|
+
unset prim_rewrite_source
|
|
734
|
+
fi
|
|
735
|
+
${PRIM_POST_REWRITE_BLOCK_END}`;
|
|
736
|
+
}
|
|
737
|
+
var POST_COMMIT_MANAGED_HOOK = {
|
|
738
|
+
hookName: "post-commit",
|
|
739
|
+
blockStart: PRIM_POST_COMMIT_BLOCK_START,
|
|
740
|
+
blockEnd: PRIM_POST_COMMIT_BLOCK_END,
|
|
741
|
+
createdMark: PRIM_CREATED_MARK,
|
|
742
|
+
block: postCommitHookBlock
|
|
743
|
+
};
|
|
744
|
+
var POST_REWRITE_MANAGED_HOOK = {
|
|
745
|
+
hookName: "post-rewrite",
|
|
746
|
+
blockStart: PRIM_POST_REWRITE_BLOCK_START,
|
|
747
|
+
blockEnd: PRIM_POST_REWRITE_BLOCK_END,
|
|
748
|
+
createdMark: PRIM_POST_REWRITE_CREATED_MARK,
|
|
749
|
+
block: postRewriteHookBlock
|
|
750
|
+
};
|
|
685
751
|
function legacyFloatingInvocation() {
|
|
686
752
|
return `if command -v prim-post-commit >/dev/null 2>&1; then
|
|
687
753
|
prim-post-commit || true
|
|
@@ -737,10 +803,10 @@ function isKnownLegacyGlobalGate(value) {
|
|
|
737
803
|
const suffix = Buffer.from("\nfi\n");
|
|
738
804
|
return value.subarray(0, prefix.length).equals(prefix) && value.subarray(-suffix.length).equals(suffix) && isPinnedInvocation(value.subarray(prefix.length, -suffix.length).toString("utf8"));
|
|
739
805
|
}
|
|
740
|
-
function createdScaffold() {
|
|
806
|
+
function createdScaffold(spec) {
|
|
741
807
|
return Buffer.from(`#!/bin/sh
|
|
742
|
-
${
|
|
743
|
-
# ${
|
|
808
|
+
${spec.block()}
|
|
809
|
+
# ${spec.createdMark}
|
|
744
810
|
`);
|
|
745
811
|
}
|
|
746
812
|
function safeGitPath(root, value) {
|
|
@@ -749,7 +815,7 @@ function safeGitPath(root, value) {
|
|
|
749
815
|
}
|
|
750
816
|
return isAbsolute2(value) ? resolve3(value) : resolve3(root, value);
|
|
751
817
|
}
|
|
752
|
-
function
|
|
818
|
+
function resolveEffectiveManagedHook(spec, cwd = process.cwd()) {
|
|
753
819
|
const gitRoot = gitToplevel(cwd);
|
|
754
820
|
if (!gitRoot) throw new Error("not a git repository");
|
|
755
821
|
const value = execFileSync("git", ["rev-parse", "--git-path", "hooks"], {
|
|
@@ -763,42 +829,49 @@ function resolveEffectivePostCommitHook(cwd = process.cwd()) {
|
|
|
763
829
|
return {
|
|
764
830
|
gitRoot,
|
|
765
831
|
hooksDir,
|
|
766
|
-
hookPath: resolve3(dirname3(hooksDir),
|
|
832
|
+
hookPath: resolve3(dirname3(hooksDir), spec.hookName),
|
|
767
833
|
kind: "husky_v9",
|
|
768
|
-
dispatcherPath: resolve3(hooksDir,
|
|
834
|
+
dispatcherPath: resolve3(hooksDir, spec.hookName)
|
|
769
835
|
};
|
|
770
836
|
}
|
|
771
837
|
return {
|
|
772
838
|
gitRoot,
|
|
773
839
|
hooksDir,
|
|
774
|
-
hookPath: resolve3(hooksDir,
|
|
840
|
+
hookPath: resolve3(hooksDir, spec.hookName),
|
|
775
841
|
kind: "direct"
|
|
776
842
|
};
|
|
777
843
|
}
|
|
778
|
-
function
|
|
844
|
+
function resolveEffectivePostCommitHook(cwd = process.cwd()) {
|
|
845
|
+
return resolveEffectiveManagedHook(POST_COMMIT_MANAGED_HOOK, cwd);
|
|
846
|
+
}
|
|
847
|
+
function resolveEffectivePostRewriteHook(cwd = process.cwd()) {
|
|
848
|
+
return resolveEffectiveManagedHook(POST_REWRITE_MANAGED_HOOK, cwd);
|
|
849
|
+
}
|
|
850
|
+
function assertSafeFile(path, spec) {
|
|
779
851
|
if (!existsSync3(path)) return void 0;
|
|
780
852
|
const stat = lstatSync3(path);
|
|
781
853
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_HOOK_BYTES) {
|
|
782
|
-
throw new Error(`unsafe
|
|
854
|
+
throw new Error(`unsafe ${spec.hookName} hook target: ${path}`);
|
|
783
855
|
}
|
|
784
856
|
return stat;
|
|
785
857
|
}
|
|
786
|
-
function assertUsableHuskyDispatcher(target) {
|
|
858
|
+
function assertUsableHuskyDispatcher(target, spec) {
|
|
787
859
|
if (!target.dispatcherPath) return;
|
|
788
|
-
const stat = assertSafeFile(target.dispatcherPath);
|
|
860
|
+
const stat = assertSafeFile(target.dispatcherPath, spec);
|
|
789
861
|
if (!stat || (stat.mode & 64) === 0) {
|
|
790
862
|
throw new Error(
|
|
791
|
-
`Husky
|
|
863
|
+
`Husky ${spec.hookName} dispatcher is missing or not executable: ${target.dispatcherPath}`
|
|
792
864
|
);
|
|
793
865
|
}
|
|
794
|
-
const dispatcher = decodeHookText(readHookFile(target.dispatcherPath));
|
|
795
|
-
|
|
866
|
+
const dispatcher = decodeHookText(readHookFile(target.dispatcherPath), spec);
|
|
867
|
+
const directDispatcher = HUSKY_DIRECT_DISPATCHER.replaceAll("post-commit", spec.hookName);
|
|
868
|
+
if (dispatcher === directDispatcher) return;
|
|
796
869
|
const normalizedDispatcher = dispatcher.endsWith("\n") ? dispatcher.slice(0, -1) : dispatcher;
|
|
797
870
|
if (!HUSKY_V9_DISPATCHERS.has(normalizedDispatcher)) {
|
|
798
|
-
throw new Error(`unrecognized Husky
|
|
871
|
+
throw new Error(`unrecognized Husky ${spec.hookName} dispatcher: ${target.dispatcherPath}`);
|
|
799
872
|
}
|
|
800
873
|
const huskyRuntime = resolve3(dirname3(target.dispatcherPath), "h");
|
|
801
|
-
const runtimeStat = assertSafeFile(huskyRuntime);
|
|
874
|
+
const runtimeStat = assertSafeFile(huskyRuntime, spec);
|
|
802
875
|
const runtime = runtimeStat ? readHookFile(huskyRuntime) : void 0;
|
|
803
876
|
const runtimeHash = runtime ? createHash2("sha256").update(runtime).digest("hex") : void 0;
|
|
804
877
|
if (!runtimeHash || !HUSKY_V9_RUNTIME_HASHES.has(runtimeHash)) {
|
|
@@ -809,28 +882,28 @@ function readHookFile(path) {
|
|
|
809
882
|
const value = readFileSync3(path);
|
|
810
883
|
return Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
811
884
|
}
|
|
812
|
-
function decodeHookText(content) {
|
|
813
|
-
if (content.includes(0)) throw new Error(
|
|
885
|
+
function decodeHookText(content, spec) {
|
|
886
|
+
if (content.includes(0)) throw new Error(`binary ${spec.hookName} hook`);
|
|
814
887
|
try {
|
|
815
888
|
return new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
816
889
|
} catch {
|
|
817
|
-
throw new Error(
|
|
890
|
+
throw new Error(`binary ${spec.hookName} hook`);
|
|
818
891
|
}
|
|
819
892
|
}
|
|
820
|
-
function supportedShebangEnd(content) {
|
|
821
|
-
const text = decodeHookText(content);
|
|
893
|
+
function supportedShebangEnd(content, spec) {
|
|
894
|
+
const text = decodeHookText(content, spec);
|
|
822
895
|
const newline = text.indexOf("\n");
|
|
823
896
|
const shebang = newline === -1 ? text : text.slice(0, newline);
|
|
824
897
|
if (!SUPPORTED_SHEBANGS.has(shebang) || newline === -1) {
|
|
825
|
-
throw new Error(
|
|
898
|
+
throw new Error(`unsupported ${spec.hookName} hook interpreter`);
|
|
826
899
|
}
|
|
827
900
|
return Buffer.byteLength(text.slice(0, newline + 1), "utf8");
|
|
828
901
|
}
|
|
829
|
-
function shellInsertionPoint(content, allowShebangless) {
|
|
830
|
-
const text = decodeHookText(content);
|
|
831
|
-
if (text.startsWith("#!")) return supportedShebangEnd(content);
|
|
902
|
+
function shellInsertionPoint(content, allowShebangless, spec) {
|
|
903
|
+
const text = decodeHookText(content, spec);
|
|
904
|
+
if (text.startsWith("#!")) return supportedShebangEnd(content, spec);
|
|
832
905
|
if (allowShebangless) return 0;
|
|
833
|
-
throw new Error(
|
|
906
|
+
throw new Error(`unsupported ${spec.hookName} hook interpreter`);
|
|
834
907
|
}
|
|
835
908
|
function allIndexes(haystack, needle) {
|
|
836
909
|
const indexes = [];
|
|
@@ -843,28 +916,28 @@ function allIndexes(haystack, needle) {
|
|
|
843
916
|
}
|
|
844
917
|
return indexes;
|
|
845
918
|
}
|
|
846
|
-
function blockRange(content) {
|
|
847
|
-
decodeHookText(content);
|
|
848
|
-
const startMarker = Buffer.from(
|
|
849
|
-
const endMarker = Buffer.from(
|
|
919
|
+
function blockRange(content, spec) {
|
|
920
|
+
decodeHookText(content, spec);
|
|
921
|
+
const startMarker = Buffer.from(spec.blockStart);
|
|
922
|
+
const endMarker = Buffer.from(spec.blockEnd);
|
|
850
923
|
const starts = allIndexes(content, startMarker);
|
|
851
924
|
const ends = allIndexes(content, endMarker);
|
|
852
925
|
if (starts.length === 0 && ends.length === 0) return { kind: "absent" };
|
|
853
926
|
if (starts.length !== 1 || ends.length !== 1 || ends[0] < starts[0]) {
|
|
854
|
-
throw new Error(
|
|
927
|
+
throw new Error(`malformed Prim ${spec.hookName} markers`);
|
|
855
928
|
}
|
|
856
929
|
const start = starts[0];
|
|
857
930
|
const end = ends[0] + endMarker.length;
|
|
858
|
-
const current = Buffer.from(
|
|
931
|
+
const current = Buffer.from(spec.block());
|
|
859
932
|
return content.subarray(start, end).equals(current) ? { kind: "current", start, end } : { kind: "stale", start, end };
|
|
860
933
|
}
|
|
861
|
-
function mergedContent(existing, allowShebangless) {
|
|
862
|
-
const block = Buffer.from(
|
|
934
|
+
function mergedContent(existing, allowShebangless, spec) {
|
|
935
|
+
const block = Buffer.from(spec.block());
|
|
863
936
|
if (!existing) {
|
|
864
|
-
return createdScaffold();
|
|
937
|
+
return createdScaffold(spec);
|
|
865
938
|
}
|
|
866
|
-
const range = blockRange(existing);
|
|
867
|
-
if (range.kind === "absent" && existing.toString("utf8").startsWith(`#!/bin/sh
|
|
939
|
+
const range = blockRange(existing, spec);
|
|
940
|
+
if (spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName && range.kind === "absent" && existing.toString("utf8").startsWith(`#!/bin/sh
|
|
868
941
|
${LEGACY_GLOBAL_OWNED_HEADER}
|
|
869
942
|
`)) {
|
|
870
943
|
const gateAt = existing.indexOf(Buffer.from('if [ "$(git config --get prim.active'));
|
|
@@ -879,7 +952,7 @@ ${LEGACY_GLOBAL_OWNED_HEADER}
|
|
|
879
952
|
existing.subarray(0, gateAt),
|
|
880
953
|
existing.subarray(chainAt)
|
|
881
954
|
]);
|
|
882
|
-
const shebangEnd2 = shellInsertionPoint(withoutLegacyGate, allowShebangless);
|
|
955
|
+
const shebangEnd2 = shellInsertionPoint(withoutLegacyGate, allowShebangless, spec);
|
|
883
956
|
return Buffer.concat([
|
|
884
957
|
withoutLegacyGate.subarray(0, shebangEnd2),
|
|
885
958
|
block,
|
|
@@ -887,25 +960,25 @@ ${LEGACY_GLOBAL_OWNED_HEADER}
|
|
|
887
960
|
withoutLegacyGate.subarray(shebangEnd2)
|
|
888
961
|
]);
|
|
889
962
|
}
|
|
890
|
-
if (range.kind === "absent" && existing.toString("utf8").startsWith(`#!/bin/sh
|
|
963
|
+
if (spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName && range.kind === "absent" && existing.toString("utf8").startsWith(`#!/bin/sh
|
|
891
964
|
${LEGACY_PRIM_OWNED_HEADER}
|
|
892
965
|
`)) {
|
|
893
966
|
const prefixLength = legacyProjectPrefixLength(existing);
|
|
894
967
|
if (prefixLength === void 0) {
|
|
895
968
|
throw new Error("unrecognized legacy Prim post-commit invocation");
|
|
896
969
|
}
|
|
897
|
-
return Buffer.concat([createdScaffold(), existing.subarray(prefixLength)]);
|
|
970
|
+
return Buffer.concat([createdScaffold(spec), existing.subarray(prefixLength)]);
|
|
898
971
|
}
|
|
899
972
|
if (range.kind !== "absent") {
|
|
900
973
|
const oldCreatedPrefixes = [
|
|
901
974
|
Buffer.from(`#!/bin/sh
|
|
902
|
-
# ${
|
|
975
|
+
# ${spec.createdMark}
|
|
903
976
|
|
|
904
977
|
`),
|
|
905
|
-
Buffer.from(`#!/bin/sh
|
|
978
|
+
...spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName ? [Buffer.from(`#!/bin/sh
|
|
906
979
|
# ${LEGACY_PRIM_CREATED_MARK}
|
|
907
980
|
|
|
908
|
-
`)
|
|
981
|
+
`)] : []
|
|
909
982
|
];
|
|
910
983
|
const oldCreated = oldCreatedPrefixes.some(
|
|
911
984
|
(prefix) => range.start === prefix.length && existing.subarray(0, prefix.length).equals(prefix)
|
|
@@ -913,10 +986,10 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
913
986
|
if (oldCreated) {
|
|
914
987
|
const suffix = existing.subarray(range.end);
|
|
915
988
|
const tail = suffix.at(0) === 10 ? suffix.subarray(1) : suffix;
|
|
916
|
-
return Buffer.concat([createdScaffold(), tail]);
|
|
989
|
+
return Buffer.concat([createdScaffold(spec), tail]);
|
|
917
990
|
}
|
|
918
991
|
}
|
|
919
|
-
const shebangEnd = shellInsertionPoint(existing, allowShebangless);
|
|
992
|
+
const shebangEnd = shellInsertionPoint(existing, allowShebangless, spec);
|
|
920
993
|
if (range.kind === "current" && range.start === shebangEnd) return existing;
|
|
921
994
|
let withoutBlock = existing;
|
|
922
995
|
if (range.kind !== "absent") {
|
|
@@ -936,22 +1009,22 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
936
1009
|
function errorCode(error) {
|
|
937
1010
|
return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
938
1011
|
}
|
|
939
|
-
function assertTargetUnchanged(path, expected, stat) {
|
|
1012
|
+
function assertTargetUnchanged(path, expected, spec, stat) {
|
|
940
1013
|
let current;
|
|
941
1014
|
try {
|
|
942
1015
|
current = lstatSync3(path);
|
|
943
1016
|
} catch (error) {
|
|
944
1017
|
if (expected === void 0 && errorCode(error) === "ENOENT") return;
|
|
945
|
-
throw new Error(
|
|
1018
|
+
throw new Error(`${spec.hookName} hook changed concurrently: ${path}`);
|
|
946
1019
|
}
|
|
947
1020
|
if (expected === void 0) {
|
|
948
|
-
throw new Error(
|
|
1021
|
+
throw new Error(`${spec.hookName} hook appeared concurrently: ${path}`);
|
|
949
1022
|
}
|
|
950
1023
|
if (!current.isFile() || current.isSymbolicLink() || current.size > MAX_HOOK_BYTES || current.dev !== stat?.dev || current.ino !== stat?.ino || (current.mode & 4095) !== ((stat?.mode ?? 0) & 4095) || !readHookFile(path).equals(expected)) {
|
|
951
|
-
throw new Error(
|
|
1024
|
+
throw new Error(`${spec.hookName} hook changed concurrently: ${path}`);
|
|
952
1025
|
}
|
|
953
1026
|
}
|
|
954
|
-
function rewriteHookAtomically(path, next, expected, stat) {
|
|
1027
|
+
function rewriteHookAtomically(path, next, expected, spec, stat) {
|
|
955
1028
|
const parent = dirname3(path);
|
|
956
1029
|
mkdirSync2(parent, { recursive: true });
|
|
957
1030
|
const temporary = resolve3(
|
|
@@ -970,7 +1043,7 @@ function rewriteHookAtomically(path, next, expected, stat) {
|
|
|
970
1043
|
fsyncSync2(fd);
|
|
971
1044
|
closeSync3(fd);
|
|
972
1045
|
fd = void 0;
|
|
973
|
-
assertTargetUnchanged(path, expected, stat);
|
|
1046
|
+
assertTargetUnchanged(path, expected, spec, stat);
|
|
974
1047
|
renameSync2(temporary, path);
|
|
975
1048
|
} finally {
|
|
976
1049
|
if (fd !== void 0) {
|
|
@@ -987,32 +1060,36 @@ function rewriteHookAtomically(path, next, expected, stat) {
|
|
|
987
1060
|
}
|
|
988
1061
|
}
|
|
989
1062
|
}
|
|
990
|
-
function unlinkHookUnchanged(path, expected, stat) {
|
|
991
|
-
assertTargetUnchanged(path, expected, stat);
|
|
1063
|
+
function unlinkHookUnchanged(path, expected, stat, spec) {
|
|
1064
|
+
assertTargetUnchanged(path, expected, spec, stat);
|
|
992
1065
|
unlinkSync(path);
|
|
993
1066
|
}
|
|
994
1067
|
function ensureEffectivePostCommitHook(cwd = process.cwd()) {
|
|
995
1068
|
const target = resolveEffectivePostCommitHook(cwd);
|
|
996
|
-
return ensureTarget(target);
|
|
1069
|
+
return ensureTarget(target, POST_COMMIT_MANAGED_HOOK);
|
|
1070
|
+
}
|
|
1071
|
+
function ensureEffectivePostRewriteHook(cwd = process.cwd()) {
|
|
1072
|
+
const target = resolveEffectivePostRewriteHook(cwd);
|
|
1073
|
+
return ensureTarget(target, POST_REWRITE_MANAGED_HOOK);
|
|
997
1074
|
}
|
|
998
|
-
function ensureTarget(target, initialContent) {
|
|
999
|
-
assertUsableHuskyDispatcher(target);
|
|
1000
|
-
const stat = assertSafeFile(target.hookPath);
|
|
1075
|
+
function ensureTarget(target, spec, initialContent) {
|
|
1076
|
+
assertUsableHuskyDispatcher(target, spec);
|
|
1077
|
+
const stat = assertSafeFile(target.hookPath, spec);
|
|
1001
1078
|
if (stat && target.kind === "direct" && (stat.mode & 64) === 0) {
|
|
1002
|
-
throw new Error(`existing
|
|
1079
|
+
throw new Error(`existing ${spec.hookName} hook is not executable: ${target.hookPath}`);
|
|
1003
1080
|
}
|
|
1004
1081
|
const existing = stat ? readHookFile(target.hookPath) : void 0;
|
|
1005
|
-
const next = !existing && initialContent !== void 0 ? Buffer.from(initialContent) : mergedContent(existing, target.kind === "husky_v9");
|
|
1082
|
+
const next = !existing && initialContent !== void 0 ? Buffer.from(initialContent) : mergedContent(existing, target.kind === "husky_v9", spec);
|
|
1006
1083
|
if (!existing && initialContent !== void 0) {
|
|
1007
|
-
const shebangEnd = shellInsertionPoint(next, target.kind === "husky_v9");
|
|
1008
|
-
const range = blockRange(next);
|
|
1084
|
+
const shebangEnd = shellInsertionPoint(next, target.kind === "husky_v9", spec);
|
|
1085
|
+
const range = blockRange(next, spec);
|
|
1009
1086
|
if (range.kind !== "current" || range.start !== shebangEnd) {
|
|
1010
|
-
throw new Error(
|
|
1087
|
+
throw new Error(`invalid initial Prim ${spec.hookName} scaffold`);
|
|
1011
1088
|
}
|
|
1012
1089
|
}
|
|
1013
1090
|
const contentChanged = !existing || !next.equals(existing);
|
|
1014
1091
|
if (contentChanged) {
|
|
1015
|
-
rewriteHookAtomically(target.hookPath, next, existing, stat);
|
|
1092
|
+
rewriteHookAtomically(target.hookPath, next, existing, spec, stat);
|
|
1016
1093
|
}
|
|
1017
1094
|
return { path: target.hookPath, changed: contentChanged || !stat, kind: target.kind };
|
|
1018
1095
|
}
|
|
@@ -1025,14 +1102,35 @@ function ensurePostCommitHookAtPath(hookPath, initialContent) {
|
|
|
1025
1102
|
hookPath: absolute,
|
|
1026
1103
|
kind: "direct"
|
|
1027
1104
|
},
|
|
1105
|
+
POST_COMMIT_MANAGED_HOOK,
|
|
1106
|
+
initialContent
|
|
1107
|
+
);
|
|
1108
|
+
return { ...result, kind: "direct" };
|
|
1109
|
+
}
|
|
1110
|
+
function ensurePostRewriteHookAtPath(hookPath, initialContent) {
|
|
1111
|
+
const absolute = resolve3(hookPath);
|
|
1112
|
+
const result = ensureTarget(
|
|
1113
|
+
{
|
|
1114
|
+
gitRoot: dirname3(dirname3(absolute)),
|
|
1115
|
+
hooksDir: dirname3(absolute),
|
|
1116
|
+
hookPath: absolute,
|
|
1117
|
+
kind: "direct"
|
|
1118
|
+
},
|
|
1119
|
+
POST_REWRITE_MANAGED_HOOK,
|
|
1028
1120
|
initialContent
|
|
1029
1121
|
);
|
|
1030
1122
|
return { ...result, kind: "direct" };
|
|
1031
1123
|
}
|
|
1032
1124
|
function inspectEffectivePostCommitHook(cwd = process.cwd()) {
|
|
1033
|
-
|
|
1125
|
+
return inspectEffectiveManagedHook(POST_COMMIT_MANAGED_HOOK, cwd);
|
|
1126
|
+
}
|
|
1127
|
+
function inspectEffectivePostRewriteHook(cwd = process.cwd()) {
|
|
1128
|
+
return inspectEffectiveManagedHook(POST_REWRITE_MANAGED_HOOK, cwd);
|
|
1129
|
+
}
|
|
1130
|
+
function inspectEffectiveManagedHook(spec, cwd) {
|
|
1131
|
+
const target = resolveEffectiveManagedHook(spec, cwd);
|
|
1034
1132
|
try {
|
|
1035
|
-
assertUsableHuskyDispatcher(target);
|
|
1133
|
+
assertUsableHuskyDispatcher(target, spec);
|
|
1036
1134
|
} catch (error) {
|
|
1037
1135
|
const missing = error instanceof Error && (error.message.includes("missing or not executable") || error.message.includes("unsafe"));
|
|
1038
1136
|
return {
|
|
@@ -1045,7 +1143,7 @@ function inspectEffectivePostCommitHook(cwd = process.cwd()) {
|
|
|
1045
1143
|
}
|
|
1046
1144
|
let stat;
|
|
1047
1145
|
try {
|
|
1048
|
-
stat = assertSafeFile(target.hookPath);
|
|
1146
|
+
stat = assertSafeFile(target.hookPath, spec);
|
|
1049
1147
|
} catch {
|
|
1050
1148
|
return {
|
|
1051
1149
|
...target,
|
|
@@ -1067,8 +1165,8 @@ function inspectEffectivePostCommitHook(cwd = process.cwd()) {
|
|
|
1067
1165
|
const executable = target.kind === "husky_v9" || (stat.mode & 64) !== 0;
|
|
1068
1166
|
try {
|
|
1069
1167
|
const content = readHookFile(target.hookPath);
|
|
1070
|
-
const shebangEnd = shellInsertionPoint(content, target.kind === "husky_v9");
|
|
1071
|
-
const range = blockRange(content);
|
|
1168
|
+
const shebangEnd = shellInsertionPoint(content, target.kind === "husky_v9", spec);
|
|
1169
|
+
const range = blockRange(content, spec);
|
|
1072
1170
|
const positioned = range.kind !== "absent" && range.start === shebangEnd;
|
|
1073
1171
|
const current = range.kind === "current" && positioned;
|
|
1074
1172
|
return {
|
|
@@ -1092,7 +1190,11 @@ function inspectEffectivePostCommitHook(cwd = process.cwd()) {
|
|
|
1092
1190
|
}
|
|
1093
1191
|
function uninstallEffectivePostCommitHook(cwd = process.cwd()) {
|
|
1094
1192
|
const target = resolveEffectivePostCommitHook(cwd);
|
|
1095
|
-
return uninstallTarget(target);
|
|
1193
|
+
return uninstallTarget(target, POST_COMMIT_MANAGED_HOOK);
|
|
1194
|
+
}
|
|
1195
|
+
function uninstallEffectivePostRewriteHook(cwd = process.cwd()) {
|
|
1196
|
+
const target = resolveEffectivePostRewriteHook(cwd);
|
|
1197
|
+
return uninstallTarget(target, POST_REWRITE_MANAGED_HOOK);
|
|
1096
1198
|
}
|
|
1097
1199
|
function projectHooksPathIsConfigured(gitRoot) {
|
|
1098
1200
|
for (const scope of ["--worktree", "--local"]) {
|
|
@@ -1125,13 +1227,29 @@ function uninstallProjectPostCommitHook(cwd = process.cwd()) {
|
|
|
1125
1227
|
resolve3(safeGitPath(gitRoot, commonDir), "hooks", "post-commit")
|
|
1126
1228
|
);
|
|
1127
1229
|
}
|
|
1128
|
-
function
|
|
1129
|
-
const
|
|
1230
|
+
function uninstallProjectPostRewriteHook(cwd = process.cwd()) {
|
|
1231
|
+
const gitRoot = gitToplevel(cwd);
|
|
1232
|
+
if (!gitRoot) throw new Error("not a git repository");
|
|
1233
|
+
if (projectHooksPathIsConfigured(gitRoot)) {
|
|
1234
|
+
return uninstallEffectivePostRewriteHook(gitRoot);
|
|
1235
|
+
}
|
|
1236
|
+
const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
1237
|
+
cwd: gitRoot,
|
|
1238
|
+
encoding: "utf8",
|
|
1239
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1240
|
+
timeout: GIT_TIMEOUT_MS
|
|
1241
|
+
}).replace(/\r?\n$/u, "");
|
|
1242
|
+
return uninstallPostRewriteHookAtPath(
|
|
1243
|
+
resolve3(safeGitPath(gitRoot, commonDir), "hooks", POST_REWRITE_MANAGED_HOOK.hookName)
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
function uninstallTarget(target, spec) {
|
|
1247
|
+
const stat = assertSafeFile(target.hookPath, spec);
|
|
1130
1248
|
if (!stat) return { path: target.hookPath, changed: false, removedFile: false };
|
|
1131
1249
|
const existing = readHookFile(target.hookPath);
|
|
1132
|
-
const range = blockRange(existing);
|
|
1250
|
+
const range = blockRange(existing, spec);
|
|
1133
1251
|
if (range.kind === "absent") {
|
|
1134
|
-
if (existing.toString("utf8").startsWith(`#!/bin/sh
|
|
1252
|
+
if (spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName && existing.toString("utf8").startsWith(`#!/bin/sh
|
|
1135
1253
|
${LEGACY_PRIM_OWNED_HEADER}
|
|
1136
1254
|
`)) {
|
|
1137
1255
|
const prefixLength = legacyProjectPrefixLength(existing);
|
|
@@ -1140,47 +1258,49 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
1140
1258
|
}
|
|
1141
1259
|
const tail = existing.subarray(prefixLength);
|
|
1142
1260
|
if (tail.length === 0) {
|
|
1143
|
-
unlinkHookUnchanged(target.hookPath, existing, stat);
|
|
1261
|
+
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1144
1262
|
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1145
1263
|
}
|
|
1146
1264
|
rewriteHookAtomically(
|
|
1147
1265
|
target.hookPath,
|
|
1148
1266
|
Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]),
|
|
1149
1267
|
existing,
|
|
1268
|
+
spec,
|
|
1150
1269
|
stat
|
|
1151
1270
|
);
|
|
1152
1271
|
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1153
1272
|
}
|
|
1154
1273
|
return { path: target.hookPath, changed: false, removedFile: false };
|
|
1155
1274
|
}
|
|
1156
|
-
const shebangEnd = shellInsertionPoint(existing, target.kind === "husky_v9");
|
|
1275
|
+
const shebangEnd = shellInsertionPoint(existing, target.kind === "husky_v9", spec);
|
|
1157
1276
|
const suffix = existing.subarray(range.end);
|
|
1158
1277
|
const createdSuffix = Buffer.from(`
|
|
1159
|
-
# ${
|
|
1278
|
+
# ${spec.createdMark}
|
|
1160
1279
|
`);
|
|
1161
1280
|
if (range.start === shebangEnd && suffix.subarray(0, createdSuffix.length).equals(createdSuffix)) {
|
|
1162
1281
|
const tail = suffix.subarray(createdSuffix.length);
|
|
1163
1282
|
if (tail.length === 0) {
|
|
1164
|
-
unlinkHookUnchanged(target.hookPath, existing, stat);
|
|
1283
|
+
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1165
1284
|
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1166
1285
|
}
|
|
1167
1286
|
rewriteHookAtomically(
|
|
1168
1287
|
target.hookPath,
|
|
1169
1288
|
Buffer.concat([existing.subarray(0, shebangEnd), tail]),
|
|
1170
1289
|
existing,
|
|
1290
|
+
spec,
|
|
1171
1291
|
stat
|
|
1172
1292
|
);
|
|
1173
1293
|
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1174
1294
|
}
|
|
1175
1295
|
const createdPrefixes = [
|
|
1176
1296
|
Buffer.from(`#!/bin/sh
|
|
1177
|
-
# ${
|
|
1297
|
+
# ${spec.createdMark}
|
|
1178
1298
|
|
|
1179
1299
|
`),
|
|
1180
|
-
Buffer.from(`#!/bin/sh
|
|
1300
|
+
...spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName ? [Buffer.from(`#!/bin/sh
|
|
1181
1301
|
# ${LEGACY_PRIM_CREATED_MARK}
|
|
1182
1302
|
|
|
1183
|
-
`)
|
|
1303
|
+
`)] : []
|
|
1184
1304
|
];
|
|
1185
1305
|
const createdPrefix = createdPrefixes.find(
|
|
1186
1306
|
(prefix) => range.start === prefix.length && existing.subarray(0, prefix.length).equals(prefix)
|
|
@@ -1188,13 +1308,14 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
1188
1308
|
if (createdPrefix) {
|
|
1189
1309
|
const tail = suffix.at(0) === 10 ? suffix.subarray(1) : suffix;
|
|
1190
1310
|
if (tail.length === 0) {
|
|
1191
|
-
unlinkHookUnchanged(target.hookPath, existing, stat);
|
|
1311
|
+
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1192
1312
|
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1193
1313
|
}
|
|
1194
1314
|
rewriteHookAtomically(
|
|
1195
1315
|
target.hookPath,
|
|
1196
1316
|
Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]),
|
|
1197
1317
|
existing,
|
|
1318
|
+
spec,
|
|
1198
1319
|
stat
|
|
1199
1320
|
);
|
|
1200
1321
|
return { path: target.hookPath, changed: true, removedFile: false };
|
|
@@ -1203,17 +1324,32 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
1203
1324
|
existing.subarray(0, range.start),
|
|
1204
1325
|
range.start === shebangEnd && suffix.at(0) === 10 ? suffix.subarray(1) : suffix
|
|
1205
1326
|
]);
|
|
1206
|
-
rewriteHookAtomically(target.hookPath, next, existing, stat);
|
|
1327
|
+
rewriteHookAtomically(target.hookPath, next, existing, spec, stat);
|
|
1207
1328
|
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1208
1329
|
}
|
|
1209
1330
|
function uninstallPostCommitHookAtPath(hookPath) {
|
|
1210
1331
|
const absolute = resolve3(hookPath);
|
|
1211
|
-
return uninstallTarget(
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1332
|
+
return uninstallTarget(
|
|
1333
|
+
{
|
|
1334
|
+
gitRoot: dirname3(dirname3(absolute)),
|
|
1335
|
+
hooksDir: dirname3(absolute),
|
|
1336
|
+
hookPath: absolute,
|
|
1337
|
+
kind: "direct"
|
|
1338
|
+
},
|
|
1339
|
+
POST_COMMIT_MANAGED_HOOK
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
function uninstallPostRewriteHookAtPath(hookPath) {
|
|
1343
|
+
const absolute = resolve3(hookPath);
|
|
1344
|
+
return uninstallTarget(
|
|
1345
|
+
{
|
|
1346
|
+
gitRoot: dirname3(dirname3(absolute)),
|
|
1347
|
+
hooksDir: dirname3(absolute),
|
|
1348
|
+
hookPath: absolute,
|
|
1349
|
+
kind: "direct"
|
|
1350
|
+
},
|
|
1351
|
+
POST_REWRITE_MANAGED_HOOK
|
|
1352
|
+
);
|
|
1217
1353
|
}
|
|
1218
1354
|
|
|
1219
1355
|
// src/lib/repository-binding.ts
|
|
@@ -1222,29 +1358,47 @@ async function resolveRepositoryBinding(root, options) {
|
|
|
1222
1358
|
if (!repositoryFullName) {
|
|
1223
1359
|
throw new Error("origin must be a GitHub HTTPS/SSH remote in owner/name form");
|
|
1224
1360
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1361
|
+
let response;
|
|
1362
|
+
try {
|
|
1363
|
+
response = await getClient().post(
|
|
1364
|
+
"/api/cli/repositories/bind",
|
|
1365
|
+
{ repositoryFullName },
|
|
1366
|
+
options
|
|
1367
|
+
);
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
if (error instanceof HttpError && error.status === 404) {
|
|
1370
|
+
return { status: "pending", repositoryFullName };
|
|
1371
|
+
}
|
|
1372
|
+
throw error;
|
|
1373
|
+
}
|
|
1230
1374
|
if (!isValidRepoSyncId(response.repoSyncId)) {
|
|
1231
1375
|
throw new Error("server returned no repository binding");
|
|
1232
1376
|
}
|
|
1233
|
-
return { repoSyncId: response.repoSyncId, repositoryFullName };
|
|
1377
|
+
return { status: "connected", repoSyncId: response.repoSyncId, repositoryFullName };
|
|
1234
1378
|
}
|
|
1235
1379
|
async function bindRepository(root, options) {
|
|
1236
1380
|
const binding = await resolveRepositoryBinding(root, options);
|
|
1237
|
-
|
|
1381
|
+
if (binding.status === "connected") {
|
|
1382
|
+
setRepoSyncId(root, binding.repoSyncId);
|
|
1383
|
+
} else {
|
|
1384
|
+
clearRepoSyncId(root);
|
|
1385
|
+
}
|
|
1238
1386
|
return binding;
|
|
1239
1387
|
}
|
|
1240
1388
|
|
|
1241
1389
|
export {
|
|
1242
1390
|
postCommitHookBlock,
|
|
1391
|
+
postRewriteHookBlock,
|
|
1243
1392
|
ensureEffectivePostCommitHook,
|
|
1393
|
+
ensureEffectivePostRewriteHook,
|
|
1244
1394
|
ensurePostCommitHookAtPath,
|
|
1395
|
+
ensurePostRewriteHookAtPath,
|
|
1245
1396
|
inspectEffectivePostCommitHook,
|
|
1397
|
+
inspectEffectivePostRewriteHook,
|
|
1246
1398
|
uninstallProjectPostCommitHook,
|
|
1399
|
+
uninstallProjectPostRewriteHook,
|
|
1247
1400
|
uninstallPostCommitHookAtPath,
|
|
1401
|
+
uninstallPostRewriteHookAtPath,
|
|
1248
1402
|
resolveRepositoryBinding,
|
|
1249
1403
|
bindRepository,
|
|
1250
1404
|
printJson,
|