vtasks-automate-cli 0.9.8 → 0.9.9
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/content/index.js +168 -144
- package/hooks/commit-msg +73 -0
- package/hooks/post-checkout +20 -0
- package/hooks/post-commit +3 -0
- package/hooks/pre-commit +24 -0
- package/hooks/pre-push +55 -0
- package/hooks/validate-config-dist.js +233 -0
- package/init.js +146 -160
- package/lib/hooks.js +55 -0
- package/lib/update-hooks.js +10 -0
- package/package.json +1 -1
package/content/index.js
CHANGED
|
@@ -1,144 +1,168 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const LOG_COLORS = {
|
|
7
|
-
RED: "\x1b[31m",
|
|
8
|
-
GREEN: "\x1b[32m",
|
|
9
|
-
YELLOW: "\x1b[33m",
|
|
10
|
-
BLUE: "\x1b[34m",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const VTASKS_URL =
|
|
14
|
-
"https://vtasks.venturing.com.ar/vTasks/api/project/action/track";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
console.log(LOG_COLORS.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1
|
+
import { exec, execSync } from "child_process";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const LOG_COLORS = {
|
|
7
|
+
RED: "\x1b[31m",
|
|
8
|
+
GREEN: "\x1b[32m",
|
|
9
|
+
YELLOW: "\x1b[33m",
|
|
10
|
+
BLUE: "\x1b[34m",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const VTASKS_URL =
|
|
14
|
+
"https://vtasks.venturing.com.ar/vTasks/api/project/action/track";
|
|
15
|
+
const VTASKS_PKG = "vtasks-automate-cli";
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
function getInstalledVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
22
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
23
|
+
return pkg.version || null;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getLatestVersion() {
|
|
30
|
+
try {
|
|
31
|
+
return execSync(`npm show ${VTASKS_PKG} version`, { encoding: "utf-8" }).trim();
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function checkAndUpdate() {
|
|
38
|
+
const installed = getInstalledVersion();
|
|
39
|
+
if (!installed) return;
|
|
40
|
+
|
|
41
|
+
const latest = getLatestVersion();
|
|
42
|
+
if (!latest || installed === latest) return;
|
|
43
|
+
|
|
44
|
+
console.log(LOG_COLORS.BLUE, `Updating ${VTASKS_PKG} (${installed} → ${latest})...`);
|
|
45
|
+
execSync(`npm install ${VTASKS_PKG}@latest`, { cwd: process.cwd(), stdio: "inherit" });
|
|
46
|
+
console.log(LOG_COLORS.GREEN, `Updated to ${VTASKS_PKG}@${latest}`);
|
|
47
|
+
|
|
48
|
+
const githooksDir = path.join(process.cwd(), "..", ".githooks");
|
|
49
|
+
const updateHooksScript = path.join(
|
|
50
|
+
process.cwd(),
|
|
51
|
+
"node_modules",
|
|
52
|
+
VTASKS_PKG,
|
|
53
|
+
"lib",
|
|
54
|
+
"update-hooks.js"
|
|
55
|
+
);
|
|
56
|
+
if (fs.existsSync(updateHooksScript)) {
|
|
57
|
+
execSync(`node "${updateHooksScript}" "${githooksDir}"`, { stdio: "inherit" });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class GitService {
|
|
62
|
+
static async getCurrentBranch() {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
exec("git branch --show-current", (error, stdout) => {
|
|
65
|
+
if (error) {
|
|
66
|
+
reject("Error getting current branch: Error executing git cmd");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
resolve(stdout.trim());
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static async getCurrentUser() {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
exec("git config --list", (error, stdout) => {
|
|
77
|
+
if (error) {
|
|
78
|
+
reject("Error getting user email: Error executing git cmd");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const item = stdout.split("\n").find((t) => t.startsWith("user.email"));
|
|
82
|
+
if (!item) {
|
|
83
|
+
reject("Error getting user email: user.email not found");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
resolve(item.split("=")[1].trim());
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static async getCurrentProject() {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
exec("git remote -v", (error, stdout) => {
|
|
94
|
+
if (error) {
|
|
95
|
+
console.log(LOG_COLORS.RED, "Error getting project name: Error executing git cmd");
|
|
96
|
+
reject("Error getting project name: Error executing git cmd");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const item = stdout.split("\n").find((t) => t.startsWith("origin"));
|
|
100
|
+
if (!item) {
|
|
101
|
+
reject("Error getting project name: origin not found");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const url = item.split(" ").find((t) => t.includes("http") || t.includes("git@github"));
|
|
105
|
+
const urlSplit = url.split("/");
|
|
106
|
+
const projectName = urlSplit[urlSplit.length - 1].replace(".git", "");
|
|
107
|
+
resolve(projectName);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function main() {
|
|
114
|
+
console.log(LOG_COLORS.BLUE, "Checking vTasks config:");
|
|
115
|
+
|
|
116
|
+
checkAndUpdate();
|
|
117
|
+
|
|
118
|
+
const branch = await GitService.getCurrentBranch();
|
|
119
|
+
|
|
120
|
+
if (branch === "qa" || branch === "dev" || branch === "master") {
|
|
121
|
+
console.log(LOG_COLORS.YELLOW, "Non issue branch - Skip checks and movement");
|
|
122
|
+
return process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const userEmail = await GitService.getCurrentUser();
|
|
126
|
+
if (!userEmail) {
|
|
127
|
+
console.log(LOG_COLORS.RED, "Git user email not found");
|
|
128
|
+
throw new Error("Git user email not found");
|
|
129
|
+
}
|
|
130
|
+
console.log(LOG_COLORS.GREEN, `User email: ${userEmail}`);
|
|
131
|
+
|
|
132
|
+
const projectName = await GitService.getCurrentProject();
|
|
133
|
+
if (!projectName) {
|
|
134
|
+
console.log(LOG_COLORS.RED, "Missing project name configuration");
|
|
135
|
+
throw new Error("Missing Project name");
|
|
136
|
+
}
|
|
137
|
+
console.log(LOG_COLORS.GREEN, `Project: ${projectName}`);
|
|
138
|
+
|
|
139
|
+
const issueNumber = Number(branch.split("_")[0]);
|
|
140
|
+
if (isNaN(issueNumber)) {
|
|
141
|
+
console.log(LOG_COLORS.YELLOW, "Non issue branch - Skip checks and movement");
|
|
142
|
+
return process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
console.log(LOG_COLORS.GREEN, `Currently working on issue: ${issueNumber}`);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const response = await fetch(VTASKS_URL, {
|
|
148
|
+
method: "POST",
|
|
149
|
+
body: JSON.stringify({ projectName, branch, userEmail, issueNumber }),
|
|
150
|
+
headers: { "Content-Type": "application/json" },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
const errorBody = await response.text();
|
|
155
|
+
console.log(LOG_COLORS.RED, `Error al actualizar el estado de la tarea (HTTP ${response.status})`);
|
|
156
|
+
console.log(LOG_COLORS.RED, errorBody);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await response.json();
|
|
161
|
+
console.log(LOG_COLORS.GREEN, "vTasks status changed successfully!");
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.log(LOG_COLORS.RED, "Error al actualizar el estado de la tarea");
|
|
164
|
+
console.log(LOG_COLORS.RED, error.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
main();
|
package/hooks/commit-msg
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# .githooks/commit-msg
|
|
3
|
+
|
|
4
|
+
MSG_FILE="$1"
|
|
5
|
+
COMMIT_MSG=$(cat "$MSG_FILE")
|
|
6
|
+
|
|
7
|
+
# --- Extract issue number from branch pattern ###_title ---
|
|
8
|
+
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
|
|
9
|
+
ISSUE_NUMBER=$(echo "$BRANCH_NAME" | grep -oE '^[0-9]+')
|
|
10
|
+
|
|
11
|
+
# --- Validate: reject if no issue number in branch AND none in original message ---
|
|
12
|
+
ISSUE_IN_ORIGINAL=$(echo "$COMMIT_MSG" | grep -oE '#[0-9]+' | head -1)
|
|
13
|
+
if [ -z "$ISSUE_NUMBER" ] && [ -z "$ISSUE_IN_ORIGINAL" ]; then
|
|
14
|
+
echo "ERROR: No issue number found!"
|
|
15
|
+
echo ""
|
|
16
|
+
echo "Please include an issue number in one of these ways:"
|
|
17
|
+
echo " 1. Add '#XXX' to the end of your commit message (where XXX is the issue number)"
|
|
18
|
+
echo " 2. Use a branch name that starts with the issue number (e.g., '123_feature_description')"
|
|
19
|
+
echo ""
|
|
20
|
+
echo "Example: git commit -m \"Fix login bug #123\""
|
|
21
|
+
echo ""
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# --- Add issue number to first line if not already present ---
|
|
26
|
+
FIRST_LINE=$(printf '%s' "$COMMIT_MSG" | head -1)
|
|
27
|
+
ISSUE_IN_FIRST_LINE=$(echo "$FIRST_LINE" | grep -oE '#[0-9]+' | head -1)
|
|
28
|
+
|
|
29
|
+
if [ -n "$ISSUE_NUMBER" ] && [ -z "$ISSUE_IN_FIRST_LINE" ]; then
|
|
30
|
+
REST=$(printf '%s' "$COMMIT_MSG" | tail -n +2)
|
|
31
|
+
if [ -n "$REST" ]; then
|
|
32
|
+
COMMIT_MSG="$FIRST_LINE #$ISSUE_NUMBER$REST"
|
|
33
|
+
else
|
|
34
|
+
COMMIT_MSG="$FIRST_LINE #$ISSUE_NUMBER"
|
|
35
|
+
fi
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# --- Get staged diff; fallback to HEAD if empty ---
|
|
39
|
+
DIFF=$(git diff --cached --patch)
|
|
40
|
+
if [ -z "$DIFF" ]; then
|
|
41
|
+
DIFF=$(git diff HEAD --patch)
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# --- Only run AI if diff is non-empty ---
|
|
45
|
+
if [ -n "$DIFF" ]; then
|
|
46
|
+
PROMPT=$(printf "The developer wrote this commit message:\n\n\"%s\"\n\nThe git diff is:\n\n%s\n\nTask: Append a short, non-redundant summary of the changes (max ~5 lines). Do NOT modify or repeat the issue reference. Do NOT include any tips, hints, metadata or further introductions or salutations in your response - only the summary content." "$COMMIT_MSG" "$DIFF")
|
|
47
|
+
|
|
48
|
+
AI_APPEND=$(echo "$PROMPT" | claude --print 2>&1)
|
|
49
|
+
STATUS=$?
|
|
50
|
+
|
|
51
|
+
if [ $STATUS -ne 0 ] || [ -z "$AI_APPEND" ]; then
|
|
52
|
+
echo "[commit-msg hook warning] AI augmentation failed or returned empty, proceeding without it."
|
|
53
|
+
else
|
|
54
|
+
COMMIT_MSG="$COMMIT_MSG
|
|
55
|
+
|
|
56
|
+
Details:
|
|
57
|
+
$AI_APPEND"
|
|
58
|
+
fi
|
|
59
|
+
else
|
|
60
|
+
echo "[commit-msg hook info] No staged changes detected, skipping AI augmentation."
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# --- Always append issue number at the end ---
|
|
64
|
+
if [ -n "$ISSUE_NUMBER" ]; then
|
|
65
|
+
COMMIT_MSG="$COMMIT_MSG
|
|
66
|
+
|
|
67
|
+
#$ISSUE_NUMBER"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# --- Write final commit message ---
|
|
71
|
+
printf '%s' "$COMMIT_MSG" > "$MSG_FILE"
|
|
72
|
+
|
|
73
|
+
exit 0
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
echo -e "\e[34mSwitching current branch\e[0m"
|
|
4
|
+
|
|
5
|
+
folders=("client" "server")
|
|
6
|
+
executed=false
|
|
7
|
+
|
|
8
|
+
for folder in "${folders[@]}"; do
|
|
9
|
+
script_path="./$folder/node_modules/vtasks-automate-cli/content/index.js"
|
|
10
|
+
if [ -f "$script_path" ]; then
|
|
11
|
+
echo -e "\e[32mRunning vTasks script in $script_path\e[0m"
|
|
12
|
+
node "$script_path"
|
|
13
|
+
executed=true
|
|
14
|
+
break
|
|
15
|
+
fi
|
|
16
|
+
done
|
|
17
|
+
|
|
18
|
+
if [ "$executed" = false ]; then
|
|
19
|
+
echo -e "\e[33mNo vTasks script found in client or server\e[0m"
|
|
20
|
+
fi
|
package/hooks/pre-commit
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# Validate configuration .dist files
|
|
4
|
+
echo "Validating configuration .dist files..."
|
|
5
|
+
node .githooks/validate-config-dist.js
|
|
6
|
+
if [ $? -ne 0 ]; then
|
|
7
|
+
echo "Configuration validation failed. Commit aborted."
|
|
8
|
+
exit 1
|
|
9
|
+
else
|
|
10
|
+
echo "Configuration validation succeeded"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Apply prettier formatting to staged files
|
|
14
|
+
echo "Applying code formatting..."
|
|
15
|
+
FILES=$(git diff --cached --name-only --diff-filter=ACMR "*.js" "*.ts" "*.tsx" "*.scss" | sed 's| |\\ |g')
|
|
16
|
+
if [ -n "$FILES" ]; then
|
|
17
|
+
echo "$FILES" | xargs ./node_modules/.bin/prettier --write
|
|
18
|
+
echo "$FILES" | xargs git add
|
|
19
|
+
echo "Code formatting applied"
|
|
20
|
+
else
|
|
21
|
+
echo "No files to format"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
exit 0
|
package/hooks/pre-push
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
|
|
4
|
+
|
|
5
|
+
compile_client_project() {
|
|
6
|
+
echo "Compiling client project..."
|
|
7
|
+
cd "$PROJECT_ROOT/client"
|
|
8
|
+
npm run build
|
|
9
|
+
|
|
10
|
+
if [ $? -ne 0 ]; then
|
|
11
|
+
echo ""
|
|
12
|
+
echo "CLIENT COMPILATION FAILED!"
|
|
13
|
+
echo ""
|
|
14
|
+
echo "The Next.js client project failed to build. This usually indicates:"
|
|
15
|
+
echo " - TypeScript compilation errors"
|
|
16
|
+
echo " - Missing dependencies"
|
|
17
|
+
echo " - Syntax errors in React components"
|
|
18
|
+
echo " - Import/export issues"
|
|
19
|
+
echo ""
|
|
20
|
+
echo "Please fix the compilation errors above and try pushing again."
|
|
21
|
+
echo "You can run 'npm run build' in the client directory to see the full error details."
|
|
22
|
+
echo ""
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
echo "Client project compiled successfully"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
compile_server_project() {
|
|
29
|
+
echo "Compiling server project..."
|
|
30
|
+
cd "$PROJECT_ROOT/server"
|
|
31
|
+
npm run build
|
|
32
|
+
|
|
33
|
+
if [ $? -ne 0 ]; then
|
|
34
|
+
echo ""
|
|
35
|
+
echo "SERVER COMPILATION FAILED!"
|
|
36
|
+
echo ""
|
|
37
|
+
echo "The Node.js server project failed to build. This usually indicates:"
|
|
38
|
+
echo " - TypeScript compilation errors"
|
|
39
|
+
echo " - Missing dependencies"
|
|
40
|
+
echo " - Database entity/migration issues"
|
|
41
|
+
echo " - Import/export issues"
|
|
42
|
+
echo " - Configuration problems"
|
|
43
|
+
echo ""
|
|
44
|
+
echo "Please fix the compilation errors above and try pushing again."
|
|
45
|
+
echo "You can run 'npm run build' in the server directory to see the full error details."
|
|
46
|
+
echo ""
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
echo "Server project compiled successfully"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
compile_client_project
|
|
53
|
+
compile_server_project
|
|
54
|
+
|
|
55
|
+
exit 0
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function findDistFiles(dir, distFiles = []) {
|
|
7
|
+
if (!fs.existsSync(dir)) {
|
|
8
|
+
return distFiles;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
12
|
+
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const fullPath = path.join(dir, entry.name);
|
|
15
|
+
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
if (entry.name !== 'node_modules') {
|
|
18
|
+
findDistFiles(fullPath, distFiles);
|
|
19
|
+
}
|
|
20
|
+
} else if (entry.isFile() && entry.name.endsWith('.dist')) {
|
|
21
|
+
distFiles.push(fullPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return distFiles;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractObjectKeys(obj, prefix = '', keys = new Set()) {
|
|
29
|
+
if (obj === null || typeof obj !== 'object') {
|
|
30
|
+
return keys;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (Array.isArray(obj)) {
|
|
34
|
+
keys.add(prefix);
|
|
35
|
+
return keys;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
39
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
40
|
+
keys.add(fullPath);
|
|
41
|
+
|
|
42
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
43
|
+
extractObjectKeys(value, fullPath, keys);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return keys;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function safeEvaluateConfig(filePath) {
|
|
51
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
52
|
+
|
|
53
|
+
if (content.includes('export default')) {
|
|
54
|
+
const tempFilePath = path.join(process.cwd(), '.githooks', 'temp_config.js');
|
|
55
|
+
|
|
56
|
+
let transformedContent = content
|
|
57
|
+
.replace(/import\s+\*\s+as\s+path\s+from\s+['"]path['"];?/g, 'const path = require("path");')
|
|
58
|
+
.replace(/import\s+.*?from\s+['"].*?['"];?/g, '')
|
|
59
|
+
.replace(/export\s+default\s+/, 'module.exports = ')
|
|
60
|
+
.replace(/__dirname/g, `"${path.dirname(path.resolve(filePath)).replace(/\\/g, '/')}"`);
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(tempFilePath, transformedContent);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
delete require.cache[path.resolve(tempFilePath)];
|
|
66
|
+
const config = require(path.resolve(tempFilePath));
|
|
67
|
+
fs.unlinkSync(tempFilePath);
|
|
68
|
+
return config;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (fs.existsSync(tempFilePath)) {
|
|
71
|
+
fs.unlinkSync(tempFilePath);
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
} else if (content.includes('module.exports')) {
|
|
76
|
+
delete require.cache[path.resolve(filePath)];
|
|
77
|
+
return require(path.resolve(filePath));
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error('Unsupported module format');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validateJsTsConfig(actualFilePath, distFilePath) {
|
|
84
|
+
try {
|
|
85
|
+
const actualConfig = safeEvaluateConfig(actualFilePath);
|
|
86
|
+
const distConfig = safeEvaluateConfig(distFilePath);
|
|
87
|
+
|
|
88
|
+
const actualKeys = extractObjectKeys(actualConfig);
|
|
89
|
+
const distKeys = extractObjectKeys(distConfig);
|
|
90
|
+
|
|
91
|
+
const missingKeys = [];
|
|
92
|
+
for (const key of actualKeys) {
|
|
93
|
+
if (!distKeys.has(key)) {
|
|
94
|
+
missingKeys.push(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return missingKeys;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(`Error validating JS/TS config ${actualFilePath}:`, error.message);
|
|
101
|
+
return [`Error reading file: ${error.message}`];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function validateEnvConfig(actualFilePath, distFilePath) {
|
|
106
|
+
try {
|
|
107
|
+
const actualContent = fs.readFileSync(actualFilePath, 'utf8');
|
|
108
|
+
const distContent = fs.readFileSync(distFilePath, 'utf8');
|
|
109
|
+
|
|
110
|
+
const parseEnvFile = (content) => {
|
|
111
|
+
const keys = new Set();
|
|
112
|
+
for (const line of content.split('\n')) {
|
|
113
|
+
const trimmedLine = line.trim();
|
|
114
|
+
if (trimmedLine && !trimmedLine.startsWith('#')) {
|
|
115
|
+
const equalsIndex = trimmedLine.indexOf('=');
|
|
116
|
+
if (equalsIndex > 0) {
|
|
117
|
+
keys.add(trimmedLine.substring(0, equalsIndex).trim());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return keys;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const actualKeys = parseEnvFile(actualContent);
|
|
125
|
+
const distKeys = parseEnvFile(distContent);
|
|
126
|
+
|
|
127
|
+
const missingKeys = [];
|
|
128
|
+
for (const key of actualKeys) {
|
|
129
|
+
if (!distKeys.has(key)) {
|
|
130
|
+
missingKeys.push(key);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return missingKeys;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(`Error validating .env config ${actualFilePath}:`, error.message);
|
|
137
|
+
return [`Error reading file: ${error.message}`];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function validateJsonConfig(actualFilePath, distFilePath) {
|
|
142
|
+
try {
|
|
143
|
+
const actualConfig = JSON.parse(fs.readFileSync(actualFilePath, 'utf8'));
|
|
144
|
+
const distConfig = JSON.parse(fs.readFileSync(distFilePath, 'utf8'));
|
|
145
|
+
|
|
146
|
+
const actualKeys = extractObjectKeys(actualConfig);
|
|
147
|
+
const distKeys = extractObjectKeys(distConfig);
|
|
148
|
+
|
|
149
|
+
const missingKeys = [];
|
|
150
|
+
for (const key of actualKeys) {
|
|
151
|
+
if (!distKeys.has(key)) {
|
|
152
|
+
missingKeys.push(key);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return missingKeys;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`Error validating JSON config ${actualFilePath}:`, error.message);
|
|
159
|
+
return [`Error reading file: ${error.message}`];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function validateConfigDist() {
|
|
164
|
+
const targetDirs = ['client', 'server'];
|
|
165
|
+
const allDistFiles = [];
|
|
166
|
+
|
|
167
|
+
for (const dir of targetDirs) {
|
|
168
|
+
if (fs.existsSync(dir)) {
|
|
169
|
+
findDistFiles(dir, allDistFiles);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (allDistFiles.length === 0) {
|
|
174
|
+
console.log('No .dist files found in client or server directories.');
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let hasErrors = false;
|
|
179
|
+
const errors = {};
|
|
180
|
+
|
|
181
|
+
for (const distFile of allDistFiles) {
|
|
182
|
+
const actualFile = distFile.replace(/\.dist$/, '');
|
|
183
|
+
|
|
184
|
+
if (!fs.existsSync(actualFile)) {
|
|
185
|
+
console.error(`ERROR: Configuration file ${actualFile} does not exist for ${distFile}`);
|
|
186
|
+
hasErrors = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let missingKeys = [];
|
|
191
|
+
|
|
192
|
+
const actualFileExt = path.extname(actualFile).toLowerCase();
|
|
193
|
+
const actualFileName = path.basename(actualFile);
|
|
194
|
+
|
|
195
|
+
if (actualFileExt === '.js' || actualFileExt === '.ts') {
|
|
196
|
+
missingKeys = validateJsTsConfig(actualFile, distFile);
|
|
197
|
+
} else if (actualFileName === '.env' || actualFileExt === '.env') {
|
|
198
|
+
missingKeys = validateEnvConfig(actualFile, distFile);
|
|
199
|
+
} else if (actualFileExt === '.json') {
|
|
200
|
+
missingKeys = validateJsonConfig(actualFile, distFile);
|
|
201
|
+
} else {
|
|
202
|
+
console.warn(`Warning: Unknown file type for ${actualFile}, skipping validation.`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (missingKeys.length > 0) {
|
|
207
|
+
errors[distFile] = missingKeys;
|
|
208
|
+
hasErrors = true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (hasErrors) {
|
|
213
|
+
console.error('\nConfiguration validation failed!');
|
|
214
|
+
console.error('The following .dist files are missing keys that exist in their corresponding configuration files:\n');
|
|
215
|
+
|
|
216
|
+
for (const [distFile, missingKeys] of Object.entries(errors)) {
|
|
217
|
+
console.error(`${distFile}:`);
|
|
218
|
+
for (const key of missingKeys) {
|
|
219
|
+
console.error(` Missing key: ${key}`);
|
|
220
|
+
}
|
|
221
|
+
console.error('');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.error('Please update the .dist files to include all configuration keys from their corresponding actual files.\n');
|
|
225
|
+
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const isValid = validateConfigDist();
|
|
233
|
+
process.exit(isValid ? 0 : 1);
|
package/init.js
CHANGED
|
@@ -1,160 +1,146 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const newContent = packageContent.replace(oldCmd, newCmd);
|
|
148
|
-
fs.writeFileSync(packagePath, newContent);
|
|
149
|
-
console.log(LOG_COLORS.GREEN, "Server app start script modified");
|
|
150
|
-
} else {
|
|
151
|
-
console.log(LOG_COLORS.YELLOW, "Server app start script not found");
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
console.log(LOG_COLORS.YELLOW, "Server app not found");
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
console.log(LOG_COLORS.GREEN, "Set Up completed ✓");
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
import { updateHooks } from "./lib/hooks.js";
|
|
7
|
+
|
|
8
|
+
const LOG_COLORS = {
|
|
9
|
+
RED: "\x1b[31m",
|
|
10
|
+
GREEN: "\x1b[32m",
|
|
11
|
+
YELLOW: "\x1b[33m",
|
|
12
|
+
BLUE: "\x1b[34m",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const FOLDERS = ["client", "server"];
|
|
16
|
+
const PKJSON = "package.json";
|
|
17
|
+
const VTASKS_PKG = "vtasks-automate-cli";
|
|
18
|
+
const VTASKS_SCRIPT = `node node_modules/${VTASKS_PKG}/content/index.js`;
|
|
19
|
+
|
|
20
|
+
function getCmdContent(content = "", cmd = "dev") {
|
|
21
|
+
const cmdToGet = `"${cmd}":`;
|
|
22
|
+
const cmdLength = cmdToGet.length;
|
|
23
|
+
for (let i = cmdLength; i < content.length; i++) {
|
|
24
|
+
const str = content.substring(i - cmdLength, i);
|
|
25
|
+
if (str == cmdToGet) {
|
|
26
|
+
let startMarkIndex;
|
|
27
|
+
let endMarkIndex;
|
|
28
|
+
while ((!startMarkIndex || !endMarkIndex) && i < content.length) {
|
|
29
|
+
if (content[i] == `"`) {
|
|
30
|
+
if (!startMarkIndex) {
|
|
31
|
+
startMarkIndex = i;
|
|
32
|
+
} else {
|
|
33
|
+
endMarkIndex = i;
|
|
34
|
+
return content.substring(startMarkIndex + 1, endMarkIndex);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getInstalledVersion(folderPath) {
|
|
45
|
+
const pkgPath = path.join(folderPath, "node_modules", VTASKS_PKG, PKJSON);
|
|
46
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
47
|
+
try {
|
|
48
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
49
|
+
return pkg.version || null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getLatestVersion(folderPath) {
|
|
56
|
+
try {
|
|
57
|
+
const result = execSync(`npm show ${VTASKS_PKG} version`, {
|
|
58
|
+
cwd: folderPath,
|
|
59
|
+
encoding: "utf-8",
|
|
60
|
+
});
|
|
61
|
+
return result.trim();
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function installOrUpdate(folderPath, folderName) {
|
|
68
|
+
const installed = getInstalledVersion(folderPath);
|
|
69
|
+
|
|
70
|
+
if (!installed) {
|
|
71
|
+
console.log(LOG_COLORS.BLUE, `Installing ${VTASKS_PKG} at ./${folderName}...`);
|
|
72
|
+
execSync(`npm install ${VTASKS_PKG}`, { cwd: folderPath, stdio: "inherit" });
|
|
73
|
+
console.log(LOG_COLORS.GREEN, `Installed at ${folderName}\n`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const latest = getLatestVersion(folderPath);
|
|
78
|
+
if (!latest) {
|
|
79
|
+
console.log(LOG_COLORS.YELLOW, `Could not fetch latest version for ${folderName}, skipping update check.`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (installed === latest) {
|
|
84
|
+
console.log(LOG_COLORS.GREEN, `${folderName}: ${VTASKS_PKG}@${installed} is up to date`);
|
|
85
|
+
} else {
|
|
86
|
+
console.log(LOG_COLORS.BLUE, `Updating ${VTASKS_PKG} at ./${folderName} (${installed} → ${latest})...`);
|
|
87
|
+
execSync(`npm install ${VTASKS_PKG}@latest`, { cwd: folderPath, stdio: "inherit" });
|
|
88
|
+
console.log(LOG_COLORS.GREEN, `Updated at ${folderName}\n`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function patchDevScript(folderPath, folderName) {
|
|
93
|
+
const packagePath = path.join(folderPath, PKJSON);
|
|
94
|
+
if (!fs.existsSync(packagePath)) {
|
|
95
|
+
console.log(LOG_COLORS.YELLOW, `${folderName}: package.json not found, skipping script patch`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const packageContent = fs.readFileSync(packagePath, "utf-8");
|
|
100
|
+
const devCmd = getCmdContent(packageContent, "dev");
|
|
101
|
+
|
|
102
|
+
if (!devCmd) {
|
|
103
|
+
console.log(LOG_COLORS.YELLOW, `${folderName}: "dev" script not found`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (devCmd.includes(VTASKS_SCRIPT)) {
|
|
108
|
+
console.log(LOG_COLORS.GREEN, `${folderName}: "dev" script already configured`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const newCmd = `${VTASKS_SCRIPT} && ${devCmd}`;
|
|
113
|
+
const newContent = packageContent.replace(devCmd, newCmd);
|
|
114
|
+
fs.writeFileSync(packagePath, newContent);
|
|
115
|
+
console.log(LOG_COLORS.GREEN, `${folderName}: "dev" script patched`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const main = async () => {
|
|
119
|
+
console.log(LOG_COLORS.BLUE, "Setting up vtasks-automate-cli...\n");
|
|
120
|
+
|
|
121
|
+
const existingFolders = FOLDERS.filter((folder) => {
|
|
122
|
+
const fullPath = path.join(process.cwd(), folder);
|
|
123
|
+
return fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (existingFolders.length === 0) {
|
|
127
|
+
console.log(LOG_COLORS.YELLOW, "No /client or /server folders found. Nothing to do.");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const folder of existingFolders) {
|
|
132
|
+
const folderPath = path.join(process.cwd(), folder);
|
|
133
|
+
try {
|
|
134
|
+
installOrUpdate(folderPath, folder);
|
|
135
|
+
patchDevScript(folderPath, folder);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(LOG_COLORS.RED, `Error processing ${folder}:`, error.message);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
updateHooks(path.join(process.cwd(), ".githooks"));
|
|
142
|
+
|
|
143
|
+
console.log(LOG_COLORS.GREEN, "\nSet up completed ✓");
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
main();
|
package/lib/hooks.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const HOOKS_SOURCE_DIR = path.join(__dirname, "..", "hooks");
|
|
7
|
+
|
|
8
|
+
const HOOK_FILES = [
|
|
9
|
+
"post-checkout",
|
|
10
|
+
"commit-msg",
|
|
11
|
+
"post-commit",
|
|
12
|
+
"pre-commit",
|
|
13
|
+
"pre-push",
|
|
14
|
+
"validate-config-dist.js",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const LOG_COLORS = {
|
|
18
|
+
RED: "\x1b[31m",
|
|
19
|
+
GREEN: "\x1b[32m",
|
|
20
|
+
YELLOW: "\x1b[33m",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function updateHookFile(githooksDir, fileName) {
|
|
24
|
+
const sourcePath = path.join(HOOKS_SOURCE_DIR, fileName);
|
|
25
|
+
const targetPath = path.join(githooksDir, fileName);
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(sourcePath)) {
|
|
28
|
+
console.log(LOG_COLORS.YELLOW, `Hook source not found: ${sourcePath}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const newContent = fs.readFileSync(sourcePath, "utf-8");
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(githooksDir)) {
|
|
35
|
+
fs.mkdirSync(githooksDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const existingContent = fs.existsSync(targetPath)
|
|
39
|
+
? fs.readFileSync(targetPath, "utf-8")
|
|
40
|
+
: null;
|
|
41
|
+
|
|
42
|
+
if (existingContent === newContent) {
|
|
43
|
+
console.log(LOG_COLORS.GREEN, `Git hook "${fileName}" already up to date`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(targetPath, newContent);
|
|
48
|
+
console.log(LOG_COLORS.GREEN, `Git hook "${fileName}" written`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function updateHooks(githooksDir) {
|
|
52
|
+
for (const fileName of HOOK_FILES) {
|
|
53
|
+
updateHookFile(githooksDir, fileName);
|
|
54
|
+
}
|
|
55
|
+
}
|