e-pick 1.0.0 → 2.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/cli.js CHANGED
@@ -2,7 +2,9 @@
2
2
  import meow from 'meow';
3
3
  import fs from 'fs';
4
4
  import pickCommit from './lib/pick-commit.js';
5
+ import { startServer } from './server.js';
5
6
 
7
+ // Define CLI options and usage
6
8
  const cli = meow(`
7
9
  Usage
8
10
  $ epick [options] <file_path>
@@ -16,6 +18,8 @@ Options
16
18
  --execute, -x Execute the cherry-pick command. Without this flag, the command will only simulate execution.
17
19
  --allow-empty-commit, -a Allows empty commits. Default is false.
18
20
  --verbose, -v Print detailed logs.
21
+ --serve, -s Start the server to serve the pick code UI.
22
+ --port, -p Port to run the server on. Default is 3000.
19
23
 
20
24
  Examples
21
25
  $ epick --commit-index 3 --repo-index 2 --repo-name "your-repo" --execute ./commits.csv
@@ -55,27 +59,53 @@ Examples
55
59
  shortFlag: 'v',
56
60
  default: false
57
61
  },
62
+ serve: {
63
+ type: 'boolean',
64
+ shortFlag: 's',
65
+ default: false,
66
+ },
67
+ port: {
68
+ type: 'number',
69
+ shortFlag: 'p',
70
+ default: 3000,
71
+ },
58
72
  },
59
73
  allowUnknownFlags: false
60
74
  });
61
75
 
62
- if (cli.input.length === 0) {
63
- console.error("Error: No file path provided. Please specify the path to the CSV file.");
64
- process.exit(1);
65
- }
76
+ // Start the server if the --serve flag is provided
77
+ if (cli.flags.serve) {
78
+ startServer(cli.flags.port);
79
+ } else {
80
+ // Ensure a file path is provided
81
+ if (cli.input.length === 0) {
82
+ console.error("Error: No file path provided. Please specify the path to the CSV file.");
83
+ process.exit(1);
84
+ }
66
85
 
67
- const filePath = cli.input[0];
86
+ const filePath = cli.input[0];
68
87
 
69
- if (!fs.existsSync(filePath)) {
70
- console.error(`Error: The file "${filePath}" does not exist.`);
71
- process.exit(1);
72
- }
88
+ // Check if the file exists
89
+ if (!fs.existsSync(filePath)) {
90
+ console.error(`Error: The file "${filePath}" does not exist.`);
91
+ process.exit(1);
92
+ }
73
93
 
74
- const options = cli.flags;
94
+ const options = cli.flags;
75
95
 
76
- if (options.verbose) {
77
- console.log("Options:", options);
78
- console.log("Processing file:", filePath);
79
- }
96
+ // Log options and file path if verbose flag is set
97
+ if (options.verbose) {
98
+ console.log("Options:", options);
99
+ console.log("Processing file:", filePath);
100
+ }
80
101
 
81
- await pickCommit(filePath, options);
102
+ // Process the file and execute the pickCommit function
103
+ (async () => {
104
+ try {
105
+ await pickCommit(filePath, options);
106
+ } catch (error) {
107
+ console.error("Error processing the file:", error);
108
+ process.exit(1);
109
+ }
110
+ })();
111
+ }
@@ -8,6 +8,9 @@ const uniqueArray = (array) => [...new Set(array)];
8
8
 
9
9
  function isExistCommit(commitId, repoPath) {
10
10
  try {
11
+ if (!isValidCommit(commitId)) {
12
+ return false;
13
+ }
11
14
  // Change the current working directory to the repository path
12
15
  const originalCwd = process.cwd();
13
16
  process.chdir(repoPath);
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "e-pick",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "",
5
- "scripts": {
6
- },
5
+ "scripts": {},
7
6
  "bin": {
8
7
  "epick": "./cli.js"
9
8
  },
@@ -12,6 +11,8 @@
12
11
  "type": "module",
13
12
  "license": "MIT",
14
13
  "dependencies": {
14
+ "express": "^4.21.0",
15
+ "joi": "^17.13.3",
15
16
  "meow": "^13.2.0"
16
17
  }
17
18
  }
@@ -0,0 +1,44 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Excel File Processor</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <h1>Excel File Processor</h1>
12
+ <input type="file" id="fileInput" accept=".xlsx, .xls">
13
+ <div id="loading" style="display: none;">Loading...</div>
14
+ <div id="searchContainer" style="display: none;">
15
+ <input type="text" id="searchBox" list="sheetSuggestions" placeholder="Search for a sheet...">
16
+ <datalist id="sheetSuggestions"></datalist>
17
+ </div>
18
+ <ul id="selectedSheetList"></ul>
19
+ <div id="tableContainer"></div>
20
+ <div>
21
+ <label for="headerSelector1">Repo Column:</label>
22
+ <select id="headerSelector1" disabled></select>
23
+ </div>
24
+ <div>
25
+ <label for="headerSelector2">Commit ID Column:</label>
26
+ <select id="headerSelector2" disabled></select>
27
+ </div>
28
+ <div id="submitContainer">
29
+ <button id="submitButton">Submit</button>
30
+ </div>
31
+ </div>
32
+ <!-- Modal for displaying success and error messages -->
33
+ <div id="modal" class="modal">
34
+ <div class="modal-content">
35
+ <span class="close-button" id="closeButton">&times;</span>
36
+ <p id="modalMessage"></p>
37
+ <button id="copyButton" style="display: none;">Copy Command</button>
38
+ </div>
39
+ </div>
40
+ <!-- Include the xlsx library -->
41
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.9/xlsx.full.min.js"></script>
42
+ <script src="script.js"></script>
43
+ </body>
44
+ </html>
@@ -0,0 +1,263 @@
1
+ const COMMIT_HASH_REGEX = /^[0-9a-f]{40}$/;
2
+ const isValidCommit = (commit_id) => COMMIT_HASH_REGEX.test(commit_id);
3
+
4
+ document.addEventListener('DOMContentLoaded', () => {
5
+ const fileInput = document.getElementById('fileInput');
6
+ const loading = document.getElementById('loading');
7
+ const searchContainer = document.getElementById('searchContainer');
8
+ const searchBox = document.getElementById('searchBox');
9
+ const sheetSuggestions = document.getElementById('sheetSuggestions');
10
+ const selectedSheetList = document.getElementById('selectedSheetList');
11
+ const tableContainer = document.getElementById('tableContainer');
12
+ const submitContainer = document.getElementById('submitContainer');
13
+ const headerSelector1 = document.getElementById('headerSelector1');
14
+ const headerSelector2 = document.getElementById('headerSelector2');
15
+ let workbook;
16
+
17
+ fileInput.addEventListener('change', () => {
18
+ console.log('File selected:', fileInput.files[0].name);
19
+ if (fileInput.files.length) {
20
+ uploadFile(fileInput.files[0]);
21
+ }
22
+ });
23
+
24
+ function uploadFile(file) {
25
+ console.log('Uploading file:', file.name);
26
+ const reader = new FileReader();
27
+
28
+ reader.onload = (e) => {
29
+ const data = new Uint8Array(e.target.result);
30
+ workbook = XLSX.read(data, { type: 'array' });
31
+ console.log('Workbook loaded:', workbook);
32
+
33
+ // Hide loading indicator
34
+ loading.style.display = 'none';
35
+
36
+ // Show search box
37
+ searchContainer.style.display = 'block';
38
+
39
+ // Populate datalist with sheet names and valid row counts
40
+ sheetSuggestions.innerHTML = '';
41
+ workbook.SheetNames.forEach(sheet => {
42
+ const sheetData = XLSX.utils.sheet_to_json(workbook.Sheets[sheet]);
43
+ const validRowCount = sheetData.filter(row => {
44
+ const validCellCount = Object.values(row).filter(value => value !== undefined && value !== '').length;
45
+ return validCellCount >= 3;
46
+ }).length;
47
+
48
+ const option = document.createElement('option');
49
+ option.value = `${sheet} (Valid Rows: ${validRowCount})`;
50
+ sheetSuggestions.appendChild(option);
51
+ console.log(`Sheet: ${sheet}, Valid Rows: ${validRowCount}`);
52
+ });
53
+
54
+ // Handle sheet selection
55
+ searchBox.addEventListener('change', () => {
56
+ const selectedSheet = searchBox.value.split(' (')[0]; // Extract sheet name
57
+ if (selectedSheet && !Array.from(selectedSheetList.children).some(item => item.textContent.includes(selectedSheet))) {
58
+ console.log('Sheet selected:', selectedSheet);
59
+ const listItem = document.createElement('li');
60
+ listItem.textContent = selectedSheet;
61
+
62
+ // Create remove button
63
+ const removeButton = document.createElement('button');
64
+ removeButton.textContent = 'x';
65
+ removeButton.style.marginLeft = '10px';
66
+ removeButton.addEventListener('click', () => {
67
+ console.log('Removing sheet:', selectedSheet);
68
+ selectedSheetList.removeChild(listItem);
69
+ updateTable();
70
+ });
71
+
72
+ listItem.appendChild(removeButton);
73
+ selectedSheetList.appendChild(listItem);
74
+ searchBox.value = ''; // Clear the search box
75
+ updateTable();
76
+ }
77
+ });
78
+ };
79
+
80
+ // Show loading indicator
81
+ loading.style.display = 'block';
82
+ searchContainer.style.display = 'none';
83
+ selectedSheetList.innerHTML = ''; // Clear selected sheets
84
+ tableContainer.innerHTML = ''; // Clear previous table
85
+ submitContainer.innerHTML = ''; // Clear previous submit button
86
+
87
+ reader.readAsArrayBuffer(file);
88
+ }
89
+
90
+ // Update the table with merged data from selected sheets
91
+ function updateTable() {
92
+ const selectedSheets = Array.from(selectedSheetList.children).map(item => item.textContent.replace('x', '').trim());
93
+ let mergedData = [];
94
+ let validRowCounts = {};
95
+
96
+ selectedSheets.forEach(sheetName => {
97
+ console.log(`Processing sheet: ${sheetName}`);
98
+ const sheet = workbook.Sheets[sheetName];
99
+ let sheetData = XLSX.utils.sheet_to_json(sheet);
100
+
101
+ // Filter out rows with fewer than 3 valid (non-empty) cells
102
+ sheetData = sheetData.filter(row => {
103
+ const validCellCount = Object.values(row).filter(value => value !== undefined && value !== '').length;
104
+ const isValid = validCellCount >= 3;
105
+ console.log(`Row: ${JSON.stringify(row)}, Valid Cells: ${validCellCount}, Valid: ${isValid}`);
106
+ return isValid;
107
+ });
108
+
109
+ validRowCounts[sheetName] = sheetData.length;
110
+ console.log(`Valid rows in sheet ${sheetName}: ${validRowCounts[sheetName]}`);
111
+ mergedData = mergedData.concat(sheetData);
112
+ });
113
+
114
+ console.log('Merged Data:', mergedData);
115
+ displayTable(mergedData, validRowCounts);
116
+ }
117
+
118
+ // Display merged data in a table
119
+ function displayTable(data, validRowCounts) {
120
+ tableContainer.innerHTML = ''; // Clear previous table
121
+ submitContainer.innerHTML = ''; // Clear previous submit button
122
+
123
+ if (data.length === 0) {
124
+ tableContainer.textContent = 'No valid data to display.';
125
+ return;
126
+ }
127
+
128
+ const table = document.createElement('table');
129
+ const thead = document.createElement('thead');
130
+ const tbody = document.createElement('tbody');
131
+
132
+ // Create table headers
133
+ const headers = Object.keys(data[0]);
134
+ const headerRow = document.createElement('tr');
135
+ headers.forEach(header => {
136
+ const th = document.createElement('th');
137
+ th.textContent = header;
138
+ headerRow.appendChild(th);
139
+ });
140
+ thead.appendChild(headerRow);
141
+
142
+ // Populate header selectors
143
+ headerSelector1.innerHTML = '';
144
+ headerSelector2.innerHTML = '';
145
+ headers.forEach(header => {
146
+ const option1 = document.createElement('option');
147
+ option1.value = header;
148
+ option1.textContent = header;
149
+ headerSelector1.appendChild(option1);
150
+
151
+ const option2 = document.createElement('option');
152
+ option2.value = header;
153
+ option2.textContent = header;
154
+ headerSelector2.appendChild(option2);
155
+ });
156
+
157
+ // Set default values for header selectors
158
+ const defaultRepoHeader = headers.find(header => header.toLowerCase().includes('repo'));
159
+ const defaultCommitHeader = headers.find(header => header.toLowerCase().includes('commit'));
160
+
161
+ if (defaultRepoHeader) {
162
+ headerSelector1.value = defaultRepoHeader;
163
+ }
164
+
165
+ if (defaultCommitHeader) {
166
+ headerSelector2.value = defaultCommitHeader;
167
+ }
168
+
169
+ // Enable header selectors
170
+ headerSelector1.disabled = false;
171
+ headerSelector2.disabled = false;
172
+
173
+ // Create table rows
174
+ data.forEach(row => {
175
+ const tr = document.createElement('tr');
176
+ headers.forEach(header => {
177
+ const td = document.createElement('td');
178
+ td.textContent = row[header] || '';
179
+ tr.appendChild(td);
180
+ });
181
+ tbody.appendChild(tr);
182
+ });
183
+
184
+ table.appendChild(thead);
185
+ table.appendChild(tbody);
186
+ tableContainer.appendChild(table);
187
+
188
+ // Add title with valid row counts
189
+ const title = document.createElement('h2');
190
+ title.textContent = 'Merged Data (Valid Rows: ' + Object.entries(validRowCounts).map(([sheet, count]) => `${sheet}: ${count}`).join(', ') + ')';
191
+ tableContainer.insertBefore(title, table);
192
+
193
+ // Create and display submit button if there is valid data
194
+ if (data.length > 0) {
195
+ const submitButton = document.createElement('button');
196
+ submitButton.textContent = 'Submit';
197
+ submitButton.addEventListener('click', () => {
198
+ console.log('Submit button clicked');
199
+ submitData(data);
200
+ });
201
+ submitContainer.appendChild(submitButton);
202
+ }
203
+ }
204
+
205
+ // Submit data to API
206
+ function submitData(data) {
207
+ const repoColumn = headerSelector1.value;
208
+ const commitColumn = headerSelector2.value;
209
+
210
+ const payload = data.map(row => ({
211
+ repo: row[repoColumn],
212
+ commit_id: row[commitColumn]
213
+ }));
214
+
215
+ console.log('Payload:', payload);
216
+
217
+ fetch('/api/submit', {
218
+ method: 'POST',
219
+ headers: {
220
+ 'Content-Type': 'application/json'
221
+ },
222
+ body: JSON.stringify(payload)
223
+ })
224
+ .then(response => response.json())
225
+ .then(result => {
226
+ if (result.error) {
227
+ showModal(result.error, false);
228
+ } else {
229
+ console.log('Success:', result);
230
+ showModal(result.command, true);
231
+ }
232
+ })
233
+ .catch(error => {
234
+ console.error('Error:', error);
235
+ showModal(error.message, false);
236
+ });
237
+ }
238
+
239
+ // Show modal with message and copy button
240
+ function showModal(message, isSuccess) {
241
+ const modal = document.getElementById('modal');
242
+ const modalMessage = document.getElementById('modalMessage');
243
+ const copyButton = document.getElementById('copyButton');
244
+
245
+ modalMessage.textContent = message;
246
+ copyButton.style.display = isSuccess ? 'block' : 'none';
247
+
248
+ modal.style.display = 'block';
249
+
250
+ copyButton.onclick = () => {
251
+ navigator.clipboard.writeText(message).then(() => {
252
+ alert('Command copied to clipboard!');
253
+ }).catch(err => {
254
+ console.error('Failed to copy command:', err);
255
+ });
256
+ };
257
+ }
258
+
259
+ // Close modal when clicking the close button
260
+ document.getElementById('closeButton').onclick = () => {
261
+ document.getElementById('modal').style.display = 'none';
262
+ };
263
+ });
@@ -0,0 +1,179 @@
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 ADDED
@@ -0,0 +1,154 @@
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
+ };