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 CHANGED
@@ -1,144 +1,168 @@
1
- // const { exec } = require("child_process");
2
- // const config = require("./config");
3
-
4
- import { exec } from "child_process";
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
- class GitService {
17
- static async getCurrentBranch() {
18
- return new Promise((resolve, reject) => {
19
- exec("git branch --show-current", (error, stdout, stderr) => {
20
- if (error) {
21
- console.log("Error: ", error);
22
- reject("Error gettig current branch: Error executing git cmd");
23
- return;
24
- }
25
- const branch = stdout.trim();
26
- resolve(branch);
27
- });
28
- });
29
- }
30
-
31
- static async getCurrentUser() {
32
- return new Promise((resolve, reject) => {
33
- exec("git config --list", (error, stdout, stderr) => {
34
- if (error) {
35
- console.log("Error: ", error);
36
- reject("Error getting user email: Error executing git cmd");
37
- return;
38
- }
39
- const list = stdout.split("\n");
40
- const item = list.find((t) => t.startsWith("user.email"));
41
- if (!item) {
42
- reject("Error getting user email: user.email not found");
43
- return;
44
- }
45
- const split = item.split("=");
46
-
47
- const email = split[1].trim();
48
- resolve(email);
49
- });
50
- });
51
- }
52
-
53
- static async getCurrentProject() {
54
- return new Promise((resolve, reject) => {
55
- exec("git remote -v", (error, stdout, stderr) => {
56
- if (error) {
57
- console.log(
58
- LOG_COLORS.RED,
59
- "Error getting project name: Error executing git cmd"
60
- );
61
- reject("Error getting user email: Error executing git cmd");
62
- return;
63
- }
64
- const list = stdout.split("\n");
65
- const item = list.find((t) => t.startsWith("origin"));
66
- if (!item) {
67
- reject("Error getting project name: origin not found");
68
- reject("Error getting prject name: origin not found");
69
- return;
70
- }
71
- const split = item.split(" ");
72
- //Support http and ssh
73
- const url = split.find(
74
- (t) => t.includes("http") || t.includes("git@github")
75
- );
76
-
77
- const urlSplit = url.split("/");
78
- const gitfile = urlSplit[urlSplit.length - 1];
79
- const projectName = gitfile.replace(".git", "");
80
- resolve(projectName);
81
- });
82
- });
83
- }
84
- }
85
-
86
- async function main() {
87
- console.log(LOG_COLORS.BLUE, "Checking vTasks config:");
88
- const branch = await GitService.getCurrentBranch();
89
- try {
90
- if (
91
- branch.trim() == "qa" ||
92
- branch.trim() == "dev" ||
93
- branch.trim() == "master"
94
- ) {
95
- console.log(
96
- LOG_COLORS.YELLOW,
97
- "Non issue branch - Skip checks and movement"
98
- );
99
- return process.exit(0);
100
- }
101
- } catch (error) {
102
- console.log(LOG_COLORS.RED, "Error parsing branch", branch);
103
- }
104
-
105
- const userEmail = await GitService.getCurrentUser();
106
- if (!userEmail) {
107
- console.log(LOG_COLORS.RED, "Git user email not found");
108
- throw new Error("Git user email not found");
109
- }
110
- console.log(LOG_COLORS.GREEN, `User email: ${userEmail}`);
111
- const projectName = await GitService.getCurrentProject();
112
- if (!projectName) {
113
- console.log(LOG_COLORS.RED, "Missing project name configuration");
114
- throw new Error("Missing Project name");
115
- }
116
- console.log(LOG_COLORS.GREEN, `Project: ${projectName}`);
117
- let issueNumber;
118
-
119
- if (branch != "qa" && branch != "dev" && branch != "master") {
120
- issueNumber = Number(branch.split("_")[0]);
121
- console.log(LOG_COLORS.GREEN, `Currently working on issue: ${issueNumber}`);
122
- const response = await fetch(VTASKS_URL, {
123
- method: "POST",
124
- body: JSON.stringify({
125
- projectName,
126
- branch,
127
- userEmail,
128
- issueNumber,
129
- }),
130
- headers: {
131
- "Content-Type": "application/json",
132
- },
133
- });
134
- const parsed = await response.json();
135
- console.log(LOG_COLORS.GREEN, "vTasks status changed successfully!");
136
- } else {
137
- console.log(
138
- LOG_COLORS.YELLOW,
139
- "Warning: you are working in a not issue branch"
140
- );
141
- }
142
- }
143
-
144
- main();
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();
@@ -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
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ git update-index -g
@@ -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 readline from "readline";
3
- import axios from "axios";
4
- import { execSync } from "child_process";
5
-
6
- import fs from "fs";
7
- import path from "path";
8
-
9
- const LOG_COLORS = {
10
- RED: "\x1b[31m",
11
- GREEN: "\x1b[32m",
12
- YELLOW: "\x1b[33m",
13
- BLUE: "\x1b[34m",
14
- };
15
-
16
- const MAIN_FOLDERS = {
17
- REACT: "client",
18
- NEXT: "client-nextjs",
19
- SERVER: "server",
20
- };
21
-
22
- const PKJSON = "package.json";
23
-
24
- function getCmdContent(content = "", cmd = "start") {
25
- const cmdToGet = `"${cmd}":`;
26
- const cmdLength = cmdToGet.length;
27
- let endCmdIndex;
28
- for (let i = cmdLength; i < content.length; i++) {
29
- const str = content.substring(i - cmdLength, i);
30
- if (str == cmdToGet) {
31
- endCmdIndex = i;
32
- let startMarkIndex;
33
- let endMarkIndex;
34
- while ((!startMarkIndex || !endMarkIndex) && i < content.length) {
35
- if (content[i] == `"`) {
36
- if (!startMarkIndex) {
37
- startMarkIndex = i;
38
- } else {
39
- endMarkIndex = i;
40
- return content.substring(startMarkIndex + 1, endMarkIndex);
41
- }
42
- }
43
-
44
- i++;
45
- }
46
- }
47
- }
48
- console.log("Not found");
49
- }
50
-
51
- const main = async () => {
52
- console.log(LOG_COLORS.BLUE, "Generating scripts");
53
-
54
- const folders = ["client", "client-nextjs", "server"];
55
-
56
- folders.forEach((folder) => {
57
- const fullPath = path.join(process.cwd(), folder);
58
- if (fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory()) {
59
- console.log(LOG_COLORS.BLUE, `Installing dependencies at ./${folder}...`);
60
- try {
61
- execSync("npm install vtasks-automate-cli", {
62
- cwd: fullPath,
63
- stdio: "inherit",
64
- });
65
- console.log(LOG_COLORS.GREEN, `Ready at ${folder}\n`);
66
- } catch (error) {
67
- console.error(
68
- LOG_COLORS.RED,
69
- `Error installing dependencies at ${folder}:`,
70
- error.message
71
- );
72
- }
73
- } else {
74
- console.log(LOG_COLORS.YELLOW, `"${folder}" directory not founded.\n`);
75
- }
76
- });
77
-
78
- const githooksFolder = path.join(process.cwd(), ".githooks");
79
- const postCommitFile = path.join(githooksFolder, "post-checkout");
80
-
81
- //Replace start cmd
82
- const reactPath = path.join(process.cwd(), MAIN_FOLDERS.REACT);
83
- const nextPath = path.join(process.cwd(), MAIN_FOLDERS.NEXT);
84
- const serverPath = path.join(process.cwd(), MAIN_FOLDERS.SERVER);
85
-
86
- const postCommitContent = `#!/bin/bash
87
-
88
- echo -e "\e[34mSwitching current branch\e[0m"
89
-
90
- folders=("client" "client-nextjs" "server")
91
- executed=false
92
-
93
- for folder in "\${folders[@]}"; do
94
- script_path="./$folder/node_modules/vtasks-automate-cli/content/index.js"
95
- if [ -f "$script_path" ]; then
96
- echo -e "\e[32mEjecutando script en $script_path\e[0m"
97
- node "$script_path"
98
- executed=true
99
- break # Salimos del bucle después de ejecutar una vez
100
- fi
101
- done
102
-
103
- if [ "$executed" = false ]; then
104
- echo -e "\e[33mNo se encontró el script vTasks/index.js en ninguna carpeta\e[0m"
105
- fi
106
- `;
107
-
108
- console.log(LOG_COLORS.GREEN, "Post-checkout hook added");
109
- fs.writeFileSync(postCommitFile, postCommitContent);
110
-
111
- if (fs.existsSync(reactPath)) {
112
- const packagePath = path.join(reactPath, PKJSON);
113
- let packageContent = fs.readFileSync(packagePath, "utf-8");
114
- const oldCmd = getCmdContent(packageContent, "start");
115
- if (oldCmd) {
116
- const newCmd = `node node_modules/vtasks-automate-cli/content/index.js && ${oldCmd}`;
117
- const newContent = packageContent.replace(oldCmd, newCmd);
118
- fs.writeFileSync(packagePath, newContent);
119
- console.log(LOG_COLORS.GREEN, "React app start script modified");
120
- } else {
121
- console.log(LOG_COLORS.YELLOW, "React app start script not found");
122
- }
123
- } else {
124
- console.log(LOG_COLORS.YELLOW, "React app not found");
125
- }
126
- if (fs.existsSync(nextPath)) {
127
- const packagePath = path.join(nextPath, PKJSON);
128
- let packageContent = fs.readFileSync(packagePath, "utf-8");
129
- const oldCmd = getCmdContent(packageContent, "dev");
130
- if (oldCmd) {
131
- const newCmd = `node node_modules/vtasks-automate-cli/content/index.js && ${oldCmd}`;
132
- const newContent = packageContent.replace(oldCmd, newCmd);
133
- fs.writeFileSync(packagePath, newContent);
134
- console.log(LOG_COLORS.GREEN, "Next app dev script modified");
135
- } else {
136
- console.log(LOG_COLORS.YELLOW, "Next app dev script not found");
137
- }
138
- } else {
139
- console.log(LOG_COLORS.YELLOW, "Next app not found");
140
- }
141
- if (fs.existsSync(serverPath)) {
142
- const packagePath = path.join(serverPath, PKJSON);
143
- let packageContent = fs.readFileSync(packagePath, "utf-8");
144
- const oldCmd = getCmdContent(packageContent, "start");
145
- if (oldCmd) {
146
- const newCmd = `node node_modules/vtasks-automate-cli/content/index.js && ${oldCmd}`;
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
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { updateHooks } from "./hooks.js";
3
+
4
+ const githooksDir = process.argv[2];
5
+ if (!githooksDir) {
6
+ console.error("Usage: update-hooks.js <githooks-dir>");
7
+ process.exit(1);
8
+ }
9
+
10
+ updateHooks(githooksDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vtasks-automate-cli",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "Automate your vTasks workflow with ease — move tasks between states without opening the app",
5
5
  "bin": {
6
6
  "vtasks-automate": "init.js"