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/index.html
CHANGED
|
@@ -3,42 +3,224 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>
|
|
7
|
-
<link rel="stylesheet" href="styles.css">
|
|
6
|
+
<title>E-Pick Tool</title>
|
|
7
|
+
<link rel="stylesheet" href="css/styles.css">
|
|
8
|
+
|
|
9
|
+
<!-- External Libraries -->
|
|
10
|
+
<script src="https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js"></script>
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script>
|
|
8
12
|
</head>
|
|
9
13
|
<body>
|
|
10
14
|
<div class="container">
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
<header>
|
|
16
|
+
<h1>🍒 E-Pick Tool</h1>
|
|
17
|
+
<p class="subtitle">Cherry-pick git commits from Excel/CSV files</p>
|
|
18
|
+
<div class="repo-info" id="repo-info">Loading repository info...</div>
|
|
19
|
+
</header>
|
|
20
|
+
|
|
21
|
+
<main>
|
|
22
|
+
<!-- Progress Stepper -->
|
|
23
|
+
<div class="stepper" id="stepper">
|
|
24
|
+
<div class="stepper-item active">
|
|
25
|
+
<div class="step-circle active" data-step="1">1</div>
|
|
26
|
+
<div class="step-label">Upload File</div>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="stepper-item">
|
|
29
|
+
<div class="step-circle" data-step="2">2</div>
|
|
30
|
+
<div class="step-label">Select Columns</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="stepper-item">
|
|
33
|
+
<div class="step-circle" data-step="3">3</div>
|
|
34
|
+
<div class="step-label">Validate</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="stepper-item">
|
|
37
|
+
<div class="step-circle" data-step="4">4</div>
|
|
38
|
+
<div class="step-label">Review & Get Command</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="stepper-item">
|
|
41
|
+
<div class="step-circle" data-step="5">5</div>
|
|
42
|
+
<div class="step-label">View Changed Files</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Step 1: File Upload -->
|
|
47
|
+
<section class="card step-card" data-step-content="1">
|
|
48
|
+
<h2>Step 1: Upload File</h2>
|
|
49
|
+
<p>Select an Excel (.xlsx, .xls) or CSV file containing commit information</p>
|
|
50
|
+
<div class="file-input-container">
|
|
51
|
+
<input type="file" id="file-input" accept=".xlsx,.xls,.csv,.tsv" />
|
|
52
|
+
<label for="file-input" class="file-label">
|
|
53
|
+
Choose File or Drag & Drop
|
|
54
|
+
</label>
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
|
|
58
|
+
<!-- Step 1.5: Sheet Selection (for Excel files) -->
|
|
59
|
+
<section class="card" id="sheet-selector" data-step-content="1" style="display: none;">
|
|
60
|
+
<h2>Select Sheets</h2>
|
|
61
|
+
<p>This Excel file contains multiple sheets. Select one or more sheets to combine:</p>
|
|
62
|
+
|
|
63
|
+
<div style="margin: 1rem 0;">
|
|
64
|
+
<label for="sheet-search" style="display: block; font-weight: 600; color: var(--subtext1); margin-bottom: 0.5rem;">
|
|
65
|
+
Search Sheets:
|
|
66
|
+
</label>
|
|
67
|
+
<input
|
|
68
|
+
type="text"
|
|
69
|
+
id="sheet-search"
|
|
70
|
+
placeholder="Search sheets..."
|
|
71
|
+
aria-label="Search sheets"
|
|
72
|
+
>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div id="sheet-checkboxes" role="group" aria-label="Sheet selection" tabindex="0">
|
|
76
|
+
<!-- Checkboxes will be inserted here -->
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<button id="load-sheets-btn" class="btn btn-primary" disabled>
|
|
80
|
+
Load Selected Sheets
|
|
81
|
+
</button>
|
|
82
|
+
</section>
|
|
83
|
+
|
|
84
|
+
<!-- Step 2: Column Selection -->
|
|
85
|
+
<section class="card" id="column-selectors" data-step-content="2" style="display: none;">
|
|
86
|
+
<h2>Step 2: Select Columns</h2>
|
|
87
|
+
|
|
88
|
+
<!-- Action button at top -->
|
|
89
|
+
<div class="action-section">
|
|
90
|
+
<button id="validate-btn" class="btn btn-primary" style="font-size: 1.1rem; padding: 1rem 2rem;" disabled>
|
|
91
|
+
✓ Validate Commits
|
|
92
|
+
</button>
|
|
93
|
+
<p class="info-text" style="margin-top: 0.5rem;">Select columns below, then validate</p>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- Column selectors -->
|
|
97
|
+
<div class="selector-group">
|
|
98
|
+
<div class="selector">
|
|
99
|
+
<label for="commit-column">Commit Hash Column:</label>
|
|
100
|
+
<select id="commit-column"></select>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="selector">
|
|
103
|
+
<label for="repo-column">Repository Column (optional):</label>
|
|
104
|
+
<select id="repo-column"></select>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</section>
|
|
108
|
+
|
|
109
|
+
<!-- Data Preview -->
|
|
110
|
+
<section class="card" id="data-preview" data-step-content="2" style="display: none;">
|
|
111
|
+
<h3 style="margin-bottom: 1rem;">Data Preview</h3>
|
|
112
|
+
<!-- Preview content will be inserted here -->
|
|
113
|
+
</section>
|
|
114
|
+
|
|
115
|
+
<!-- Step 3: Validation Progress -->
|
|
116
|
+
<section class="card" id="validation-progress" data-step-content="3" style="display: none;">
|
|
117
|
+
<h2>Step 3: Validating Commits</h2>
|
|
118
|
+
<p class="info-text">Please wait while we validate your commits...</p>
|
|
119
|
+
<div style="text-align: center; padding: 2rem;">
|
|
120
|
+
<div style="font-size: 3rem; color: var(--blue);">⏳</div>
|
|
121
|
+
<p style="margin-top: 1rem; color: var(--subtext1);">Checking commits in repository...</p>
|
|
122
|
+
</div>
|
|
123
|
+
</section>
|
|
124
|
+
|
|
125
|
+
<!-- Step 4: Validation Results & Command -->
|
|
126
|
+
<section class="card" id="validation-results" data-step-content="4" style="display: none;">
|
|
127
|
+
<h2>Step 4: Review Results & Get Command</h2>
|
|
128
|
+
|
|
129
|
+
<!-- Command Output (shown first) -->
|
|
130
|
+
<div id="command-output" style="display: none;">
|
|
131
|
+
<h3 style="margin-bottom: 1rem;">📋 Cherry-Pick Command</h3>
|
|
132
|
+
|
|
133
|
+
<!-- Action buttons -->
|
|
134
|
+
<div class="action-section">
|
|
135
|
+
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
136
|
+
<button id="copy-btn" class="btn btn-primary" style="font-size: 1.1rem; padding: 1rem 2rem;" disabled>
|
|
137
|
+
📋 Copy Command
|
|
138
|
+
</button>
|
|
139
|
+
<button id="view-files-btn" class="btn btn-success" style="font-size: 1.1rem; padding: 1rem 2rem;" disabled>
|
|
140
|
+
📄 View Changed Files
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
<p class="info-text" style="margin-top: 0.5rem;">Copy command to clipboard or click "View Changed Files" to see diff commands for each file</p>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<!-- Command and details -->
|
|
147
|
+
<div id="command-text"></div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<!-- Validation Details (below command) -->
|
|
151
|
+
<div id="validation-details" style="display: none; margin-top: 2rem; padding-top: 2rem; border-top: 2px solid var(--surface0);">
|
|
152
|
+
<h3 style="margin-bottom: 1rem;">📊 Validation Details</h3>
|
|
153
|
+
|
|
154
|
+
<!-- Scrollable details -->
|
|
155
|
+
<div style="max-height: 500px; overflow-y: auto;" tabindex="0" role="region" aria-label="Validation details">
|
|
156
|
+
<div class="results-section">
|
|
157
|
+
<h4>⚠ Invalid Commits</h4>
|
|
158
|
+
<p class="info-text">You can ignore commits that can be safely skipped</p>
|
|
159
|
+
<div id="invalid-commits-list" class="commits-list" tabindex="0" role="region" aria-label="Invalid commits list"></div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="results-section">
|
|
163
|
+
<h4>✓ Valid Commits</h4>
|
|
164
|
+
<div id="valid-commits-filter-stats"></div>
|
|
165
|
+
<div id="valid-commits-list" class="commits-list" tabindex="0" role="region" aria-label="Valid commits list"></div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<!-- Hidden generate button (for backward compatibility) -->
|
|
171
|
+
<button id="generate-btn" style="display: none;"></button>
|
|
172
|
+
</section>
|
|
173
|
+
|
|
174
|
+
<!-- Step 5: Files Changed -->
|
|
175
|
+
<section class="card" id="files-changed" data-step-content="5" style="display: none;">
|
|
176
|
+
<h2>Step 5: View Changed Files</h2>
|
|
177
|
+
<p class="info-text">Files modified by the selected commits with diff commands</p>
|
|
178
|
+
|
|
179
|
+
<div style="margin-bottom: 1.5rem;">
|
|
180
|
+
<label for="compare-branch" class="input-label">
|
|
181
|
+
Compare Branch:
|
|
182
|
+
</label>
|
|
183
|
+
<input
|
|
184
|
+
type="text"
|
|
185
|
+
id="compare-branch"
|
|
186
|
+
value="origin/develop"
|
|
187
|
+
placeholder="origin/develop"
|
|
188
|
+
class="branch-input"
|
|
189
|
+
>
|
|
190
|
+
<span class="info-text" style="margin-left: 1rem; font-size: 0.9rem;">
|
|
191
|
+
ℹ️ Branch to compare against
|
|
192
|
+
</span>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div id="files-list" style="margin-top: 1.5rem;">
|
|
196
|
+
<!-- Files will be inserted here -->
|
|
197
|
+
</div>
|
|
198
|
+
</section>
|
|
199
|
+
|
|
200
|
+
<!-- Loading Spinner -->
|
|
201
|
+
<div id="loading" class="loading" style="display: none;">
|
|
202
|
+
Loading...
|
|
203
|
+
</div>
|
|
204
|
+
</main>
|
|
205
|
+
|
|
206
|
+
<footer>
|
|
207
|
+
<p id="version-info">E-Pick Tool</p>
|
|
208
|
+
</footer>
|
|
31
209
|
</div>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<script src="
|
|
42
|
-
<script src="
|
|
210
|
+
|
|
211
|
+
<!-- Application Scripts -->
|
|
212
|
+
<script src="js/observable.js"></script>
|
|
213
|
+
<script src="js/api-facade.js"></script>
|
|
214
|
+
<script src="js/parsers/base-parser.js"></script>
|
|
215
|
+
<script src="js/parsers/excel-parser.js"></script>
|
|
216
|
+
<script src="js/parsers/csv-parser.js"></script>
|
|
217
|
+
<script src="js/parsers/parser-factory.js"></script>
|
|
218
|
+
<script src="js/file-parser.js"></script>
|
|
219
|
+
<script src="js/cherry-pick-builder.js"></script>
|
|
220
|
+
<script src="js/stepper-states.js"></script>
|
|
221
|
+
<script src="js/filter-strategy.js"></script>
|
|
222
|
+
<script src="js/commit-validator.js"></script>
|
|
223
|
+
<script src="js/ui-manager.js"></script>
|
|
224
|
+
<script src="js/app.js"></script>
|
|
43
225
|
</body>
|
|
44
|
-
</html>
|
|
226
|
+
</html>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Facade (Facade Pattern)
|
|
3
|
+
*
|
|
4
|
+
* Provides a simplified interface for all backend API interactions.
|
|
5
|
+
* Centralizes error handling, request formatting, and response parsing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class APIFacade {
|
|
9
|
+
constructor(baseUrl = '/api') {
|
|
10
|
+
this.baseUrl = baseUrl;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate array of commit hashes
|
|
15
|
+
* @param {string[]} commits - Array of commit hashes
|
|
16
|
+
* @returns {Promise<object>} Validation results
|
|
17
|
+
*/
|
|
18
|
+
async validateCommits(commits) {
|
|
19
|
+
return this._post('/validate', { commits });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate cherry-pick command
|
|
24
|
+
* @param {string[]} commits - Valid commit hashes
|
|
25
|
+
* @param {string[]} ignoredCommits - Commits to ignore
|
|
26
|
+
* @param {object} options - Additional options
|
|
27
|
+
* @returns {Promise<object>} Generated command data
|
|
28
|
+
*/
|
|
29
|
+
async generateCommand(commits, ignoredCommits = [], options = {}) {
|
|
30
|
+
return this._post('/generate-command', {
|
|
31
|
+
commits,
|
|
32
|
+
ignoredCommits,
|
|
33
|
+
options
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if a commit exists in repository
|
|
39
|
+
* @param {string} commitHash - Commit hash to check
|
|
40
|
+
* @returns {Promise<object>} Existence check result
|
|
41
|
+
*/
|
|
42
|
+
async checkCommit(commitHash) {
|
|
43
|
+
return this._get(`/check-commit/${commitHash}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get repository information
|
|
48
|
+
* @returns {Promise<object>} Repository info
|
|
49
|
+
*/
|
|
50
|
+
async getRepoInfo() {
|
|
51
|
+
return this._get('/repo-info');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get application version
|
|
56
|
+
* @returns {Promise<object>} Version info
|
|
57
|
+
*/
|
|
58
|
+
async getVersion() {
|
|
59
|
+
return this._get('/version');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Perform GET request
|
|
64
|
+
* @param {string} endpoint - API endpoint
|
|
65
|
+
* @returns {Promise<object>} Response data
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
async _get(endpoint) {
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`);
|
|
71
|
+
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const error = await response.json();
|
|
74
|
+
throw new Error(error.message || `Request failed: ${response.status}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return await response.json();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error(`API GET ${endpoint} failed: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Perform POST request
|
|
85
|
+
* @param {string} endpoint - API endpoint
|
|
86
|
+
* @param {object} data - Request body data
|
|
87
|
+
* @returns {Promise<object>} Response data
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
async _post(endpoint, data) {
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: {
|
|
95
|
+
'Content-Type': 'application/json'
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify(data)
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
const error = await response.json();
|
|
102
|
+
throw new Error(error.message || `Request failed: ${response.status}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return await response.json();
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw new Error(`API POST ${endpoint} failed: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Export singleton instance
|
|
113
|
+
window.APIFacade = APIFacade;
|
package/public/js/app.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Application
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates all modules and handles main application flow
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class App {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.ui = new UIManager();
|
|
10
|
+
this.validator = new CommitValidator();
|
|
11
|
+
this.parsedData = null;
|
|
12
|
+
this.currentParser = null;
|
|
13
|
+
this.currentFile = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initialize application
|
|
18
|
+
*/
|
|
19
|
+
init() {
|
|
20
|
+
this.ui.init();
|
|
21
|
+
this.setupEventListeners();
|
|
22
|
+
this.ui.loadRepoInfo();
|
|
23
|
+
this.ui.loadVersion();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Setup event listeners
|
|
28
|
+
*/
|
|
29
|
+
setupEventListeners() {
|
|
30
|
+
// File input
|
|
31
|
+
this.ui.elements.fileInput.addEventListener('change', (e) => {
|
|
32
|
+
this.handleFileSelect(e.target.files[0]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Load sheets button
|
|
36
|
+
this.ui.elements.loadSheetsBtn.addEventListener('click', () => {
|
|
37
|
+
this.handleLoadSheets();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Validate button
|
|
41
|
+
this.ui.elements.validateBtn.addEventListener('click', () => {
|
|
42
|
+
this.handleValidate();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Generate command button
|
|
46
|
+
this.ui.elements.generateBtn.addEventListener('click', () => {
|
|
47
|
+
this.handleGenerateCommand();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Copy button
|
|
51
|
+
this.ui.elements.copyBtn.addEventListener('click', () => {
|
|
52
|
+
this.ui.copyToClipboard();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// View files button
|
|
56
|
+
this.ui.elements.viewFilesBtn.addEventListener('click', () => {
|
|
57
|
+
this.ui.displayChangedFiles();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Handle file selection
|
|
63
|
+
* @param {File} file - Selected file
|
|
64
|
+
*/
|
|
65
|
+
async handleFileSelect(file) {
|
|
66
|
+
if (!file) return;
|
|
67
|
+
|
|
68
|
+
this.currentFile = file;
|
|
69
|
+
this.ui.showLoading('Parsing file...');
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const result = await FileParser.parseFile(file);
|
|
73
|
+
|
|
74
|
+
if (!result.success) {
|
|
75
|
+
throw new Error(result.error);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if sheet selection is needed
|
|
79
|
+
if (result.needsSheetSelection) {
|
|
80
|
+
this.currentParser = result.parser;
|
|
81
|
+
this.ui.showSheetSelector(result.sheets);
|
|
82
|
+
this.ui.showToast('Please select a sheet to continue', 'info');
|
|
83
|
+
} else {
|
|
84
|
+
// CSV or single-sheet Excel - parse directly
|
|
85
|
+
this.parsedData = result;
|
|
86
|
+
this.ui.renderDataPreview(result);
|
|
87
|
+
this.ui.showToast(`File parsed successfully! ${result.rows.length} rows found`, 'success');
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
this.ui.showToast(error.message, 'error');
|
|
91
|
+
} finally {
|
|
92
|
+
this.ui.hideLoading();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Handle loading selected sheets
|
|
98
|
+
*/
|
|
99
|
+
async handleLoadSheets() {
|
|
100
|
+
if (!this.currentParser) return;
|
|
101
|
+
|
|
102
|
+
const selectedSheets = this.ui.getSelectedSheets();
|
|
103
|
+
if (selectedSheets.length === 0) {
|
|
104
|
+
this.ui.showToast('Please select at least one sheet', 'error');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.ui.showLoading(`Loading ${selectedSheets.length} sheet(s)...`);
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Parse all selected sheets
|
|
112
|
+
const sheetsData = await Promise.all(
|
|
113
|
+
selectedSheets.map(sheetName => FileParser.parseSheet(this.currentParser, sheetName))
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Check for errors
|
|
117
|
+
const errors = sheetsData.filter(data => !data.success);
|
|
118
|
+
if (errors.length > 0) {
|
|
119
|
+
throw new Error(errors[0].error);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Merge all sheets data
|
|
123
|
+
let mergedData;
|
|
124
|
+
if (sheetsData.length === 1) {
|
|
125
|
+
mergedData = sheetsData[0];
|
|
126
|
+
} else {
|
|
127
|
+
// Combine rows from all sheets
|
|
128
|
+
const allRows = [];
|
|
129
|
+
const firstSheet = sheetsData[0];
|
|
130
|
+
|
|
131
|
+
sheetsData.forEach(sheetData => {
|
|
132
|
+
allRows.push(...sheetData.rows);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
mergedData = {
|
|
136
|
+
success: true,
|
|
137
|
+
columns: firstSheet.columns,
|
|
138
|
+
rows: allRows,
|
|
139
|
+
currentSheet: selectedSheets.join(', ')
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.parsedData = mergedData;
|
|
144
|
+
this.ui.hideSheetSelector();
|
|
145
|
+
this.ui.renderDataPreview(mergedData);
|
|
146
|
+
this.ui.showToast(
|
|
147
|
+
`${selectedSheets.length} sheet(s) loaded! ${mergedData.rows.length} total rows`,
|
|
148
|
+
'success'
|
|
149
|
+
);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
this.ui.showToast(error.message, 'error');
|
|
152
|
+
} finally {
|
|
153
|
+
this.ui.hideLoading();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Handle validate button click
|
|
159
|
+
*/
|
|
160
|
+
async handleValidate() {
|
|
161
|
+
if (!this.parsedData) {
|
|
162
|
+
this.ui.showToast('Please select a file first', 'error');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const commitColumnIndex = parseInt(this.ui.elements.commitColumnSelect.value);
|
|
167
|
+
const repoColumnIndex = parseInt(this.ui.elements.repoColumnSelect.value);
|
|
168
|
+
|
|
169
|
+
// Advance to step 3 (validation in progress)
|
|
170
|
+
this.ui.advanceToStep(3);
|
|
171
|
+
|
|
172
|
+
this.ui.showLoading('Validating commits...');
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Extract commits from data with repo column
|
|
176
|
+
const { commits, repoData } = this.validator.extractCommits(
|
|
177
|
+
this.parsedData.rows,
|
|
178
|
+
commitColumnIndex,
|
|
179
|
+
!isNaN(repoColumnIndex) ? repoColumnIndex : null
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (commits.length === 0) {
|
|
183
|
+
throw new Error('No commits found in selected column');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Filter commits by current repository (validates commits exist)
|
|
187
|
+
const filterResult = await this.validator.filterByRepo(
|
|
188
|
+
commits,
|
|
189
|
+
repoData
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Show filter statistics if any commits were filtered
|
|
193
|
+
if (filterResult.stats.filtered > 0) {
|
|
194
|
+
const message = filterResult.stats.matchedByCommit
|
|
195
|
+
? `Matched repository: ${filterResult.stats.matchedRepo}. Filtered out ${filterResult.stats.filtered} commit(s) from other repositories.`
|
|
196
|
+
: `Filtered out ${filterResult.stats.filtered} commit(s) from other repositories`;
|
|
197
|
+
this.ui.showToast(message, 'info');
|
|
198
|
+
} else if (filterResult.stats.warning) {
|
|
199
|
+
this.ui.showToast(filterResult.stats.warning, 'info');
|
|
200
|
+
} else if (filterResult.stats.matchedByCommit) {
|
|
201
|
+
this.ui.showToast(
|
|
202
|
+
`Repository matched by commit validation: ${filterResult.stats.matchedRepo}`,
|
|
203
|
+
'success'
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Validate filtered commits
|
|
208
|
+
const results = await this.validator.validateCommits(
|
|
209
|
+
filterResult.filtered,
|
|
210
|
+
filterResult.stats
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Render results
|
|
214
|
+
this.ui.renderValidationResults(results, (commit) => {
|
|
215
|
+
this.handleIgnoreToggle(commit);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Build success message
|
|
219
|
+
let message = `Validation complete: ${results.summary.valid} valid, ${results.summary.invalid} invalid`;
|
|
220
|
+
if (filterResult.stats.filtered > 0) {
|
|
221
|
+
message += ` (${filterResult.stats.filtered} filtered)`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.ui.showToast(
|
|
225
|
+
message,
|
|
226
|
+
results.summary.invalid === 0 ? 'success' : 'info'
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Auto-generate command if there are valid commits
|
|
230
|
+
if (results.summary.valid > 0) {
|
|
231
|
+
await this.handleGenerateCommand();
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
this.ui.showToast(error.message, 'error');
|
|
235
|
+
} finally {
|
|
236
|
+
this.ui.hideLoading();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Handle ignore checkbox toggle
|
|
242
|
+
* @param {string} commit - Commit hash
|
|
243
|
+
*/
|
|
244
|
+
handleIgnoreToggle(commit) {
|
|
245
|
+
this.validator.toggleIgnore(commit);
|
|
246
|
+
|
|
247
|
+
const ignoredCount = this.validator.getIgnoredCommits().length;
|
|
248
|
+
if (ignoredCount > 0) {
|
|
249
|
+
this.ui.showToast(`${ignoredCount} commit(s) will be ignored`, 'info');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Handle generate command button click
|
|
255
|
+
*/
|
|
256
|
+
async handleGenerateCommand() {
|
|
257
|
+
if (!this.validator.validationResults) {
|
|
258
|
+
this.ui.showToast('Please validate commits first', 'error');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this.ui.showLoading('Generating command...');
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await this.validator.generateCommand();
|
|
266
|
+
|
|
267
|
+
if (!result.success) {
|
|
268
|
+
throw new Error(result.error);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.ui.displayCommand(result);
|
|
272
|
+
this.ui.showToast('Command generated successfully!', 'success');
|
|
273
|
+
} catch (error) {
|
|
274
|
+
this.ui.showToast(error.message, 'error');
|
|
275
|
+
} finally {
|
|
276
|
+
this.ui.hideLoading();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Initialize app when DOM is ready
|
|
282
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
283
|
+
window.app = new App();
|
|
284
|
+
window.app.init();
|
|
285
|
+
});
|