haraps 1.0.4 → 1.0.5
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/bin/index.js +429 -48
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -50,24 +50,98 @@ fs.chmodSync(path.resolve(process.cwd(), 'dev.sh'), '755');
|
|
|
50
50
|
|
|
51
51
|
writeFile('dev.ps1', `
|
|
52
52
|
# =============================================
|
|
53
|
-
# Local Development Runner
|
|
53
|
+
# Project Local Development Runner
|
|
54
54
|
# =============================================
|
|
55
|
+
# Starts the Backend and Next.js frontend
|
|
56
|
+
# using concurrently in a single terminal window.
|
|
55
57
|
|
|
58
|
+
Write-Host ""
|
|
59
|
+
Write-Host "=============================================" -ForegroundColor Cyan
|
|
60
|
+
Write-Host " 🚀 Project Local Dev Server" -ForegroundColor Cyan
|
|
56
61
|
Write-Host "=============================================" -ForegroundColor Cyan
|
|
57
|
-
Write-Host "
|
|
62
|
+
Write-Host ""
|
|
63
|
+
Write-Host " Backend → http://localhost:8787" -ForegroundColor Green
|
|
64
|
+
Write-Host " Frontend → http://localhost:3000" -ForegroundColor Blue
|
|
65
|
+
Write-Host ""
|
|
66
|
+
Write-Host " Starting unified development environment..." -ForegroundColor Yellow
|
|
58
67
|
Write-Host "=============================================" -ForegroundColor Cyan
|
|
68
|
+
Write-Host ""
|
|
69
|
+
|
|
70
|
+
# Cleanup zombie worker processes and ports to prevent conflicts
|
|
71
|
+
Write-Host " Performing aggressive process cleanup..." -ForegroundColor Gray
|
|
72
|
+
|
|
73
|
+
# Define processes to kill
|
|
74
|
+
$procNames = @("workerd", "node", "concurrently")
|
|
75
|
+
|
|
76
|
+
foreach ($procName in $procNames) {
|
|
77
|
+
$procs = Get-Process $procName -ErrorAction SilentlyContinue
|
|
78
|
+
if ($procs) {
|
|
79
|
+
Write-Host " Stopping $procName ($($procs.Count) instances)..." -ForegroundColor Yellow
|
|
80
|
+
$procs | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Active verification loop to ensure all target processes have fully exited
|
|
85
|
+
$maxWaitSeconds = 5
|
|
86
|
+
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
|
87
|
+
while ($stopwatch.Elapsed.TotalSeconds -lt $maxWaitSeconds) {
|
|
88
|
+
$remainingProcs = Get-Process $procNames -ErrorAction SilentlyContinue
|
|
89
|
+
if (-not $remainingProcs) {
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
Start-Sleep -Milliseconds 200
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Function to robustly remove a directory with retry logic
|
|
96
|
+
function Remove-CacheDirectory($path, $label) {
|
|
97
|
+
if (Test-Path $path) {
|
|
98
|
+
Write-Host " Purging $label cache ($path)..." -ForegroundColor Gray
|
|
99
|
+
$attempts = 0
|
|
100
|
+
$maxAttempts = 5
|
|
101
|
+
$success = $false
|
|
102
|
+
|
|
103
|
+
while ($attempts -lt $maxAttempts -and -not $success) {
|
|
104
|
+
try {
|
|
105
|
+
Remove-Item -Recurse -Force $path -ErrorAction Stop
|
|
106
|
+
$success = $true
|
|
107
|
+
} catch {
|
|
108
|
+
$attempts++
|
|
109
|
+
if ($attempts -lt $maxAttempts) {
|
|
110
|
+
Write-Host " Lock detected on $path. Retrying ($attempts/$maxAttempts)..." -ForegroundColor DarkYellow
|
|
111
|
+
Start-Sleep -Milliseconds (300 * $attempts)
|
|
112
|
+
} else {
|
|
113
|
+
Write-Host " Warning: Could not fully purge $path. Some files may be locked." -ForegroundColor Red
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Hard clear of wrangler state and Next.js cache
|
|
121
|
+
Remove-CacheDirectory "backend/.wrangler" "wrangler state"
|
|
122
|
+
Remove-CacheDirectory "frontend/.next" "frontend Turbopack cache"
|
|
59
123
|
|
|
60
|
-
# Aggressive cleanup of ports 3000 and 8787
|
|
61
124
|
$ports = @(8787, 3000)
|
|
62
125
|
foreach ($port in $ports) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
126
|
+
Write-Host " Ensuring port $port is free..." -ForegroundColor Gray
|
|
127
|
+
$retryCount = 0
|
|
128
|
+
while ($retryCount -lt 5) {
|
|
129
|
+
$netstatOutput = netstat -ano | Select-String ":$port\s+.*LISTENING\s+(\d+)"
|
|
130
|
+
if ($netstatOutput) {
|
|
131
|
+
$targetPid = $netstatOutput.Matches[0].Groups[1].Value
|
|
132
|
+
Write-Host " Port $port occupied by PID $targetPid. Terminating..." -ForegroundColor Red
|
|
133
|
+
Stop-Process -Id $targetPid -Force -ErrorAction SilentlyContinue
|
|
134
|
+
Start-Sleep -Seconds 1
|
|
135
|
+
$retryCount++
|
|
136
|
+
} else {
|
|
137
|
+
break
|
|
138
|
+
}
|
|
68
139
|
}
|
|
69
140
|
}
|
|
70
141
|
|
|
142
|
+
$env:WRANGLER_SEND_METRICS = "false"
|
|
143
|
+
$env:NODE_OPTIONS = "--max-old-space-size=4096"
|
|
144
|
+
|
|
71
145
|
Write-Host "Starting development environment..." -ForegroundColor Green
|
|
72
146
|
$runnerScript = @'
|
|
73
147
|
const { spawn, execSync } = require('child_process');
|
|
@@ -87,6 +161,8 @@ function formatUptime() {
|
|
|
87
161
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
88
162
|
const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
|
|
89
163
|
const s = (elapsed % 60).toString().padStart(2, '0');
|
|
164
|
+
const h = Math.floor(elapsed / 3600);
|
|
165
|
+
if (h > 0) return \`\${h}h \${m}m \${s}s\`;
|
|
90
166
|
return \`\${m}m \${s}s\`;
|
|
91
167
|
}
|
|
92
168
|
|
|
@@ -97,41 +173,119 @@ function updateLegend() {
|
|
|
97
173
|
if (!process.stdout.isTTY) return;
|
|
98
174
|
const rows = process.stdout.rows || 30;
|
|
99
175
|
const cols = process.stdout.columns || 80;
|
|
176
|
+
|
|
100
177
|
if (rows !== lastRows || cols !== lastCols) {
|
|
101
|
-
process.stdout.write(
|
|
178
|
+
process.stdout.write(\`\x1b[1;\${rows - 2}r\`);
|
|
102
179
|
lastRows = rows;
|
|
103
180
|
lastCols = cols;
|
|
104
181
|
}
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
182
|
+
|
|
183
|
+
const lineCount = (buffer.match(/\n/g) || []).length;
|
|
184
|
+
const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
|
|
185
|
+
const uptimeStr = formatUptime();
|
|
186
|
+
const legend1 = \` [Copy] 'c': All (\${lineCount}L / \${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors \`;
|
|
187
|
+
const legend2 = \` [System] Uptime: \${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit | 'Q': Clean & Quit \`;
|
|
188
|
+
|
|
189
|
+
process.stdout.write(\`\x1b[s\`);
|
|
190
|
+
process.stdout.write(\`\x1b[\${rows - 1};1H\x1b[42m\x1b[30m\${legend1.padEnd(cols)}\x1b[0m\`);
|
|
191
|
+
process.stdout.write(\`\x1b[\${rows};1H\x1b[42m\x1b[30m\${legend2.padEnd(cols)}\x1b[0m\`);
|
|
192
|
+
process.stdout.write(\`\x1b[u\`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let legendInterval;
|
|
196
|
+
function startLegendUpdater() {
|
|
197
|
+
if (legendInterval) clearInterval(legendInterval);
|
|
198
|
+
legendInterval = setInterval(() => {
|
|
199
|
+
updateLegend();
|
|
200
|
+
}, 1000);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function cleanupLegend() {
|
|
204
|
+
if (legendInterval) clearInterval(legendInterval);
|
|
205
|
+
if (!process.stdout.isTTY) return;
|
|
206
|
+
process.stdout.write('\x1b[r');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
process.stdout.on('resize', () => {
|
|
210
|
+
if (!process.stdout.isTTY) return;
|
|
211
|
+
cleanupLegend();
|
|
212
|
+
console.clear();
|
|
213
|
+
lastRows = 0;
|
|
214
|
+
updateLegend();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
function extractErrors(text) {
|
|
218
|
+
const lines = text.split('\n');
|
|
219
|
+
const errorPattern = /(error|exception|fail|⨯|✗|traceback)/i;
|
|
220
|
+
let inErrorBlock = false;
|
|
221
|
+
let blockStart = 0;
|
|
222
|
+
const blocks = [];
|
|
223
|
+
|
|
224
|
+
for (let i = 0; i < lines.length; i++) {
|
|
225
|
+
if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
|
|
226
|
+
if (!inErrorBlock) {
|
|
227
|
+
inErrorBlock = true;
|
|
228
|
+
blockStart = Math.max(0, i - 2);
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
if (inErrorBlock) {
|
|
232
|
+
if (lines[i].trim() === '') continue;
|
|
233
|
+
const blockEnd = Math.min(lines.length - 1, i + 2);
|
|
234
|
+
blocks.push(lines.slice(blockStart, blockEnd + 1).join('\n'));
|
|
235
|
+
inErrorBlock = false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (inErrorBlock) {
|
|
240
|
+
blocks.push(lines.slice(blockStart).join('\n'));
|
|
241
|
+
}
|
|
242
|
+
return blocks.length > 0 ? blocks.join('\n\n--- [Next Error Block] ---\n\n') : '';
|
|
109
243
|
}
|
|
110
244
|
|
|
111
|
-
|
|
245
|
+
function formatPrefix(isError, source) {
|
|
246
|
+
const d = new Date();
|
|
247
|
+
const ts = String(d.getHours()).padStart(2, '0') + ':' +
|
|
248
|
+
String(d.getMinutes()).padStart(2, '0') + ':' +
|
|
249
|
+
String(d.getSeconds()).padStart(2, '0');
|
|
250
|
+
const color = source === 'worker' ? '\x1b[36m' : '\x1b[35m';
|
|
251
|
+
const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
|
|
252
|
+
return \`\x1b[90m[\${ts}]\x1b[0m \${color}\${tag}\x1b[0m \`;
|
|
253
|
+
}
|
|
112
254
|
|
|
113
255
|
function createStreamHandler(isError, source) {
|
|
114
256
|
let remainder = '';
|
|
115
257
|
return (d) => {
|
|
116
258
|
const chunk = remainder + d.toString();
|
|
117
|
-
const lines = chunk.split('
|
|
259
|
+
const lines = chunk.split('\n');
|
|
118
260
|
remainder = lines.pop();
|
|
119
261
|
for (let line of lines) {
|
|
120
|
-
line = line.replace(
|
|
262
|
+
line = line.replace(/\x1b\[[23]J/g, '').replace(/\x1b\[[0-9;]*H/g, '').replace(/\x1bc/g, '');
|
|
263
|
+
line = line.replace(/\x1b\[[0-9]*[A-G]/g, '').replace(/\x1b\[[0-9]*[K]/g, '');
|
|
264
|
+
line = line.replace(/\r/g, '');
|
|
265
|
+
line = line.replace(/^\[[^\]]+\]\s*/, '');
|
|
266
|
+
|
|
121
267
|
if (line.trim() === '') continue;
|
|
122
|
-
|
|
123
|
-
const
|
|
268
|
+
|
|
269
|
+
const prefix = formatPrefix(isError, source);
|
|
270
|
+
const out = prefix + line + '\n';
|
|
271
|
+
|
|
124
272
|
if (isError) process.stderr.write(out);
|
|
125
273
|
else process.stdout.write(out);
|
|
126
274
|
buffer += out;
|
|
127
275
|
}
|
|
128
|
-
if (buffer.length >
|
|
276
|
+
if (buffer.length > 1000000) {
|
|
277
|
+
const idx = buffer.indexOf('\n', buffer.length - 500000);
|
|
278
|
+
buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
|
|
279
|
+
}
|
|
129
280
|
};
|
|
130
281
|
}
|
|
131
282
|
|
|
132
283
|
function startWorker() {
|
|
133
284
|
workerChild = spawn('npx.cmd', ['concurrently', '-c', 'cyan', '-n', 'worker', '"cd backend && npx wrangler dev"'], {
|
|
134
|
-
env: { ...process.env, FORCE_COLOR: '1' },
|
|
285
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
286
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
287
|
+
windowsHide: true,
|
|
288
|
+
shell: true
|
|
135
289
|
});
|
|
136
290
|
workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
|
|
137
291
|
workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
|
|
@@ -139,43 +293,132 @@ function startWorker() {
|
|
|
139
293
|
|
|
140
294
|
function startFrontend() {
|
|
141
295
|
frontendChild = spawn('npx.cmd', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"cd frontend && npm run dev"'], {
|
|
142
|
-
env: { ...process.env, FORCE_COLOR: '1' },
|
|
296
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
297
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
298
|
+
windowsHide: true,
|
|
299
|
+
shell: true
|
|
143
300
|
});
|
|
144
301
|
frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
|
|
145
302
|
frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
|
|
146
303
|
}
|
|
147
304
|
|
|
148
|
-
|
|
149
|
-
|
|
305
|
+
function startServices() {
|
|
306
|
+
startWorker();
|
|
307
|
+
startFrontend();
|
|
308
|
+
}
|
|
309
|
+
|
|
150
310
|
console.clear();
|
|
151
311
|
updateLegend();
|
|
312
|
+
startLegendUpdater();
|
|
313
|
+
startServices();
|
|
152
314
|
|
|
153
315
|
if (process.stdin.isTTY) {
|
|
154
316
|
process.stdin.setRawMode(true);
|
|
155
317
|
process.stdin.resume();
|
|
156
318
|
process.stdin.setEncoding('utf8');
|
|
157
319
|
process.stdin.on('data', k => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
320
|
+
const now = Date.now();
|
|
321
|
+
if (k === 'c' || k === 'C') {
|
|
322
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
323
|
+
if (!clean.trim()) {
|
|
324
|
+
console.log('\n\x1b[33m⚠️ Buffer is empty.\x1b[0m\n');
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
execSync('clip', { input: clean });
|
|
329
|
+
console.log('\n\x1b[32m📋 Copied ALL logs to clipboard!\x1b[0m\n');
|
|
330
|
+
} catch (e) {}
|
|
331
|
+
} else if (k === 's' || k === 'S') {
|
|
332
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
333
|
+
const shortLines = clean.split('\n').slice(-500).join('\n');
|
|
334
|
+
try {
|
|
335
|
+
execSync('clip', { input: shortLines });
|
|
336
|
+
console.log('\n\x1b[32m📋 Copied last 500 lines to clipboard!\x1b[0m\n');
|
|
337
|
+
} catch (e) {}
|
|
338
|
+
} else if (k === 'w' || k === 'W') {
|
|
339
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
340
|
+
const workerLogs = clean.split('\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\n');
|
|
341
|
+
try {
|
|
342
|
+
execSync('clip', { input: workerLogs });
|
|
343
|
+
console.log('\n\x1b[32m📋 Copied BACKEND logs to clipboard!\x1b[0m\n');
|
|
344
|
+
} catch (e) {}
|
|
345
|
+
} else if (k === 'f' || k === 'F') {
|
|
346
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
347
|
+
const frontendLogs = clean.split('\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\n');
|
|
348
|
+
try {
|
|
349
|
+
execSync('clip', { input: frontendLogs });
|
|
350
|
+
console.log('\n\x1b[32m📋 Copied FRONTEND logs to clipboard!\x1b[0m\n');
|
|
351
|
+
} catch (e) {}
|
|
352
|
+
} else if (k === 'e' || k === 'E') {
|
|
353
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
354
|
+
const errors = extractErrors(clean);
|
|
355
|
+
if (errors) {
|
|
356
|
+
try {
|
|
357
|
+
execSync('clip', { input: errors });
|
|
358
|
+
console.log('\n\x1b[31m🚨 Errors copied to clipboard!\x1b[0m\n');
|
|
359
|
+
} catch (e) {}
|
|
360
|
+
} else {
|
|
361
|
+
console.log('\n\x1b[32m✅ No errors detected.\x1b[0m\n');
|
|
362
|
+
}
|
|
363
|
+
} else if (k === 'r' || k === 'R') {
|
|
364
|
+
if (now - lastRestartTime < 1000) return;
|
|
365
|
+
lastRestartTime = now;
|
|
366
|
+
console.log('\n\x1b[33m🔄 Restarting ALL services...\x1b[0m\n');
|
|
367
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
368
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
369
|
+
startServices();
|
|
370
|
+
} else if (k === '1') {
|
|
371
|
+
if (now - lastRestartTime < 1000) return;
|
|
372
|
+
lastRestartTime = now;
|
|
373
|
+
console.log('\n\x1b[33m🔄 Restarting FRONTEND...\x1b[0m\n');
|
|
374
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
375
|
+
startFrontend();
|
|
376
|
+
} else if (k === '2') {
|
|
377
|
+
if (now - lastRestartTime < 1000) return;
|
|
378
|
+
lastRestartTime = now;
|
|
379
|
+
console.log('\n\x1b[33m🔄 Restarting BACKEND...\x1b[0m\n');
|
|
380
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
381
|
+
startWorker();
|
|
382
|
+
} else if (k === 'x' || k === 'X') {
|
|
383
|
+
console.clear();
|
|
384
|
+
buffer = '';
|
|
385
|
+
lastRows = 0;
|
|
386
|
+
updateLegend();
|
|
387
|
+
console.log('\x1b[32m🧹 Terminal cleared.\x1b[0m\n');
|
|
388
|
+
} else if (k === '\u0003' || k === 'q') {
|
|
389
|
+
console.log('\n\x1b[33mStopping services...\x1b[0m');
|
|
390
|
+
cleanupLegend();
|
|
167
391
|
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
168
392
|
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
169
|
-
|
|
170
|
-
} else if (k === '
|
|
393
|
+
process.exit();
|
|
394
|
+
} else if (k === 'Q') {
|
|
395
|
+
console.log('\n\x1b[31m🧹 Cleaning cache and dependencies, then stopping services...\x1b[0m');
|
|
396
|
+
cleanupLegend();
|
|
171
397
|
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
172
398
|
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
399
|
+
|
|
400
|
+
console.log('\x1b[33mRemoving node_modules, .next, and .wrangler...\x1b[0m');
|
|
401
|
+
const fs = require('fs');
|
|
402
|
+
const pathsToClean = ['frontend/.next', 'backend/.wrangler', 'frontend/node_modules', 'backend/node_modules', 'node_modules'];
|
|
403
|
+
for (const p of pathsToClean) {
|
|
404
|
+
try { fs.rmSync(p, { recursive: true, force: true }); } catch(e) {}
|
|
405
|
+
}
|
|
406
|
+
console.log('\x1b[32mCleanup complete.\x1b[0m');
|
|
173
407
|
process.exit();
|
|
174
408
|
}
|
|
175
409
|
});
|
|
176
410
|
}
|
|
411
|
+
|
|
412
|
+
process.on('SIGINT', () => {
|
|
413
|
+
cleanupLegend();
|
|
414
|
+
process.exit();
|
|
415
|
+
});
|
|
416
|
+
process.on('exit', () => {
|
|
417
|
+
cleanupLegend();
|
|
418
|
+
console.clear();
|
|
419
|
+
});
|
|
177
420
|
'@
|
|
178
|
-
$runnerPath = Join-Path $env:TEMP "
|
|
421
|
+
$runnerPath = Join-Path $env:TEMP "project-dev-runner.js"
|
|
179
422
|
Set-Content -Path $runnerPath -Value $runnerScript -Encoding UTF8
|
|
180
423
|
node $runnerPath
|
|
181
424
|
`);
|
|
@@ -205,24 +448,162 @@ echo -e "\\e[32mDeployment Complete!\\e[0m"
|
|
|
205
448
|
fs.chmodSync(path.resolve(process.cwd(), 'deploy.sh'), '755');
|
|
206
449
|
|
|
207
450
|
writeFile('deploy.ps1', `
|
|
451
|
+
param(
|
|
452
|
+
[switch]$SkipFrontend = $false,
|
|
453
|
+
[switch]$SkipBackend = $false,
|
|
454
|
+
[switch]$Fast = $false
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
if ($Fast) {
|
|
458
|
+
Write-Host "[FAST] Fast mode enabled." -ForegroundColor Cyan
|
|
459
|
+
}
|
|
460
|
+
|
|
208
461
|
$ErrorActionPreference = 'Stop'
|
|
462
|
+
Set-Location $PSScriptRoot
|
|
463
|
+
|
|
464
|
+
# Load .env files if present
|
|
465
|
+
$envFiles = @(".env", ".env.local", "frontend\\.env.local")
|
|
466
|
+
foreach ($file in $envFiles) {
|
|
467
|
+
$envFile = Join-Path $PSScriptRoot $file
|
|
468
|
+
if (Test-Path $envFile) {
|
|
469
|
+
Write-Host "[RESILIENCE] Loading environment variables from $file..." -ForegroundColor Cyan
|
|
470
|
+
Get-Content $envFile | Where-Object { $_ -match '^\s*([^#\s][^=]+)=(.*)' } | ForEach-Object {
|
|
471
|
+
$name = $matches[1].Trim()
|
|
472
|
+
$value = $matches[2].Trim()
|
|
473
|
+
if ($value -match '^"(.*)"$') { $value = $matches[1] }
|
|
474
|
+
elseif ($value -match "^'(.*)'$") { $value = $matches[1] }
|
|
475
|
+
[Environment]::SetEnvironmentVariable($name, $value, "Process")
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
209
479
|
|
|
210
|
-
Write-Host "
|
|
211
|
-
|
|
212
|
-
|
|
480
|
+
Write-Host "[RESILIENCE] Terminating conflicting processes to prevent locks..." -ForegroundColor Yellow
|
|
481
|
+
$conflictingProcs = Get-WmiObject Win32_Process | Where-Object {
|
|
482
|
+
$_.Name -match 'node.exe' -and
|
|
483
|
+
($_.CommandLine -match 'next build' -or $_.CommandLine -match 'tsc' -or $_.CommandLine -match 'wrangler deploy')
|
|
484
|
+
}
|
|
485
|
+
foreach ($p in $conflictingProcs) {
|
|
486
|
+
Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
|
|
487
|
+
}
|
|
213
488
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
489
|
+
$GlobalReport = [PSCustomObject]@{
|
|
490
|
+
StartTime = (Get-Date)
|
|
491
|
+
Status = "Running"
|
|
492
|
+
Steps = @()
|
|
493
|
+
Error = $null
|
|
494
|
+
}
|
|
218
495
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
496
|
+
$ReportsDir = Join-Path $PSScriptRoot "deployment_reports"
|
|
497
|
+
if (!(Test-Path $ReportsDir)) { New-Item -ItemType Directory -Path $ReportsDir | Out-Null }
|
|
498
|
+
|
|
499
|
+
function Add-Step {
|
|
500
|
+
param([string]$Name, [string]$Status, [string]$Details = "")
|
|
501
|
+
$GlobalReport.Steps += [PSCustomObject]@{
|
|
502
|
+
Name = $Name
|
|
503
|
+
Status = $Status
|
|
504
|
+
Details = $Details
|
|
505
|
+
Time = (Get-Date).ToString("T")
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function Generate-FinalReport {
|
|
510
|
+
param([string]$FinalStatus)
|
|
511
|
+
$GlobalReport.Status = $FinalStatus
|
|
512
|
+
|
|
513
|
+
$reportText = "=================================================\`n"
|
|
514
|
+
$reportText += "PROJECT DEPLOYMENT REPORT\`n"
|
|
515
|
+
$reportText += "=================================================\`n"
|
|
516
|
+
$reportText += "Status: $FinalStatus\`n"
|
|
517
|
+
$reportText += "Duration: $((Get-Date) - $GlobalReport.StartTime)\`n"
|
|
518
|
+
$reportText += "-------------------------------------------------\`n"
|
|
519
|
+
|
|
520
|
+
foreach ($step in $GlobalReport.Steps) {
|
|
521
|
+
$indicator = if ($step.Status -eq "SUCCESS") { "[OK]" } else { "[FAIL]" }
|
|
522
|
+
$reportText += "$indicator [$($step.Time)] $($step.Name): $($step.Status)\`n"
|
|
523
|
+
if ($step.Details) { $reportText += " > $($step.Details)\`n" }
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if ($GlobalReport.Error) {
|
|
527
|
+
$reportText += "-------------------------------------------------\`n"
|
|
528
|
+
$reportText += "CRITICAL ERROR:\`n$($GlobalReport.Error)\`n"
|
|
529
|
+
}
|
|
530
|
+
$reportText += "=================================================\`n"
|
|
531
|
+
|
|
532
|
+
$statusIndicator = if ($FinalStatus -eq "SUCCESS") { "SUCCESS" } else { "FAILED" }
|
|
533
|
+
$humanTime = (Get-Date).ToString("ddMMM_hh-mmtt")
|
|
534
|
+
$fileName = "Deploy_\${statusIndicator}_\${humanTime}.txt"
|
|
535
|
+
$filePath = Join-Path $ReportsDir $fileName
|
|
536
|
+
$reportText | Out-File -FilePath $filePath
|
|
537
|
+
|
|
538
|
+
try { $reportText | Set-Clipboard } catch {}
|
|
539
|
+
return $reportText
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function Invoke-ModernCommand {
|
|
543
|
+
param([string]$Step, [scriptblock]$Script, [switch]$NoExit)
|
|
544
|
+
Write-Host ""
|
|
545
|
+
Write-Host "[STEP] $($Step)..." -ForegroundColor Yellow
|
|
546
|
+
try {
|
|
547
|
+
$global:LASTEXITCODE = 0
|
|
548
|
+
& $Script
|
|
549
|
+
$exitCode = $LASTEXITCODE
|
|
550
|
+
|
|
551
|
+
if ($exitCode -eq 0) {
|
|
552
|
+
Add-Step $Step "SUCCESS"
|
|
553
|
+
Write-Host "[OK] $Step completed successfully." -ForegroundColor Green
|
|
554
|
+
} else {
|
|
555
|
+
throw "Command failed with exit code $exitCode."
|
|
556
|
+
}
|
|
557
|
+
} catch {
|
|
558
|
+
$errorMsg = $_.ToString()
|
|
559
|
+
Add-Step $Step "FAILED" "Critical Error: See Global Report"
|
|
560
|
+
$GlobalReport.Error = "Step: $Step\`nError: $errorMsg"
|
|
561
|
+
|
|
562
|
+
if ($NoExit) {
|
|
563
|
+
Write-Host "[WARN] $Step failed, but continuing." -ForegroundColor Yellow
|
|
564
|
+
throw $errorMsg
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
$finalReport = Generate-FinalReport "FAILED"
|
|
568
|
+
Write-Host "\`nDEPLOYMENT FAILED!\`n$errorMsg" -ForegroundColor Red
|
|
569
|
+
Write-Host "[CLIPBOARD] Full error report copied to your clipboard!" -ForegroundColor Cyan
|
|
570
|
+
|
|
571
|
+
Set-Location $PSScriptRoot
|
|
572
|
+
exit 1
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (!$SkipBackend) {
|
|
577
|
+
try {
|
|
578
|
+
Push-Location backend
|
|
579
|
+
Invoke-ModernCommand "Rust Backend Deployment" { npx wrangler deploy }
|
|
580
|
+
} finally {
|
|
581
|
+
Pop-Location
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
Add-Step "Backend Deployment" "SKIPPED"
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (!$SkipFrontend) {
|
|
588
|
+
try {
|
|
589
|
+
Push-Location frontend
|
|
590
|
+
$env:NODE_OPTIONS = "--max-old-space-size=8192"
|
|
591
|
+
$lockFile = Join-Path (Get-Location) ".next\lock"
|
|
592
|
+
if (Test-Path $lockFile) { Remove-Item -Force $lockFile -ErrorAction SilentlyContinue }
|
|
593
|
+
|
|
594
|
+
Invoke-ModernCommand "Frontend Build" { npm run build }
|
|
595
|
+
Invoke-ModernCommand "Frontend Vercel Deploy" { npx vercel deploy --prod }
|
|
596
|
+
} finally {
|
|
597
|
+
Pop-Location
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
Add-Step "Frontend Deployment" "SKIPPED"
|
|
601
|
+
}
|
|
224
602
|
|
|
225
|
-
|
|
603
|
+
$finalReport = Generate-FinalReport "SUCCESS"
|
|
604
|
+
Write-Host "\`nDEPLOYMENT SUCCESSFUL!" -ForegroundColor Green
|
|
605
|
+
Write-Host $finalReport
|
|
606
|
+
Write-Host "[CLIPBOARD] Full report copied to your clipboard!" -ForegroundColor Cyan
|
|
226
607
|
`);
|
|
227
608
|
|
|
228
609
|
// 3. Agent Rules
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "haraps",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "NPM package to scaffold standard development environment, deployment scripts, AGENTS.md guidelines, and core workflows for a Next.js frontend + Rust Cloudflare Worker backend.",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"bin": {
|