@primitive.ai/prim 0.1.0-alpha.65 → 0.1.0-alpha.67
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/dist/{chunk-5CKL5EYQ.js → chunk-CPSE3Y3T.js} +62 -0
- package/dist/{chunk-LNU44OJV.js → chunk-CZ24W5QU.js} +2 -1
- package/dist/{chunk-CPX3BUOC.js → chunk-RTLFE6EZ.js} +6 -2
- package/dist/{chunk-KHZNYEUV.js → chunk-U6L2RU67.js} +1 -1
- package/dist/{chunk-HIMWM526.js → chunk-UN5O5SYG.js} +1 -1
- package/dist/{chunk-CPHJUQNE.js → chunk-XPVNH3J6.js} +229 -89
- package/dist/{chunk-WS3MCNFD.js → chunk-XUAP572Z.js} +1 -1
- package/dist/daemon/server.js +6 -2
- package/dist/hooks/post-commit.js +3 -3
- package/dist/hooks/post-rewrite.js +194 -0
- package/dist/hooks/post-tool-use.js +7 -7
- package/dist/hooks/pre-commit.js +2 -2
- package/dist/hooks/pre-tool-use.js +2 -2
- package/dist/hooks/prim-hook.js +17 -17
- package/dist/hooks/session-start.js +9 -4
- package/dist/index.js +699 -62
- package/package.json +5 -4
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
HttpError,
|
|
10
10
|
getClient,
|
|
11
11
|
withFileLock
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-RTLFE6EZ.js";
|
|
13
13
|
|
|
14
14
|
// src/commands/skill.ts
|
|
15
15
|
import { randomUUID } from "crypto";
|
|
@@ -616,6 +616,9 @@ import { basename, dirname as dirname3, isAbsolute as isAbsolute2, resolve as re
|
|
|
616
616
|
var PRIM_POST_COMMIT_BLOCK_START = "# >>> prim post-commit hook >>>";
|
|
617
617
|
var PRIM_POST_COMMIT_BLOCK_END = "# <<< prim post-commit hook <<<";
|
|
618
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";
|
|
619
622
|
var LEGACY_PRIM_CREATED_MARK = "prim-created-hook";
|
|
620
623
|
var LEGACY_PRIM_OWNED_HEADER = "# prim post-commit hook \u2014 installed by: prim hooks install (prim-managed-hook)";
|
|
621
624
|
var LEGACY_GLOBAL_OWNED_HEADER = "# prim global post-commit hook (core.hooksPath) \u2014 managed by prim; do not edit.";
|
|
@@ -684,6 +687,67 @@ ${PRIM_POST_COMMIT_BLOCK_END}`;
|
|
|
684
687
|
function postCommitHookBlock() {
|
|
685
688
|
return currentPostCommitBlock();
|
|
686
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
|
+
};
|
|
687
751
|
function legacyFloatingInvocation() {
|
|
688
752
|
return `if command -v prim-post-commit >/dev/null 2>&1; then
|
|
689
753
|
prim-post-commit || true
|
|
@@ -739,10 +803,10 @@ function isKnownLegacyGlobalGate(value) {
|
|
|
739
803
|
const suffix = Buffer.from("\nfi\n");
|
|
740
804
|
return value.subarray(0, prefix.length).equals(prefix) && value.subarray(-suffix.length).equals(suffix) && isPinnedInvocation(value.subarray(prefix.length, -suffix.length).toString("utf8"));
|
|
741
805
|
}
|
|
742
|
-
function createdScaffold() {
|
|
806
|
+
function createdScaffold(spec) {
|
|
743
807
|
return Buffer.from(`#!/bin/sh
|
|
744
|
-
${
|
|
745
|
-
# ${
|
|
808
|
+
${spec.block()}
|
|
809
|
+
# ${spec.createdMark}
|
|
746
810
|
`);
|
|
747
811
|
}
|
|
748
812
|
function safeGitPath(root, value) {
|
|
@@ -751,7 +815,7 @@ function safeGitPath(root, value) {
|
|
|
751
815
|
}
|
|
752
816
|
return isAbsolute2(value) ? resolve3(value) : resolve3(root, value);
|
|
753
817
|
}
|
|
754
|
-
function
|
|
818
|
+
function resolveEffectiveManagedHook(spec, cwd = process.cwd()) {
|
|
755
819
|
const gitRoot = gitToplevel(cwd);
|
|
756
820
|
if (!gitRoot) throw new Error("not a git repository");
|
|
757
821
|
const value = execFileSync("git", ["rev-parse", "--git-path", "hooks"], {
|
|
@@ -765,42 +829,49 @@ function resolveEffectivePostCommitHook(cwd = process.cwd()) {
|
|
|
765
829
|
return {
|
|
766
830
|
gitRoot,
|
|
767
831
|
hooksDir,
|
|
768
|
-
hookPath: resolve3(dirname3(hooksDir),
|
|
832
|
+
hookPath: resolve3(dirname3(hooksDir), spec.hookName),
|
|
769
833
|
kind: "husky_v9",
|
|
770
|
-
dispatcherPath: resolve3(hooksDir,
|
|
834
|
+
dispatcherPath: resolve3(hooksDir, spec.hookName)
|
|
771
835
|
};
|
|
772
836
|
}
|
|
773
837
|
return {
|
|
774
838
|
gitRoot,
|
|
775
839
|
hooksDir,
|
|
776
|
-
hookPath: resolve3(hooksDir,
|
|
840
|
+
hookPath: resolve3(hooksDir, spec.hookName),
|
|
777
841
|
kind: "direct"
|
|
778
842
|
};
|
|
779
843
|
}
|
|
780
|
-
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) {
|
|
781
851
|
if (!existsSync3(path)) return void 0;
|
|
782
852
|
const stat = lstatSync3(path);
|
|
783
853
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_HOOK_BYTES) {
|
|
784
|
-
throw new Error(`unsafe
|
|
854
|
+
throw new Error(`unsafe ${spec.hookName} hook target: ${path}`);
|
|
785
855
|
}
|
|
786
856
|
return stat;
|
|
787
857
|
}
|
|
788
|
-
function assertUsableHuskyDispatcher(target) {
|
|
858
|
+
function assertUsableHuskyDispatcher(target, spec) {
|
|
789
859
|
if (!target.dispatcherPath) return;
|
|
790
|
-
const stat = assertSafeFile(target.dispatcherPath);
|
|
860
|
+
const stat = assertSafeFile(target.dispatcherPath, spec);
|
|
791
861
|
if (!stat || (stat.mode & 64) === 0) {
|
|
792
862
|
throw new Error(
|
|
793
|
-
`Husky
|
|
863
|
+
`Husky ${spec.hookName} dispatcher is missing or not executable: ${target.dispatcherPath}`
|
|
794
864
|
);
|
|
795
865
|
}
|
|
796
|
-
const dispatcher = decodeHookText(readHookFile(target.dispatcherPath));
|
|
797
|
-
|
|
866
|
+
const dispatcher = decodeHookText(readHookFile(target.dispatcherPath), spec);
|
|
867
|
+
const directDispatcher = HUSKY_DIRECT_DISPATCHER.replaceAll("post-commit", spec.hookName);
|
|
868
|
+
if (dispatcher === directDispatcher) return;
|
|
798
869
|
const normalizedDispatcher = dispatcher.endsWith("\n") ? dispatcher.slice(0, -1) : dispatcher;
|
|
799
870
|
if (!HUSKY_V9_DISPATCHERS.has(normalizedDispatcher)) {
|
|
800
|
-
throw new Error(`unrecognized Husky
|
|
871
|
+
throw new Error(`unrecognized Husky ${spec.hookName} dispatcher: ${target.dispatcherPath}`);
|
|
801
872
|
}
|
|
802
873
|
const huskyRuntime = resolve3(dirname3(target.dispatcherPath), "h");
|
|
803
|
-
const runtimeStat = assertSafeFile(huskyRuntime);
|
|
874
|
+
const runtimeStat = assertSafeFile(huskyRuntime, spec);
|
|
804
875
|
const runtime = runtimeStat ? readHookFile(huskyRuntime) : void 0;
|
|
805
876
|
const runtimeHash = runtime ? createHash2("sha256").update(runtime).digest("hex") : void 0;
|
|
806
877
|
if (!runtimeHash || !HUSKY_V9_RUNTIME_HASHES.has(runtimeHash)) {
|
|
@@ -811,28 +882,28 @@ function readHookFile(path) {
|
|
|
811
882
|
const value = readFileSync3(path);
|
|
812
883
|
return Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
813
884
|
}
|
|
814
|
-
function decodeHookText(content) {
|
|
815
|
-
if (content.includes(0)) throw new Error(
|
|
885
|
+
function decodeHookText(content, spec) {
|
|
886
|
+
if (content.includes(0)) throw new Error(`binary ${spec.hookName} hook`);
|
|
816
887
|
try {
|
|
817
888
|
return new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
818
889
|
} catch {
|
|
819
|
-
throw new Error(
|
|
890
|
+
throw new Error(`binary ${spec.hookName} hook`);
|
|
820
891
|
}
|
|
821
892
|
}
|
|
822
|
-
function supportedShebangEnd(content) {
|
|
823
|
-
const text = decodeHookText(content);
|
|
893
|
+
function supportedShebangEnd(content, spec) {
|
|
894
|
+
const text = decodeHookText(content, spec);
|
|
824
895
|
const newline = text.indexOf("\n");
|
|
825
896
|
const shebang = newline === -1 ? text : text.slice(0, newline);
|
|
826
897
|
if (!SUPPORTED_SHEBANGS.has(shebang) || newline === -1) {
|
|
827
|
-
throw new Error(
|
|
898
|
+
throw new Error(`unsupported ${spec.hookName} hook interpreter`);
|
|
828
899
|
}
|
|
829
900
|
return Buffer.byteLength(text.slice(0, newline + 1), "utf8");
|
|
830
901
|
}
|
|
831
|
-
function shellInsertionPoint(content, allowShebangless) {
|
|
832
|
-
const text = decodeHookText(content);
|
|
833
|
-
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);
|
|
834
905
|
if (allowShebangless) return 0;
|
|
835
|
-
throw new Error(
|
|
906
|
+
throw new Error(`unsupported ${spec.hookName} hook interpreter`);
|
|
836
907
|
}
|
|
837
908
|
function allIndexes(haystack, needle) {
|
|
838
909
|
const indexes = [];
|
|
@@ -845,28 +916,28 @@ function allIndexes(haystack, needle) {
|
|
|
845
916
|
}
|
|
846
917
|
return indexes;
|
|
847
918
|
}
|
|
848
|
-
function blockRange(content) {
|
|
849
|
-
decodeHookText(content);
|
|
850
|
-
const startMarker = Buffer.from(
|
|
851
|
-
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);
|
|
852
923
|
const starts = allIndexes(content, startMarker);
|
|
853
924
|
const ends = allIndexes(content, endMarker);
|
|
854
925
|
if (starts.length === 0 && ends.length === 0) return { kind: "absent" };
|
|
855
926
|
if (starts.length !== 1 || ends.length !== 1 || ends[0] < starts[0]) {
|
|
856
|
-
throw new Error(
|
|
927
|
+
throw new Error(`malformed Prim ${spec.hookName} markers`);
|
|
857
928
|
}
|
|
858
929
|
const start = starts[0];
|
|
859
930
|
const end = ends[0] + endMarker.length;
|
|
860
|
-
const current = Buffer.from(
|
|
931
|
+
const current = Buffer.from(spec.block());
|
|
861
932
|
return content.subarray(start, end).equals(current) ? { kind: "current", start, end } : { kind: "stale", start, end };
|
|
862
933
|
}
|
|
863
|
-
function mergedContent(existing, allowShebangless) {
|
|
864
|
-
const block = Buffer.from(
|
|
934
|
+
function mergedContent(existing, allowShebangless, spec) {
|
|
935
|
+
const block = Buffer.from(spec.block());
|
|
865
936
|
if (!existing) {
|
|
866
|
-
return createdScaffold();
|
|
937
|
+
return createdScaffold(spec);
|
|
867
938
|
}
|
|
868
|
-
const range = blockRange(existing);
|
|
869
|
-
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
|
|
870
941
|
${LEGACY_GLOBAL_OWNED_HEADER}
|
|
871
942
|
`)) {
|
|
872
943
|
const gateAt = existing.indexOf(Buffer.from('if [ "$(git config --get prim.active'));
|
|
@@ -881,7 +952,7 @@ ${LEGACY_GLOBAL_OWNED_HEADER}
|
|
|
881
952
|
existing.subarray(0, gateAt),
|
|
882
953
|
existing.subarray(chainAt)
|
|
883
954
|
]);
|
|
884
|
-
const shebangEnd2 = shellInsertionPoint(withoutLegacyGate, allowShebangless);
|
|
955
|
+
const shebangEnd2 = shellInsertionPoint(withoutLegacyGate, allowShebangless, spec);
|
|
885
956
|
return Buffer.concat([
|
|
886
957
|
withoutLegacyGate.subarray(0, shebangEnd2),
|
|
887
958
|
block,
|
|
@@ -889,25 +960,25 @@ ${LEGACY_GLOBAL_OWNED_HEADER}
|
|
|
889
960
|
withoutLegacyGate.subarray(shebangEnd2)
|
|
890
961
|
]);
|
|
891
962
|
}
|
|
892
|
-
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
|
|
893
964
|
${LEGACY_PRIM_OWNED_HEADER}
|
|
894
965
|
`)) {
|
|
895
966
|
const prefixLength = legacyProjectPrefixLength(existing);
|
|
896
967
|
if (prefixLength === void 0) {
|
|
897
968
|
throw new Error("unrecognized legacy Prim post-commit invocation");
|
|
898
969
|
}
|
|
899
|
-
return Buffer.concat([createdScaffold(), existing.subarray(prefixLength)]);
|
|
970
|
+
return Buffer.concat([createdScaffold(spec), existing.subarray(prefixLength)]);
|
|
900
971
|
}
|
|
901
972
|
if (range.kind !== "absent") {
|
|
902
973
|
const oldCreatedPrefixes = [
|
|
903
974
|
Buffer.from(`#!/bin/sh
|
|
904
|
-
# ${
|
|
975
|
+
# ${spec.createdMark}
|
|
905
976
|
|
|
906
977
|
`),
|
|
907
|
-
Buffer.from(`#!/bin/sh
|
|
978
|
+
...spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName ? [Buffer.from(`#!/bin/sh
|
|
908
979
|
# ${LEGACY_PRIM_CREATED_MARK}
|
|
909
980
|
|
|
910
|
-
`)
|
|
981
|
+
`)] : []
|
|
911
982
|
];
|
|
912
983
|
const oldCreated = oldCreatedPrefixes.some(
|
|
913
984
|
(prefix) => range.start === prefix.length && existing.subarray(0, prefix.length).equals(prefix)
|
|
@@ -915,10 +986,10 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
915
986
|
if (oldCreated) {
|
|
916
987
|
const suffix = existing.subarray(range.end);
|
|
917
988
|
const tail = suffix.at(0) === 10 ? suffix.subarray(1) : suffix;
|
|
918
|
-
return Buffer.concat([createdScaffold(), tail]);
|
|
989
|
+
return Buffer.concat([createdScaffold(spec), tail]);
|
|
919
990
|
}
|
|
920
991
|
}
|
|
921
|
-
const shebangEnd = shellInsertionPoint(existing, allowShebangless);
|
|
992
|
+
const shebangEnd = shellInsertionPoint(existing, allowShebangless, spec);
|
|
922
993
|
if (range.kind === "current" && range.start === shebangEnd) return existing;
|
|
923
994
|
let withoutBlock = existing;
|
|
924
995
|
if (range.kind !== "absent") {
|
|
@@ -938,22 +1009,22 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
938
1009
|
function errorCode(error) {
|
|
939
1010
|
return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
940
1011
|
}
|
|
941
|
-
function assertTargetUnchanged(path, expected, stat) {
|
|
1012
|
+
function assertTargetUnchanged(path, expected, spec, stat) {
|
|
942
1013
|
let current;
|
|
943
1014
|
try {
|
|
944
1015
|
current = lstatSync3(path);
|
|
945
1016
|
} catch (error) {
|
|
946
1017
|
if (expected === void 0 && errorCode(error) === "ENOENT") return;
|
|
947
|
-
throw new Error(
|
|
1018
|
+
throw new Error(`${spec.hookName} hook changed concurrently: ${path}`);
|
|
948
1019
|
}
|
|
949
1020
|
if (expected === void 0) {
|
|
950
|
-
throw new Error(
|
|
1021
|
+
throw new Error(`${spec.hookName} hook appeared concurrently: ${path}`);
|
|
951
1022
|
}
|
|
952
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)) {
|
|
953
|
-
throw new Error(
|
|
1024
|
+
throw new Error(`${spec.hookName} hook changed concurrently: ${path}`);
|
|
954
1025
|
}
|
|
955
1026
|
}
|
|
956
|
-
function rewriteHookAtomically(path, next, expected, stat) {
|
|
1027
|
+
function rewriteHookAtomically(path, next, expected, spec, stat) {
|
|
957
1028
|
const parent = dirname3(path);
|
|
958
1029
|
mkdirSync2(parent, { recursive: true });
|
|
959
1030
|
const temporary = resolve3(
|
|
@@ -972,7 +1043,7 @@ function rewriteHookAtomically(path, next, expected, stat) {
|
|
|
972
1043
|
fsyncSync2(fd);
|
|
973
1044
|
closeSync3(fd);
|
|
974
1045
|
fd = void 0;
|
|
975
|
-
assertTargetUnchanged(path, expected, stat);
|
|
1046
|
+
assertTargetUnchanged(path, expected, spec, stat);
|
|
976
1047
|
renameSync2(temporary, path);
|
|
977
1048
|
} finally {
|
|
978
1049
|
if (fd !== void 0) {
|
|
@@ -989,32 +1060,36 @@ function rewriteHookAtomically(path, next, expected, stat) {
|
|
|
989
1060
|
}
|
|
990
1061
|
}
|
|
991
1062
|
}
|
|
992
|
-
function unlinkHookUnchanged(path, expected, stat) {
|
|
993
|
-
assertTargetUnchanged(path, expected, stat);
|
|
1063
|
+
function unlinkHookUnchanged(path, expected, stat, spec) {
|
|
1064
|
+
assertTargetUnchanged(path, expected, spec, stat);
|
|
994
1065
|
unlinkSync(path);
|
|
995
1066
|
}
|
|
996
1067
|
function ensureEffectivePostCommitHook(cwd = process.cwd()) {
|
|
997
1068
|
const target = resolveEffectivePostCommitHook(cwd);
|
|
998
|
-
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);
|
|
999
1074
|
}
|
|
1000
|
-
function ensureTarget(target, initialContent) {
|
|
1001
|
-
assertUsableHuskyDispatcher(target);
|
|
1002
|
-
const stat = assertSafeFile(target.hookPath);
|
|
1075
|
+
function ensureTarget(target, spec, initialContent) {
|
|
1076
|
+
assertUsableHuskyDispatcher(target, spec);
|
|
1077
|
+
const stat = assertSafeFile(target.hookPath, spec);
|
|
1003
1078
|
if (stat && target.kind === "direct" && (stat.mode & 64) === 0) {
|
|
1004
|
-
throw new Error(`existing
|
|
1079
|
+
throw new Error(`existing ${spec.hookName} hook is not executable: ${target.hookPath}`);
|
|
1005
1080
|
}
|
|
1006
1081
|
const existing = stat ? readHookFile(target.hookPath) : void 0;
|
|
1007
|
-
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);
|
|
1008
1083
|
if (!existing && initialContent !== void 0) {
|
|
1009
|
-
const shebangEnd = shellInsertionPoint(next, target.kind === "husky_v9");
|
|
1010
|
-
const range = blockRange(next);
|
|
1084
|
+
const shebangEnd = shellInsertionPoint(next, target.kind === "husky_v9", spec);
|
|
1085
|
+
const range = blockRange(next, spec);
|
|
1011
1086
|
if (range.kind !== "current" || range.start !== shebangEnd) {
|
|
1012
|
-
throw new Error(
|
|
1087
|
+
throw new Error(`invalid initial Prim ${spec.hookName} scaffold`);
|
|
1013
1088
|
}
|
|
1014
1089
|
}
|
|
1015
1090
|
const contentChanged = !existing || !next.equals(existing);
|
|
1016
1091
|
if (contentChanged) {
|
|
1017
|
-
rewriteHookAtomically(target.hookPath, next, existing, stat);
|
|
1092
|
+
rewriteHookAtomically(target.hookPath, next, existing, spec, stat);
|
|
1018
1093
|
}
|
|
1019
1094
|
return { path: target.hookPath, changed: contentChanged || !stat, kind: target.kind };
|
|
1020
1095
|
}
|
|
@@ -1027,14 +1102,35 @@ function ensurePostCommitHookAtPath(hookPath, initialContent) {
|
|
|
1027
1102
|
hookPath: absolute,
|
|
1028
1103
|
kind: "direct"
|
|
1029
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,
|
|
1030
1120
|
initialContent
|
|
1031
1121
|
);
|
|
1032
1122
|
return { ...result, kind: "direct" };
|
|
1033
1123
|
}
|
|
1034
1124
|
function inspectEffectivePostCommitHook(cwd = process.cwd()) {
|
|
1035
|
-
|
|
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);
|
|
1036
1132
|
try {
|
|
1037
|
-
assertUsableHuskyDispatcher(target);
|
|
1133
|
+
assertUsableHuskyDispatcher(target, spec);
|
|
1038
1134
|
} catch (error) {
|
|
1039
1135
|
const missing = error instanceof Error && (error.message.includes("missing or not executable") || error.message.includes("unsafe"));
|
|
1040
1136
|
return {
|
|
@@ -1047,7 +1143,7 @@ function inspectEffectivePostCommitHook(cwd = process.cwd()) {
|
|
|
1047
1143
|
}
|
|
1048
1144
|
let stat;
|
|
1049
1145
|
try {
|
|
1050
|
-
stat = assertSafeFile(target.hookPath);
|
|
1146
|
+
stat = assertSafeFile(target.hookPath, spec);
|
|
1051
1147
|
} catch {
|
|
1052
1148
|
return {
|
|
1053
1149
|
...target,
|
|
@@ -1069,8 +1165,8 @@ function inspectEffectivePostCommitHook(cwd = process.cwd()) {
|
|
|
1069
1165
|
const executable = target.kind === "husky_v9" || (stat.mode & 64) !== 0;
|
|
1070
1166
|
try {
|
|
1071
1167
|
const content = readHookFile(target.hookPath);
|
|
1072
|
-
const shebangEnd = shellInsertionPoint(content, target.kind === "husky_v9");
|
|
1073
|
-
const range = blockRange(content);
|
|
1168
|
+
const shebangEnd = shellInsertionPoint(content, target.kind === "husky_v9", spec);
|
|
1169
|
+
const range = blockRange(content, spec);
|
|
1074
1170
|
const positioned = range.kind !== "absent" && range.start === shebangEnd;
|
|
1075
1171
|
const current = range.kind === "current" && positioned;
|
|
1076
1172
|
return {
|
|
@@ -1094,7 +1190,11 @@ function inspectEffectivePostCommitHook(cwd = process.cwd()) {
|
|
|
1094
1190
|
}
|
|
1095
1191
|
function uninstallEffectivePostCommitHook(cwd = process.cwd()) {
|
|
1096
1192
|
const target = resolveEffectivePostCommitHook(cwd);
|
|
1097
|
-
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);
|
|
1098
1198
|
}
|
|
1099
1199
|
function projectHooksPathIsConfigured(gitRoot) {
|
|
1100
1200
|
for (const scope of ["--worktree", "--local"]) {
|
|
@@ -1127,13 +1227,29 @@ function uninstallProjectPostCommitHook(cwd = process.cwd()) {
|
|
|
1127
1227
|
resolve3(safeGitPath(gitRoot, commonDir), "hooks", "post-commit")
|
|
1128
1228
|
);
|
|
1129
1229
|
}
|
|
1130
|
-
function
|
|
1131
|
-
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);
|
|
1132
1248
|
if (!stat) return { path: target.hookPath, changed: false, removedFile: false };
|
|
1133
1249
|
const existing = readHookFile(target.hookPath);
|
|
1134
|
-
const range = blockRange(existing);
|
|
1250
|
+
const range = blockRange(existing, spec);
|
|
1135
1251
|
if (range.kind === "absent") {
|
|
1136
|
-
if (existing.toString("utf8").startsWith(`#!/bin/sh
|
|
1252
|
+
if (spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName && existing.toString("utf8").startsWith(`#!/bin/sh
|
|
1137
1253
|
${LEGACY_PRIM_OWNED_HEADER}
|
|
1138
1254
|
`)) {
|
|
1139
1255
|
const prefixLength = legacyProjectPrefixLength(existing);
|
|
@@ -1142,47 +1258,49 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
1142
1258
|
}
|
|
1143
1259
|
const tail = existing.subarray(prefixLength);
|
|
1144
1260
|
if (tail.length === 0) {
|
|
1145
|
-
unlinkHookUnchanged(target.hookPath, existing, stat);
|
|
1261
|
+
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1146
1262
|
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1147
1263
|
}
|
|
1148
1264
|
rewriteHookAtomically(
|
|
1149
1265
|
target.hookPath,
|
|
1150
1266
|
Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]),
|
|
1151
1267
|
existing,
|
|
1268
|
+
spec,
|
|
1152
1269
|
stat
|
|
1153
1270
|
);
|
|
1154
1271
|
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1155
1272
|
}
|
|
1156
1273
|
return { path: target.hookPath, changed: false, removedFile: false };
|
|
1157
1274
|
}
|
|
1158
|
-
const shebangEnd = shellInsertionPoint(existing, target.kind === "husky_v9");
|
|
1275
|
+
const shebangEnd = shellInsertionPoint(existing, target.kind === "husky_v9", spec);
|
|
1159
1276
|
const suffix = existing.subarray(range.end);
|
|
1160
1277
|
const createdSuffix = Buffer.from(`
|
|
1161
|
-
# ${
|
|
1278
|
+
# ${spec.createdMark}
|
|
1162
1279
|
`);
|
|
1163
1280
|
if (range.start === shebangEnd && suffix.subarray(0, createdSuffix.length).equals(createdSuffix)) {
|
|
1164
1281
|
const tail = suffix.subarray(createdSuffix.length);
|
|
1165
1282
|
if (tail.length === 0) {
|
|
1166
|
-
unlinkHookUnchanged(target.hookPath, existing, stat);
|
|
1283
|
+
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1167
1284
|
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1168
1285
|
}
|
|
1169
1286
|
rewriteHookAtomically(
|
|
1170
1287
|
target.hookPath,
|
|
1171
1288
|
Buffer.concat([existing.subarray(0, shebangEnd), tail]),
|
|
1172
1289
|
existing,
|
|
1290
|
+
spec,
|
|
1173
1291
|
stat
|
|
1174
1292
|
);
|
|
1175
1293
|
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1176
1294
|
}
|
|
1177
1295
|
const createdPrefixes = [
|
|
1178
1296
|
Buffer.from(`#!/bin/sh
|
|
1179
|
-
# ${
|
|
1297
|
+
# ${spec.createdMark}
|
|
1180
1298
|
|
|
1181
1299
|
`),
|
|
1182
|
-
Buffer.from(`#!/bin/sh
|
|
1300
|
+
...spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName ? [Buffer.from(`#!/bin/sh
|
|
1183
1301
|
# ${LEGACY_PRIM_CREATED_MARK}
|
|
1184
1302
|
|
|
1185
|
-
`)
|
|
1303
|
+
`)] : []
|
|
1186
1304
|
];
|
|
1187
1305
|
const createdPrefix = createdPrefixes.find(
|
|
1188
1306
|
(prefix) => range.start === prefix.length && existing.subarray(0, prefix.length).equals(prefix)
|
|
@@ -1190,13 +1308,14 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
1190
1308
|
if (createdPrefix) {
|
|
1191
1309
|
const tail = suffix.at(0) === 10 ? suffix.subarray(1) : suffix;
|
|
1192
1310
|
if (tail.length === 0) {
|
|
1193
|
-
unlinkHookUnchanged(target.hookPath, existing, stat);
|
|
1311
|
+
unlinkHookUnchanged(target.hookPath, existing, stat, spec);
|
|
1194
1312
|
return { path: target.hookPath, changed: true, removedFile: true };
|
|
1195
1313
|
}
|
|
1196
1314
|
rewriteHookAtomically(
|
|
1197
1315
|
target.hookPath,
|
|
1198
1316
|
Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]),
|
|
1199
1317
|
existing,
|
|
1318
|
+
spec,
|
|
1200
1319
|
stat
|
|
1201
1320
|
);
|
|
1202
1321
|
return { path: target.hookPath, changed: true, removedFile: false };
|
|
@@ -1205,17 +1324,32 @@ ${LEGACY_PRIM_OWNED_HEADER}
|
|
|
1205
1324
|
existing.subarray(0, range.start),
|
|
1206
1325
|
range.start === shebangEnd && suffix.at(0) === 10 ? suffix.subarray(1) : suffix
|
|
1207
1326
|
]);
|
|
1208
|
-
rewriteHookAtomically(target.hookPath, next, existing, stat);
|
|
1327
|
+
rewriteHookAtomically(target.hookPath, next, existing, spec, stat);
|
|
1209
1328
|
return { path: target.hookPath, changed: true, removedFile: false };
|
|
1210
1329
|
}
|
|
1211
1330
|
function uninstallPostCommitHookAtPath(hookPath) {
|
|
1212
1331
|
const absolute = resolve3(hookPath);
|
|
1213
|
-
return uninstallTarget(
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
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
|
+
);
|
|
1219
1353
|
}
|
|
1220
1354
|
|
|
1221
1355
|
// src/lib/repository-binding.ts
|
|
@@ -1254,11 +1388,17 @@ async function bindRepository(root, options) {
|
|
|
1254
1388
|
|
|
1255
1389
|
export {
|
|
1256
1390
|
postCommitHookBlock,
|
|
1391
|
+
postRewriteHookBlock,
|
|
1257
1392
|
ensureEffectivePostCommitHook,
|
|
1393
|
+
ensureEffectivePostRewriteHook,
|
|
1258
1394
|
ensurePostCommitHookAtPath,
|
|
1395
|
+
ensurePostRewriteHookAtPath,
|
|
1259
1396
|
inspectEffectivePostCommitHook,
|
|
1397
|
+
inspectEffectivePostRewriteHook,
|
|
1260
1398
|
uninstallProjectPostCommitHook,
|
|
1399
|
+
uninstallProjectPostRewriteHook,
|
|
1261
1400
|
uninstallPostCommitHookAtPath,
|
|
1401
|
+
uninstallPostRewriteHookAtPath,
|
|
1262
1402
|
resolveRepositoryBinding,
|
|
1263
1403
|
bindRepository,
|
|
1264
1404
|
printJson,
|
package/dist/daemon/server.js
CHANGED
|
@@ -394,10 +394,12 @@ function refreshToken(options = {}) {
|
|
|
394
394
|
}
|
|
395
395
|
var HttpError = class extends Error {
|
|
396
396
|
status;
|
|
397
|
-
|
|
397
|
+
body;
|
|
398
|
+
constructor(status, message, body) {
|
|
398
399
|
super(message);
|
|
399
400
|
this.name = "HttpError";
|
|
400
401
|
this.status = status;
|
|
402
|
+
this.body = body;
|
|
401
403
|
}
|
|
402
404
|
};
|
|
403
405
|
var _cachedCredential;
|
|
@@ -458,7 +460,9 @@ async function request(method, path, body, options) {
|
|
|
458
460
|
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
459
461
|
}
|
|
460
462
|
const errorBody = await response.json().catch(() => null);
|
|
461
|
-
|
|
463
|
+
const errorRecord = typeof errorBody === "object" && errorBody !== null && !Array.isArray(errorBody) ? errorBody : void 0;
|
|
464
|
+
const message = typeof errorRecord?.error === "string" ? errorRecord.error : `HTTP ${response.status}`;
|
|
465
|
+
throw new HttpError(response.status, message, errorBody);
|
|
462
466
|
}
|
|
463
467
|
return response.json();
|
|
464
468
|
}
|