e-pick 2.0.0 → 3.0.0
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/CLAUDE.md +339 -0
- package/README.md +109 -0
- package/package.json +39 -10
- package/public/css/styles.css +994 -0
- package/public/index.html +216 -34
- package/public/js/api-facade.js +113 -0
- package/public/js/app.js +285 -0
- package/public/js/cherry-pick-builder.js +165 -0
- package/public/js/commit-validator.js +183 -0
- package/public/js/file-parser.js +30 -0
- package/public/js/filter-strategy.js +225 -0
- package/public/js/observable.js +113 -0
- package/public/js/parsers/base-parser.js +92 -0
- package/public/js/parsers/csv-parser.js +88 -0
- package/public/js/parsers/excel-parser.js +142 -0
- package/public/js/parsers/parser-factory.js +69 -0
- package/public/js/stepper-states.js +319 -0
- package/public/js/ui-manager.js +668 -0
- package/src/cli.js +289 -0
- package/src/commands/cherry-pick.command.js +79 -0
- package/src/config/app.config.js +115 -0
- package/src/config/repo-manager.js +131 -0
- package/src/controllers/commit.controller.js +102 -0
- package/src/middleware/error.middleware.js +33 -0
- package/src/middleware/validation.middleware.js +61 -0
- package/src/server.js +121 -0
- package/src/services/git.service.js +277 -0
- package/src/services/validation.service.js +102 -0
- package/src/utils/error-handler.js +80 -0
- package/src/validators/commit.validator.js +160 -0
- package/cli.js +0 -111
- package/lib/pick-commit.js +0 -165
- package/public/script.js +0 -263
- package/public/styles.css +0 -179
- package/server.js +0 -154
package/public/styles.css
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/* General Styles */
|
|
2
|
-
body {
|
|
3
|
-
font-family: Arial, sans-serif;
|
|
4
|
-
background-color: #f4f4f9;
|
|
5
|
-
margin: 0;
|
|
6
|
-
padding: 0;
|
|
7
|
-
display: flex;
|
|
8
|
-
justify-content: center;
|
|
9
|
-
align-items: center;
|
|
10
|
-
height: 100vh;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.container {
|
|
14
|
-
background-color: #fff;
|
|
15
|
-
padding: 20px;
|
|
16
|
-
border-radius: 8px;
|
|
17
|
-
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
18
|
-
width: 90%;
|
|
19
|
-
max-width: 600px;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
h1, h2 {
|
|
23
|
-
color: #333;
|
|
24
|
-
text-align: center;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
input[type="file"] {
|
|
28
|
-
display: block;
|
|
29
|
-
margin: 20px auto;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
button {
|
|
33
|
-
display: block;
|
|
34
|
-
width: 100%;
|
|
35
|
-
padding: 10px;
|
|
36
|
-
margin: 10px 0;
|
|
37
|
-
border: none;
|
|
38
|
-
border-radius: 4px;
|
|
39
|
-
background-color: #007bff;
|
|
40
|
-
color: white;
|
|
41
|
-
font-size: 16px;
|
|
42
|
-
cursor: pointer;
|
|
43
|
-
transition: background-color 0.3s ease;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
button:hover {
|
|
47
|
-
background-color: #0056b3;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
button:disabled {
|
|
51
|
-
background-color: #cccccc; /* Light gray background */
|
|
52
|
-
cursor: not-allowed; /* Change cursor to not-allowed */
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
input[type="text"], select {
|
|
56
|
-
width: 100%;
|
|
57
|
-
padding: 10px;
|
|
58
|
-
margin: 10px 0;
|
|
59
|
-
border: 1px solid #ccc;
|
|
60
|
-
border-radius: 4px;
|
|
61
|
-
box-sizing: border-box;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
input[type="text"]:focus, select:focus {
|
|
65
|
-
border-color: #007bff;
|
|
66
|
-
outline: none;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
ul {
|
|
70
|
-
list-style-type: none;
|
|
71
|
-
padding: 0;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
ul li {
|
|
75
|
-
background-color: #f9f9f9;
|
|
76
|
-
margin: 5px 0;
|
|
77
|
-
padding: 10px;
|
|
78
|
-
border: 1px solid #ddd;
|
|
79
|
-
border-radius: 4px;
|
|
80
|
-
display: flex;
|
|
81
|
-
justify-content: space-between;
|
|
82
|
-
align-items: center;
|
|
83
|
-
position: relative;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
ul li button {
|
|
87
|
-
background-color: #dc3545;
|
|
88
|
-
color: white;
|
|
89
|
-
border: none;
|
|
90
|
-
border-radius: 4px;
|
|
91
|
-
padding: 5px 10px;
|
|
92
|
-
cursor: pointer;
|
|
93
|
-
transition: background-color 0.3s ease;
|
|
94
|
-
position: absolute;
|
|
95
|
-
right: 10px;
|
|
96
|
-
width: 30px;
|
|
97
|
-
text-align: center;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
ul li button:hover {
|
|
101
|
-
background-color: #c82333;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
table {
|
|
105
|
-
width: 100%;
|
|
106
|
-
border-collapse: collapse;
|
|
107
|
-
margin: 20px 0;
|
|
108
|
-
max-height: 400px;
|
|
109
|
-
overflow-y: auto;
|
|
110
|
-
display: block;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
table, th, td {
|
|
114
|
-
border: 1px solid #ddd;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
th, td {
|
|
118
|
-
padding: 10px;
|
|
119
|
-
text-align: left;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
th {
|
|
123
|
-
background-color: #f2f2f2;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
#loading {
|
|
127
|
-
text-align: center;
|
|
128
|
-
font-size: 18px;
|
|
129
|
-
color: #007bff;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
/* Modal styles */
|
|
135
|
-
.modal {
|
|
136
|
-
display: none;
|
|
137
|
-
position: fixed;
|
|
138
|
-
z-index: 1;
|
|
139
|
-
left: 0;
|
|
140
|
-
top: 0;
|
|
141
|
-
width: 100%;
|
|
142
|
-
height: 100%;
|
|
143
|
-
overflow: auto;
|
|
144
|
-
background-color: rgb(0,0,0);
|
|
145
|
-
background-color: rgba(0,0,0,0.4);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.modal-content {
|
|
149
|
-
background-color: #fefefe;
|
|
150
|
-
margin: 15% auto;
|
|
151
|
-
padding: 20px;
|
|
152
|
-
border: 1px solid #888;
|
|
153
|
-
width: 80%;
|
|
154
|
-
max-width: 500px;
|
|
155
|
-
text-align: center;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.close-button {
|
|
159
|
-
color: #aaa;
|
|
160
|
-
float: right;
|
|
161
|
-
font-size: 28px;
|
|
162
|
-
font-weight: bold;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.close-button:hover,
|
|
166
|
-
.close-button:focus {
|
|
167
|
-
color: black;
|
|
168
|
-
text-decoration: none;
|
|
169
|
-
cursor: pointer;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
#copyButton {
|
|
173
|
-
margin-top: 20px;
|
|
174
|
-
background-color: #28a745;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
#copyButton:hover {
|
|
178
|
-
background-color: #218838;
|
|
179
|
-
}
|
package/server.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import net from 'net';
|
|
5
|
-
import Joi from 'joi';
|
|
6
|
-
import { execSync } from 'child_process';
|
|
7
|
-
|
|
8
|
-
const app = express();
|
|
9
|
-
|
|
10
|
-
// Resolve the path to the "public" directory
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
const publicDirectoryPath = path.resolve(__dirname, 'public');
|
|
14
|
-
|
|
15
|
-
// Serve static files from the "public" directory
|
|
16
|
-
app.use(express.static(publicDirectoryPath));
|
|
17
|
-
|
|
18
|
-
// Middleware to parse JSON payloads
|
|
19
|
-
app.use(express.json());
|
|
20
|
-
|
|
21
|
-
const COMMIT_HASH_REGEX = /^[0-9a-f]{40}$/;
|
|
22
|
-
const isValidCommit = (commit_id) => COMMIT_HASH_REGEX.test(commit_id);
|
|
23
|
-
|
|
24
|
-
function isExistCommit(commitId, repoPath) {
|
|
25
|
-
try {
|
|
26
|
-
if (!isValidCommit(commitId)) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
// Change the current working directory to the repository path
|
|
30
|
-
const originalCwd = process.cwd();
|
|
31
|
-
process.chdir(repoPath);
|
|
32
|
-
|
|
33
|
-
// Execute the git command to check the commit type
|
|
34
|
-
const result = execSync(`git cat-file -t ${commitId}`, { stdio: 'pipe' }).toString().trim();
|
|
35
|
-
|
|
36
|
-
// Change back to the original working directory
|
|
37
|
-
process.chdir(originalCwd);
|
|
38
|
-
|
|
39
|
-
// If the command's output starts with "commit", the ID is valid
|
|
40
|
-
return result === 'commit';
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error(`Error checking commit ID: ${error.message}`);
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const uniqueArray = (array) => [...new Set(array)];
|
|
48
|
-
|
|
49
|
-
function getRepoName(data) {
|
|
50
|
-
const allRepos = uniqueArray(data.map((row) => row.repo).filter(Boolean));
|
|
51
|
-
|
|
52
|
-
return allRepos.find((name) => {
|
|
53
|
-
const commitId = data.find((row) => row.repo === name && row?.commit_id)?.commit_id;
|
|
54
|
-
return isExistCommit(commitId, process.cwd());
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Validation schema using Joi
|
|
59
|
-
const schema = Joi.array().items(
|
|
60
|
-
Joi.object({
|
|
61
|
-
repo: Joi.string().required(),
|
|
62
|
-
commit_id: Joi.string()
|
|
63
|
-
.pattern(/^[0-9a-f]{40}$/)
|
|
64
|
-
.required(),
|
|
65
|
-
})
|
|
66
|
-
).min(1);
|
|
67
|
-
|
|
68
|
-
const orderCommitIds = (commits) => {
|
|
69
|
-
// Run git show command for each commit
|
|
70
|
-
const command = `git show -s --format="%ci %H" ${commits.join(' ')}`;
|
|
71
|
-
|
|
72
|
-
let output;
|
|
73
|
-
try {
|
|
74
|
-
output = execSync(command, { encoding: 'utf8' }).trim();
|
|
75
|
-
} catch (err) {
|
|
76
|
-
return { error: err.message };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Sort the output data by commit date and hash
|
|
80
|
-
const outputData = output
|
|
81
|
-
.split('\n')
|
|
82
|
-
.sort()
|
|
83
|
-
.map((line) => line.split(' '));
|
|
84
|
-
|
|
85
|
-
// Extract sorted commit hashes
|
|
86
|
-
const sortedCommits = outputData.map((line) => line[3]);
|
|
87
|
-
|
|
88
|
-
// Generate cherry-pick command
|
|
89
|
-
const pickCommand = `git cherry-pick ${sortedCommits.join(' ')}`;
|
|
90
|
-
|
|
91
|
-
return { data: pickCommand };
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Handle file upload and process Excel file
|
|
95
|
-
app.post('/api/submit', async (req, res) => {
|
|
96
|
-
const { error, value } = schema.validate(req.body);
|
|
97
|
-
if (error) {
|
|
98
|
-
return res.status(400).send({ error: `Invalid data format: ${error.details[0].message}` });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const repoName = getRepoName(value);
|
|
102
|
-
|
|
103
|
-
if (!repoName) {
|
|
104
|
-
return res.status(404).send({ error: 'No valid repository found.' });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const allMatchRepoCommitIds = value.filter((row) => row.repo === repoName).map((row) => row.commit_id);
|
|
108
|
-
const orderData = orderCommitIds(allMatchRepoCommitIds);
|
|
109
|
-
|
|
110
|
-
if (orderData.error) {
|
|
111
|
-
return res.status(500).send({ error: `Failed to execute command: ${orderData.error}` });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
res.status(200).json({ repo: repoName, command: orderData.data });
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Check port availability
|
|
118
|
-
const checkPortAvailability = (port, callback) => {
|
|
119
|
-
const server = net.createServer();
|
|
120
|
-
server.once('error', (err) => {
|
|
121
|
-
if (err.code === 'EADDRINUSE') {
|
|
122
|
-
callback(false);
|
|
123
|
-
} else {
|
|
124
|
-
callback(err);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
server.once('listening', () => {
|
|
128
|
-
server.close();
|
|
129
|
-
callback(true);
|
|
130
|
-
});
|
|
131
|
-
server.listen(port);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Flag to track if the server has already been started
|
|
135
|
-
let serverStarted = false;
|
|
136
|
-
|
|
137
|
-
// Start the server
|
|
138
|
-
export const startServer = (port) => {
|
|
139
|
-
if (serverStarted) {
|
|
140
|
-
console.log('Server is already running.');
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
checkPortAvailability(port, (isAvailable) => {
|
|
145
|
-
if (isAvailable) {
|
|
146
|
-
serverStarted = true;
|
|
147
|
-
app.listen(port, () => {
|
|
148
|
-
console.log(`Server is running on http://localhost:${port}`);
|
|
149
|
-
});
|
|
150
|
-
} else {
|
|
151
|
-
console.error(`Port ${port} is already in use.`);
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
};
|