22
33// @ts -check
44
5- import { existsSync , readFileSync , writeFileSync } from 'fs'
5+ import { existsSync , mkdirSync , readFileSync , statSync , writeFileSync } from 'fs'
66import { chmod , writeFile } from 'fs/promises'
77import { join } from 'path'
88
@@ -22,42 +22,95 @@ const {name} = readPackageJson()
2222 ** - for CI and the same precommit package
2323 ** - for the `@s-ui/precommit` pkg itself */
2424
25+ /**
26+ * Get the actual git directory path, handling both normal repos and worktrees.
27+ * In a worktree, .git is a file containing a reference to the actual git directory.
28+ * @param {string } gitPath - Path to the .git file or directory
29+ * @returns {string } Path to the actual git directory
30+ */
31+ function getGitDirectory ( gitPath ) {
32+ const gitStat = statSync ( gitPath )
33+
34+ if ( gitStat . isDirectory ( ) ) {
35+ // Normal git repository
36+ return gitPath
37+ } else if ( gitStat . isFile ( ) ) {
38+ // Git worktree - read and parse the gitdir reference
39+ const gitFileContent = readFileSync ( gitPath , { encoding : 'utf8' } ) . trim ( )
40+
41+ // Expected format: "gitdir: /path/to/actual/.git/worktrees/name"
42+ const match = gitFileContent . match ( / ^ g i t d i r : \s * ( .+ ) $ / )
43+
44+ if ( ! match ) {
45+ throw new Error ( `Invalid .git file format: ${ gitFileContent } ` )
46+ }
47+
48+ const gitDir = match [ 1 ] . trim ( )
49+
50+ // Validate the referenced directory exists
51+ if ( ! existsSync ( gitDir ) ) {
52+ throw new Error ( `Git directory referenced in .git file does not exist: ${ gitDir } ` )
53+ }
54+
55+ return gitDir
56+ } else {
57+ throw new Error ( '.git exists but is neither a file nor a directory' )
58+ }
59+ }
60+
2561if ( CI === false && name !== '@s-ui/precommit' ) {
26- const hooksPath = join ( cwd , '.git' )
62+ const gitPath = join ( cwd , '.git' )
2763
2864 /**
29- * Check if hooks directory exists. If not, it means
65+ * Check if .git exists. If not, it means
3066 * the project is not a Git Repository and it doesn't
3167 * make sense to install Git hooks for now.
3268 */
33- if ( ! existsSync ( hooksPath ) ) {
69+ if ( ! existsSync ( gitPath ) ) {
3470 log ( 'No .git folder found. Skipping precommit hooks installation...' )
3571 process . exit ( 0 )
3672 }
3773
38- log ( 'Installing precommit hooks...' )
39-
40- const commitMsgPath = `${ hooksPath } /hooks/commit-msg`
41- const preCommitPath = `${ hooksPath } /hooks/pre-commit`
42- const prePushPath = `${ hooksPath } /hooks/pre-push`
43-
44- Promise . all ( [
45- writeFile ( commitMsgPath , '#!/bin/sh\nnpm run commit-msg --if-present' ) ,
46- writeFile ( preCommitPath , '#!/bin/sh\nnpm run pre-commit --if-present' ) ,
47- writeFile ( prePushPath , '#!/bin/sh\nnpm run pre-push --if-present' )
48- ] ) . then ( ( ) => Promise . all ( [ chmod ( commitMsgPath , '755' ) , chmod ( preCommitPath , '755' ) , chmod ( prePushPath , '755' ) ] ) )
49-
50- try {
51- addToPackageJson ( 'sui-lint js --staged && sui-lint sass --staged' , 'scripts.lint' , false )
52- addToPackageJson ( 'echo "Skipping tests as they are not present"' , 'scripts.test' , false )
53- addToPackageJson ( 'npm run lint' , 'scripts.pre-commit' , false )
54- addToPackageJson ( 'npm run test' , 'scripts.pre-push' , false )
55- removeFromPackageJson ( 'husky' )
56- } catch ( err ) {
57- log ( err . message )
58- log ( '[@s-ui/precommit] Installation has FAILED.' )
59- process . exit ( 1 )
60- }
74+ Promise . resolve ( )
75+ . then ( ( ) => {
76+ // Get the actual git directory (handles both normal repos and worktrees)
77+ const gitDirectory = getGitDirectory ( gitPath )
78+ const hooksPath = join ( gitDirectory , 'hooks' )
79+
80+ // Ensure hooks directory exists (important for worktrees)
81+ if ( ! existsSync ( hooksPath ) ) {
82+ mkdirSync ( hooksPath , { recursive : true } )
83+ log ( 'Created hooks directory...' )
84+ }
85+
86+ log ( 'Installing precommit hooks...' )
87+
88+ const commitMsgPath = join ( hooksPath , 'commit-msg' )
89+ const preCommitPath = join ( hooksPath , 'pre-commit' )
90+ const prePushPath = join ( hooksPath , 'pre-push' )
91+
92+ return { commitMsgPath, preCommitPath, prePushPath}
93+ } )
94+ . then ( ( { commitMsgPath, preCommitPath, prePushPath} ) =>
95+ Promise . all ( [
96+ writeFile ( commitMsgPath , '#!/bin/sh\nnpm run commit-msg --if-present' ) ,
97+ writeFile ( preCommitPath , '#!/bin/sh\nnpm run pre-commit --if-present' ) ,
98+ writeFile ( prePushPath , '#!/bin/sh\nnpm run pre-push --if-present' )
99+ ] ) . then ( ( ) => Promise . all ( [ chmod ( commitMsgPath , '755' ) , chmod ( preCommitPath , '755' ) , chmod ( prePushPath , '755' ) ] ) )
100+ )
101+ . then ( ( ) => {
102+ // Add package.json modifications
103+ addToPackageJson ( 'sui-lint js --staged && sui-lint sass --staged' , 'scripts.lint' , false )
104+ addToPackageJson ( 'echo "Skipping tests as they are not present"' , 'scripts.test' , false )
105+ addToPackageJson ( 'npm run lint' , 'scripts.pre-commit' , false )
106+ addToPackageJson ( 'npm run test' , 'scripts.pre-push' , false )
107+ removeFromPackageJson ( 'husky' )
108+ } )
109+ . catch ( err => {
110+ log ( err . message )
111+ log ( '[@s-ui/precommit] Installation has FAILED.' )
112+ process . exit ( 1 )
113+ } )
61114}
62115
63116function log ( ...args ) {
0 commit comments