secure-husky-setup 1.0.12 → 1.0.13
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/bin/index.js +16 -20
- package/lib/ci.js +22 -28
- package/lib/git.js +8 -7
- package/lib/hooks.js +32 -11
- package/lib/husky.js +22 -7
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -3,7 +3,6 @@ const { installHusky } = require('../lib/husky');
|
|
|
3
3
|
const { installGitleaks } = require('../lib/gitleaks');
|
|
4
4
|
const { installSonarScanner, setupSonarProperties } = require('../lib/sonarqube');
|
|
5
5
|
const { setupPreCommitHook } = require('../lib/hooks');
|
|
6
|
-
// Added by Arjun — import CI setup functions from the new lib/ci.js module
|
|
7
6
|
const { setupPrePushHook, setupCIScript, setupCIWorkflow, validateProject, ensurePackageLock } = require('../lib/ci');
|
|
8
7
|
const { isGitRepo } = require('../lib/git');
|
|
9
8
|
const { logInfo, logError, logSuccess } = require('../lib/logger');
|
|
@@ -19,41 +18,38 @@ const command = process.argv[2];
|
|
|
19
18
|
try {
|
|
20
19
|
logInfo("Initializing secure git hooks...");
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const { found, gitRoot, projectRoot } = await isGitRepo();
|
|
22
|
+
|
|
23
|
+
if (!found) {
|
|
24
|
+
throw new Error("Not inside a git repository. Please run 'git init' first.");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (gitRoot !== projectRoot) {
|
|
28
|
+
logInfo(`Git root detected at: ${gitRoot}`);
|
|
29
|
+
logInfo(`Project root (package.json): ${projectRoot}`);
|
|
30
|
+
logInfo(`Monorepo/subfolder setup detected — hooks installed at git root, config at project root.`);
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
// ──
|
|
27
|
-
await installHusky();
|
|
33
|
+
// ── Pre-commit hooks ──────────────────────────────────────────────────────
|
|
34
|
+
await installHusky(gitRoot);
|
|
28
35
|
await installGitleaks();
|
|
29
36
|
await installSonarScanner();
|
|
30
37
|
await setupSonarProperties();
|
|
31
|
-
await setupPreCommitHook();
|
|
38
|
+
await setupPreCommitHook(gitRoot);
|
|
32
39
|
|
|
33
40
|
logSuccess("Secure Husky + Gitleaks + SonarQube setup completed.");
|
|
34
41
|
logInfo("Next step: edit sonar-project.properties and set sonar.host.url and sonar.token.");
|
|
35
42
|
|
|
36
|
-
//
|
|
37
|
-
// Runs Newman API tests and smoke tests automatically on every git push
|
|
43
|
+
// ── Pre-push hook + GitHub Actions CI workflow ────────────────────────────
|
|
38
44
|
logInfo("Setting up Newman & Smoke Test CI workflow...");
|
|
39
45
|
|
|
40
|
-
// Added by Arjun — ensure package-lock.json exists (required by npm ci in workflow)
|
|
41
46
|
await ensurePackageLock();
|
|
42
|
-
|
|
43
|
-
// Added by Arjun — validate package.json has "start" and "test" scripts
|
|
44
47
|
await validateProject();
|
|
45
|
-
|
|
46
|
-
// Added by Arjun — write standalone scripts/run-ci-checks.sh (all test logic lives here)
|
|
47
|
-
await setupCIScript();
|
|
48
|
-
|
|
49
|
-
// Added by Arjun — copy ci-tests.yml into .github/workflows/
|
|
48
|
+
await setupCIScript(gitRoot);
|
|
50
49
|
await setupCIWorkflow();
|
|
51
|
-
|
|
52
|
-
// Added by Arjun — create .husky/pre-push hook (thin wrapper that calls run-ci-checks.sh)
|
|
53
|
-
await setupPrePushHook();
|
|
50
|
+
await setupPrePushHook(gitRoot);
|
|
54
51
|
|
|
55
52
|
logSuccess("Newman + Smoke Test pre-push hook and GitHub Actions workflow setup completed.");
|
|
56
|
-
// ── End of Arjun's additions ──────────────────────────────────────────────
|
|
57
53
|
|
|
58
54
|
} catch (err) {
|
|
59
55
|
logError(err.message);
|
package/lib/ci.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
-
// Added by Arjun
|
|
5
|
-
// File: lib/ci.js
|
|
6
|
-
// Purpose: Sets up Newman API tests + Smoke Tests as a pre-push git hook
|
|
7
|
-
// and copies the GitHub Actions CI workflow into the project.
|
|
8
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
3
|
const fs = require('fs-extra');
|
|
11
4
|
const path = require('path');
|
|
12
5
|
const { execSync } = require('child_process');
|
|
@@ -14,7 +7,8 @@ const { logInfo, logSuccess, logError } = require('./logger');
|
|
|
14
7
|
|
|
15
8
|
const TEMPLATE_PATH = path.resolve(__dirname, '../templates/ci-tests.yml');
|
|
16
9
|
|
|
17
|
-
exports.setupCIScript = async () => {
|
|
10
|
+
exports.setupCIScript = async (gitRoot) => {
|
|
11
|
+
const projectDir = path.relative(gitRoot, process.cwd()) || '.';
|
|
18
12
|
const scriptsDir = path.join(process.cwd(), 'scripts');
|
|
19
13
|
const scriptPath = path.join(scriptsDir, 'run-ci-checks.sh');
|
|
20
14
|
|
|
@@ -26,14 +20,14 @@ exports.setupCIScript = async () => {
|
|
|
26
20
|
logInfo("Creating scripts/run-ci-checks.sh...");
|
|
27
21
|
}
|
|
28
22
|
|
|
29
|
-
await fs.writeFile(scriptPath, buildCIScript());
|
|
23
|
+
await fs.writeFile(scriptPath, buildCIScript(projectDir));
|
|
30
24
|
await fs.chmod(scriptPath, 0o755);
|
|
31
25
|
logSuccess("scripts/run-ci-checks.sh created.");
|
|
32
26
|
logInfo("To move tests to pre-commit in future: add './scripts/run-ci-checks.sh' to .husky/pre-commit.");
|
|
33
27
|
};
|
|
34
28
|
|
|
35
|
-
exports.setupPrePushHook = async () => {
|
|
36
|
-
const huskyDir = path.join(
|
|
29
|
+
exports.setupPrePushHook = async (gitRoot) => {
|
|
30
|
+
const huskyDir = path.join(gitRoot, '.husky');
|
|
37
31
|
const hookPath = path.join(huskyDir, 'pre-push');
|
|
38
32
|
|
|
39
33
|
if (!await fs.pathExists(huskyDir)) {
|
|
@@ -41,13 +35,15 @@ exports.setupPrePushHook = async () => {
|
|
|
41
35
|
return;
|
|
42
36
|
}
|
|
43
37
|
|
|
38
|
+
const projectDir = path.relative(gitRoot, process.cwd()) || '.';
|
|
39
|
+
|
|
44
40
|
if (await fs.pathExists(hookPath)) {
|
|
45
41
|
logInfo("Pre-push hook already configured. Overwriting with latest setup...");
|
|
46
42
|
} else {
|
|
47
43
|
logInfo("Creating new pre-push hook...");
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
await fs.writeFile(hookPath, buildPrePushHook());
|
|
46
|
+
await fs.writeFile(hookPath, buildPrePushHook(projectDir));
|
|
51
47
|
await fs.chmod(hookPath, 0o755);
|
|
52
48
|
logSuccess("Pre-push hook created — calls scripts/run-ci-checks.sh.");
|
|
53
49
|
};
|
|
@@ -85,12 +81,12 @@ exports.validateProject = async () => {
|
|
|
85
81
|
const scripts = pkg.scripts || {};
|
|
86
82
|
|
|
87
83
|
if (!scripts.start) {
|
|
88
|
-
logError('No "start" script in package.json —
|
|
84
|
+
logError('No "start" script in package.json — smoke tests will be skipped.');
|
|
89
85
|
logInfo('Add: "start": "node index.js"');
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
if (!scripts.test) {
|
|
93
|
-
logError('No "test" script in package.json — smoke tests will
|
|
89
|
+
logError('No "test" script in package.json — smoke tests will be skipped.');
|
|
94
90
|
logInfo('Add: "test": "jest" (or your test runner)');
|
|
95
91
|
}
|
|
96
92
|
|
|
@@ -117,19 +113,17 @@ exports.ensurePackageLock = async () => {
|
|
|
117
113
|
}
|
|
118
114
|
};
|
|
119
115
|
|
|
120
|
-
function buildPrePushHook() {
|
|
116
|
+
function buildPrePushHook(projectDir) {
|
|
117
|
+
const cdLine = projectDir !== '.' ? `cd "${projectDir}"` : '';
|
|
121
118
|
return `#!/bin/sh
|
|
122
119
|
|
|
123
|
-
# ---------------------------------------------------------------
|
|
124
120
|
# Pre-push hook — Newman + Smoke Tests
|
|
125
|
-
|
|
126
|
-
# ---------------------------------------------------------------
|
|
127
|
-
|
|
121
|
+
${cdLine ? cdLine + '\n' : ''}
|
|
128
122
|
./scripts/run-ci-checks.sh
|
|
129
123
|
`;
|
|
130
124
|
}
|
|
131
125
|
|
|
132
|
-
function buildCIScript() {
|
|
126
|
+
function buildCIScript(projectDir) {
|
|
133
127
|
return `#!/bin/sh
|
|
134
128
|
|
|
135
129
|
# ---------------------------------------------------------------
|
|
@@ -166,17 +160,17 @@ echo ""
|
|
|
166
160
|
echo "[CI Checks] Starting checks..."
|
|
167
161
|
|
|
168
162
|
# ---------------------------------------------------------------
|
|
169
|
-
# Check if start
|
|
163
|
+
# Check if start and test scripts exist
|
|
170
164
|
# ---------------------------------------------------------------
|
|
171
|
-
START_SCRIPT=$(node -e "const p=require('./package.json');
|
|
172
|
-
TEST_SCRIPT=$(node -e "const p=require('./package.json');
|
|
165
|
+
START_SCRIPT=$(node -e "try{const p=require('./package.json');console.log(p.scripts&&p.scripts.start?'yes':'no')}catch(e){console.log('no')}" 2>/dev/null)
|
|
166
|
+
TEST_SCRIPT=$(node -e "try{const p=require('./package.json');console.log(p.scripts&&p.scripts.test?'yes':'no')}catch(e){console.log('no')}" 2>/dev/null)
|
|
173
167
|
|
|
174
168
|
if [ "$START_SCRIPT" = "no" ]; then
|
|
175
169
|
echo "[Smoke Tests] No start script in package.json — skipping smoke tests."
|
|
176
170
|
else
|
|
177
171
|
|
|
178
172
|
# ---------------------------------------------------------------
|
|
179
|
-
# Step 1: Smoke Tests
|
|
173
|
+
# Step 1: Smoke Tests — start server + run tests
|
|
180
174
|
# ---------------------------------------------------------------
|
|
181
175
|
echo ""
|
|
182
176
|
echo "[Smoke Tests] Starting server..."
|
|
@@ -184,10 +178,10 @@ else
|
|
|
184
178
|
npm start &
|
|
185
179
|
SERVER_PID=\$!
|
|
186
180
|
|
|
187
|
-
# Auto-detect port — tries common ports
|
|
181
|
+
# Auto-detect port — tries all common ports
|
|
188
182
|
SERVER_UP=0
|
|
189
183
|
for i in \$(seq 1 30); do
|
|
190
|
-
for PORT_TRY in 3000 5000 8000 8080 4000 4200 3001; do
|
|
184
|
+
for PORT_TRY in 3000 5000 8000 8080 4000 4200 3001 8081 1337 9000; do
|
|
191
185
|
if curl -sf http://localhost:\$PORT_TRY > /dev/null 2>&1; then
|
|
192
186
|
PORT=\$PORT_TRY
|
|
193
187
|
SERVER_UP=1
|
|
@@ -221,7 +215,7 @@ else
|
|
|
221
215
|
fi
|
|
222
216
|
|
|
223
217
|
# ---------------------------------------------------------------
|
|
224
|
-
# Step 2: Newman
|
|
218
|
+
# Step 2: Newman API Tests
|
|
225
219
|
# ---------------------------------------------------------------
|
|
226
220
|
echo ""
|
|
227
221
|
echo "[Newman] Looking for Postman collections..."
|
|
@@ -241,7 +235,7 @@ else
|
|
|
241
235
|
|
|
242
236
|
if ! command -v newman > /dev/null 2>&1; then
|
|
243
237
|
echo "[Newman] Installing newman globally..."
|
|
244
|
-
npm install -g newman newman-reporter-htmlextra
|
|
238
|
+
npm install -g newman newman-reporter-htmlextra 2>/dev/null || true
|
|
245
239
|
fi
|
|
246
240
|
|
|
247
241
|
mkdir -p newman-reports
|
package/lib/git.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
+
// Returns { found, gitRoot, projectRoot }
|
|
5
|
+
// gitRoot = where .git folder is (where husky installs)
|
|
6
|
+
// projectRoot = where package.json is (where scripts/sonar/gitleaks live)
|
|
4
7
|
exports.isGitRepo = async () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
let dir = process.cwd();
|
|
8
|
+
const projectRoot = process.cwd();
|
|
9
|
+
let dir = projectRoot;
|
|
8
10
|
|
|
9
11
|
while (true) {
|
|
10
12
|
if (fs.existsSync(path.join(dir, '.git'))) {
|
|
11
|
-
return true;
|
|
13
|
+
return { found: true, gitRoot: dir, projectRoot };
|
|
12
14
|
}
|
|
13
15
|
const parent = path.dirname(dir);
|
|
14
|
-
if (parent === dir) break;
|
|
16
|
+
if (parent === dir) break;
|
|
15
17
|
dir = parent;
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
return false;
|
|
19
|
+
return { found: false, gitRoot: null, projectRoot };
|
|
19
20
|
};
|
package/lib/hooks.js
CHANGED
|
@@ -2,8 +2,8 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { logInfo, logSuccess } = require('./logger');
|
|
4
4
|
|
|
5
|
-
exports.setupPreCommitHook = async () => {
|
|
6
|
-
const huskyDir = path.join(
|
|
5
|
+
exports.setupPreCommitHook = async (gitRoot) => {
|
|
6
|
+
const huskyDir = path.join(gitRoot, '.husky');
|
|
7
7
|
const hookPath = path.join(huskyDir, 'pre-commit');
|
|
8
8
|
|
|
9
9
|
if (!await fs.pathExists(huskyDir)) {
|
|
@@ -11,7 +11,11 @@ exports.setupPreCommitHook = async () => {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// projectDir = where package.json, sonar-project.properties, .tools/ live
|
|
15
|
+
// This is relative path from gitRoot to project (e.g. "server" or ".")
|
|
16
|
+
const projectDir = path.relative(gitRoot, process.cwd()) || '.';
|
|
17
|
+
|
|
18
|
+
const hookContent = buildHookScript(projectDir);
|
|
15
19
|
|
|
16
20
|
if (await fs.pathExists(hookPath)) {
|
|
17
21
|
logInfo("Pre-commit hook already configured. Overwriting with latest setup...");
|
|
@@ -22,6 +26,7 @@ exports.setupPreCommitHook = async () => {
|
|
|
22
26
|
await fs.writeFile(hookPath, hookContent);
|
|
23
27
|
await fs.chmod(hookPath, 0o755);
|
|
24
28
|
|
|
29
|
+
// .gitleaksignore goes in the project dir, not git root
|
|
25
30
|
const gitleaksIgnorePath = path.join(process.cwd(), '.gitleaksignore');
|
|
26
31
|
await fs.writeFile(gitleaksIgnorePath, '.tools/\nsonar-project.properties\n');
|
|
27
32
|
logInfo(".gitleaksignore created — excluding .tools/ and sonar-project.properties.");
|
|
@@ -29,9 +34,23 @@ exports.setupPreCommitHook = async () => {
|
|
|
29
34
|
logSuccess("Pre-commit hook created with Gitleaks + SonarQube (git diff only).");
|
|
30
35
|
};
|
|
31
36
|
|
|
32
|
-
function buildHookScript() {
|
|
37
|
+
function buildHookScript(projectDir) {
|
|
38
|
+
// If project is in a subfolder, all tool paths must be prefixed
|
|
39
|
+
const cdLine = projectDir !== '.' ? `cd "${projectDir}"` : '';
|
|
40
|
+
const gitleaksBin = projectDir !== '.'
|
|
41
|
+
? `./${projectDir}/.tools/gitleaks/gitleaks`
|
|
42
|
+
: `./.tools/gitleaks/gitleaks`;
|
|
43
|
+
const sonarBin = projectDir !== '.'
|
|
44
|
+
? `./${projectDir}/node_modules/.bin/sonar-scanner`
|
|
45
|
+
: `./node_modules/.bin/sonar-scanner`;
|
|
46
|
+
const sonarProps = projectDir !== '.'
|
|
47
|
+
? `./${projectDir}/sonar-project.properties`
|
|
48
|
+
: `./sonar-project.properties`;
|
|
49
|
+
|
|
33
50
|
return `#!/bin/sh
|
|
34
51
|
|
|
52
|
+
# Move to project directory if in monorepo/subfolder setup
|
|
53
|
+
${cdLine ? cdLine + '\n' : ''}
|
|
35
54
|
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
|
|
36
55
|
|
|
37
56
|
if [ -z "$STAGED_FILES" ]; then
|
|
@@ -47,7 +66,7 @@ done
|
|
|
47
66
|
echo ""
|
|
48
67
|
echo "[Gitleaks] Scanning changed files for secrets..."
|
|
49
68
|
|
|
50
|
-
GITLEAKS_BIN="
|
|
69
|
+
GITLEAKS_BIN="${gitleaksBin}"
|
|
51
70
|
|
|
52
71
|
if [ ! -f "$GITLEAKS_BIN" ]; then
|
|
53
72
|
echo "[Gitleaks] Binary not found. Skipping."
|
|
@@ -58,6 +77,7 @@ else
|
|
|
58
77
|
case "$FILE" in
|
|
59
78
|
sonar-project.properties) ;;
|
|
60
79
|
.tools/*) ;;
|
|
80
|
+
${projectDir !== '.' ? `${projectDir}/.tools/*) ;;` : ''}
|
|
61
81
|
*)
|
|
62
82
|
if [ -f "$FILE" ]; then
|
|
63
83
|
DEST="$GITLEAKS_TMPDIR/$FILE"
|
|
@@ -83,15 +103,16 @@ fi
|
|
|
83
103
|
echo ""
|
|
84
104
|
echo "[SonarQube] Scanning changed files..."
|
|
85
105
|
|
|
86
|
-
SONAR_BIN="
|
|
106
|
+
SONAR_BIN="${sonarBin}"
|
|
107
|
+
SONAR_PROPS="${sonarProps}"
|
|
87
108
|
|
|
88
109
|
if [ ! -f "$SONAR_BIN" ]; then
|
|
89
110
|
echo "[SonarQube] sonar-scanner not found. Skipping."
|
|
90
111
|
else
|
|
91
|
-
if [ ! -f "
|
|
112
|
+
if [ ! -f "$SONAR_PROPS" ]; then
|
|
92
113
|
echo "[SonarQube] sonar-project.properties not found. Skipping."
|
|
93
114
|
else
|
|
94
|
-
SONAR_HOST=$(grep "^sonar.host.url="
|
|
115
|
+
SONAR_HOST=$(grep "^sonar.host.url=" "$SONAR_PROPS" | cut -d'=' -f2 | tr -d '[:space:]')
|
|
95
116
|
SONAR_DOMAIN=$(echo "$SONAR_HOST" | sed 's|https://||' | sed 's|http://||' | cut -d'/' -f1 | cut -d':' -f1)
|
|
96
117
|
SONAR_PORT=$(echo "$SONAR_HOST" | grep -o ':[0-9]*$' | tr -d ':')
|
|
97
118
|
SONAR_PORT=\${SONAR_PORT:-9000}
|
|
@@ -99,15 +120,15 @@ else
|
|
|
99
120
|
if ! nc -z -w3 "$SONAR_DOMAIN" "$SONAR_PORT" 2>/dev/null; then
|
|
100
121
|
echo "[SonarQube] Server unreachable — skipping analysis."
|
|
101
122
|
else
|
|
102
|
-
SONAR_INCLUSIONS=$(echo "$STAGED_FILES" | tr '
|
|
123
|
+
SONAR_INCLUSIONS=$(echo "$STAGED_FILES" | tr '\\n' ',' | sed 's/,$//')
|
|
103
124
|
echo "[SonarQube] Scanning: $SONAR_INCLUSIONS"
|
|
104
125
|
|
|
105
|
-
$SONAR_BIN -Dsonar.inclusions="$SONAR_INCLUSIONS" -Dsonar.qualitygate.wait=true
|
|
126
|
+
$SONAR_BIN -Dproject.settings="$SONAR_PROPS" -Dsonar.inclusions="$SONAR_INCLUSIONS" -Dsonar.qualitygate.wait=true
|
|
106
127
|
SONAR_EXIT=$?
|
|
107
128
|
|
|
108
129
|
if [ $SONAR_EXIT -ne 0 ]; then
|
|
109
130
|
echo "[SonarQube] Quality Gate FAILED. Commit blocked."
|
|
110
|
-
echo "[SonarQube] Fix issues at: $SONAR_HOST
|
|
131
|
+
echo "[SonarQube] Fix issues at: $SONAR_HOST"
|
|
111
132
|
exit 1
|
|
112
133
|
fi
|
|
113
134
|
|
package/lib/husky.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { readJSON, writeJSON } = require('./utils');
|
|
2
2
|
const { installDevDependency } = require('./packageManager');
|
|
3
3
|
const execa = require('execa');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
|
-
const { logInfo, logSuccess } = require('./logger');
|
|
6
|
+
const { logInfo, logSuccess, logError } = require('./logger');
|
|
7
7
|
|
|
8
|
-
exports.installHusky = async () => {
|
|
8
|
+
exports.installHusky = async (gitRoot) => {
|
|
9
9
|
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
10
|
+
|
|
11
|
+
if (!await fs.pathExists(pkgPath)) {
|
|
12
|
+
throw new Error(`No package.json found in ${process.cwd()}. Please run from your project root.`);
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
const pkg = await readJSON(pkgPath);
|
|
11
16
|
|
|
12
17
|
if (!pkg.devDependencies || !pkg.devDependencies.husky) {
|
|
@@ -15,13 +20,23 @@ exports.installHusky = async () => {
|
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
logInfo("Initializing Husky...");
|
|
18
|
-
await execa('npx', ['husky', 'install'], { stdio: 'inherit' });
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
// Always run husky install from the git root — this is where .husky/ is created
|
|
25
|
+
await execa('npx', ['husky', 'install'], { stdio: 'inherit', cwd: gitRoot });
|
|
21
26
|
|
|
27
|
+
// Add prepare script to the project's package.json
|
|
28
|
+
// If project is a subfolder, prepare script needs to reference path to git root
|
|
29
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
22
30
|
if (!pkg.scripts.prepare) {
|
|
23
|
-
|
|
31
|
+
const isSubfolder = gitRoot !== process.cwd();
|
|
32
|
+
if (isSubfolder) {
|
|
33
|
+
// Calculate relative path from project root to git root for husky install
|
|
34
|
+
const relPath = path.relative(process.cwd(), gitRoot);
|
|
35
|
+
pkg.scripts.prepare = `cd ${relPath} && husky install ${path.relative(gitRoot, process.cwd())}/.husky || true`;
|
|
36
|
+
} else {
|
|
37
|
+
pkg.scripts.prepare = "husky install";
|
|
38
|
+
}
|
|
24
39
|
await writeJSON(pkgPath, pkg);
|
|
25
40
|
logSuccess("Added prepare script.");
|
|
26
41
|
}
|
|
27
|
-
};
|
|
42
|
+
};
|