haraps 1.0.5 β 1.0.6
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/SKILL.md +9 -1
- package/bin/index.js +1381 -756
- package/deploy.ps1 +414 -0
- package/deploy.sh +10 -0
- package/dev.ps1 +409 -0
- package/dev.sh +359 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -1,760 +1,1385 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
6
|
-
|
|
7
|
-
console.log('π Scaffold Project Environment (Hara-XY) π');
|
|
8
|
-
|
|
9
|
-
function ensureDir(dir) {
|
|
10
|
-
if (!fs.existsSync(dir)) {
|
|
11
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function writeFile(filePath, content) {
|
|
16
|
-
const fullPath = path.resolve(process.cwd(), filePath);
|
|
17
|
-
ensureDir(path.dirname(fullPath));
|
|
18
|
-
fs.writeFileSync(fullPath, content.trim() + '\n', 'utf8');
|
|
19
|
-
console.log(`β
Created: ${filePath}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// 1. Dev Scripts
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
console.log('π Scaffold Project Environment (Hara-XY) π');
|
|
8
|
+
|
|
9
|
+
function ensureDir(dir) {
|
|
10
|
+
if (!fs.existsSync(dir)) {
|
|
11
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writeFile(filePath, content) {
|
|
16
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
17
|
+
ensureDir(path.dirname(fullPath));
|
|
18
|
+
fs.writeFileSync(fullPath, content.trim() + '\n', 'utf8');
|
|
19
|
+
console.log(`β
Created: ${filePath}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 1. Dev Scripts
|
|
23
23
|
writeFile('dev.sh', `
|
|
24
|
-
#!/usr/bin/env bash
|
|
25
|
-
# =============================================
|
|
26
|
-
# Local Development Runner
|
|
27
|
-
# =============================================
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
echo
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
24
|
+
#!/usr/bin/env bash
|
|
25
|
+
# =============================================
|
|
26
|
+
# Medizys Local Development Runner
|
|
27
|
+
# =============================================
|
|
28
|
+
# Starts the Worker and Next.js frontend
|
|
29
|
+
# using concurrently in a single terminal window.
|
|
30
|
+
|
|
31
|
+
echo ""
|
|
32
|
+
echo -e "\\e[36m=============================================\\e[0m"
|
|
33
|
+
echo -e "\\e[36m π Medizys Local Dev Server\\e[0m"
|
|
34
|
+
echo -e "\\e[36m=============================================\\e[0m"
|
|
35
|
+
echo ""
|
|
36
|
+
echo -e "\\e[32m Backend β http://localhost:8787\\e[0m"
|
|
37
|
+
echo -e "\\e[34m Frontend β http://localhost:3000\\e[0m"
|
|
38
|
+
echo ""
|
|
39
|
+
echo -e "\\e[33m Starting unified development environment...\\e[0m"
|
|
40
|
+
echo -e "\\e[36m=============================================\\e[0m"
|
|
41
|
+
echo ""
|
|
42
|
+
|
|
43
|
+
# Ensure Node 22 is used if nvm is available
|
|
44
|
+
if [ -s "$HOME/.nvm/nvm.sh" ]; then
|
|
45
|
+
export NVM_DIR="$HOME/.nvm"
|
|
46
|
+
source "$NVM_DIR/nvm.sh"
|
|
47
|
+
nvm use 22 > /dev/null 2>&1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Cleanup zombie worker processes and ports to prevent conflicts
|
|
51
|
+
echo -e "\\e[90m Performing aggressive process cleanup...\\e[0m"
|
|
52
|
+
|
|
53
|
+
# Define processes to kill
|
|
54
|
+
for procName in workerd node concurrently; do
|
|
55
|
+
if pgrep -x "$procName" > /dev/null; then
|
|
56
|
+
echo -e "\\e[33m Stopping $procName...\\e[0m"
|
|
57
|
+
pkill -f "$procName" 2>/dev/null
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
# Active verification loop to ensure all target processes have fully exited
|
|
62
|
+
maxWaitSeconds=5
|
|
63
|
+
start=$(date +%s)
|
|
64
|
+
while [ $(( $(date +%s) - start )) -lt $maxWaitSeconds ]; do
|
|
65
|
+
if ! pgrep -f "workerd|concurrently" > /dev/null; then
|
|
66
|
+
break
|
|
67
|
+
fi
|
|
68
|
+
sleep 0.2
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
# Function to robustly remove a directory with retry logic
|
|
72
|
+
remove_cache_directory() {
|
|
73
|
+
local path=$1
|
|
74
|
+
local label=$2
|
|
75
|
+
if [ -d "$path" ]; then
|
|
76
|
+
echo -e "\\e[90m Purging $label cache ($path)...\\e[0m"
|
|
77
|
+
rm -rf "$path"
|
|
78
|
+
fi
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Hard clear of wrangler state and Next.js cache
|
|
82
|
+
remove_cache_directory "worker/.wrangler" "wrangler state"
|
|
83
|
+
remove_cache_directory "frontend/.next" "frontend Turbopack cache"
|
|
84
|
+
|
|
85
|
+
for port in 8787 3000; do
|
|
86
|
+
echo -e "\\e[90m Ensuring port $port is free...\\e[0m"
|
|
87
|
+
retryCount=0
|
|
88
|
+
while [ $retryCount -lt 5 ]; do
|
|
89
|
+
targetPid=$(lsof -ti :$port)
|
|
90
|
+
if [ ! -z "$targetPid" ]; then
|
|
91
|
+
echo -e "\\e[31m Port $port occupied by PID $targetPid. Terminating...\\e[0m"
|
|
92
|
+
kill -9 $targetPid 2>/dev/null
|
|
93
|
+
sleep 1
|
|
94
|
+
retryCount=$((retryCount+1))
|
|
95
|
+
else
|
|
96
|
+
break
|
|
97
|
+
fi
|
|
98
|
+
done
|
|
99
|
+
done
|
|
100
|
+
|
|
101
|
+
# Disable wrangler usage metrics prompt
|
|
102
|
+
export WRANGLER_SEND_METRICS="false"
|
|
103
|
+
# Limit Node.js memory
|
|
104
|
+
export NODE_OPTIONS="--max-old-space-size=4096"
|
|
105
|
+
|
|
106
|
+
echo -e "\\e[32mStarting development environment...\\e[0m"
|
|
107
|
+
|
|
108
|
+
runnerPath="/tmp/mediqueue-dev-runner.js"
|
|
109
|
+
|
|
110
|
+
cat << 'EOF' > "$runnerPath"
|
|
111
|
+
const { spawn, execSync } = require('child_process');
|
|
112
|
+
let buffer = '';
|
|
113
|
+
let workerChild;
|
|
114
|
+
let frontendChild;
|
|
115
|
+
let lastRestartTime = 0;
|
|
116
|
+
const startTime = Date.now();
|
|
117
|
+
|
|
118
|
+
function formatSize(bytes) {
|
|
119
|
+
if (bytes < 1024) return bytes + 'B';
|
|
120
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
|
|
121
|
+
return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function formatUptime() {
|
|
125
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
126
|
+
const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
|
|
127
|
+
const s = (elapsed % 60).toString().padStart(2, '0');
|
|
128
|
+
const h = Math.floor(elapsed / 3600);
|
|
129
|
+
if (h > 0) return \`\${h}h \${m}m \${s}s\`;
|
|
130
|
+
return \`\${m}m \${s}s\`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let lastRows = 0;
|
|
134
|
+
let lastCols = 0;
|
|
135
|
+
|
|
136
|
+
function updateLegend() {
|
|
137
|
+
if (!process.stdout.isTTY) return;
|
|
138
|
+
const rows = process.stdout.rows || 30;
|
|
139
|
+
const cols = process.stdout.columns || 80;
|
|
140
|
+
|
|
141
|
+
// Set scrolling region only if changed (DECSTBM homes the cursor to 1,1!)
|
|
142
|
+
if (rows !== lastRows || cols !== lastCols) {
|
|
143
|
+
process.stdout.write(\`\\x1b[1;\${rows - 2}r\`);
|
|
144
|
+
lastRows = rows;
|
|
145
|
+
lastCols = cols;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const lineCount = (buffer.match(/\\n/g) || []).length;
|
|
149
|
+
const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
|
|
150
|
+
const uptimeStr = formatUptime();
|
|
151
|
+
const legend1 = \` [Copy] 'c': All (\${lineCount}L / \${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors \`;
|
|
152
|
+
const legend2 = \` [System] Uptime: \${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit \`;
|
|
153
|
+
|
|
154
|
+
// Draw legend
|
|
155
|
+
process.stdout.write(\`\\x1b[s\`); // save cursor
|
|
156
|
+
process.stdout.write(\`\\x1b[\${rows - 1};1H\\x1b[42m\\x1b[30m\${legend1.padEnd(cols)}\\x1b[0m\`);
|
|
157
|
+
process.stdout.write(\`\\x1b[\${rows};1H\\x1b[42m\\x1b[30m\${legend2.padEnd(cols)}\\x1b[0m\`);
|
|
158
|
+
process.stdout.write(\`\\x1b[u\`); // restore cursor
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let legendInterval;
|
|
162
|
+
function startLegendUpdater() {
|
|
163
|
+
if (legendInterval) clearInterval(legendInterval);
|
|
164
|
+
legendInterval = setInterval(() => {
|
|
165
|
+
updateLegend();
|
|
166
|
+
}, 1000);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function cleanupLegend() {
|
|
170
|
+
if (legendInterval) clearInterval(legendInterval);
|
|
171
|
+
if (!process.stdout.isTTY) return;
|
|
172
|
+
process.stdout.write('\\x1b[r'); // reset scrolling region
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
process.stdout.on('resize', () => {
|
|
176
|
+
if (!process.stdout.isTTY) return;
|
|
177
|
+
cleanupLegend();
|
|
178
|
+
console.clear();
|
|
179
|
+
lastRows = 0;
|
|
180
|
+
updateLegend();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
function extractErrors(text) {
|
|
184
|
+
const lines = text.split('\\n');
|
|
185
|
+
const errorPattern = /(error|exception|fail|β¨―|β|traceback)/i;
|
|
186
|
+
let inErrorBlock = false;
|
|
187
|
+
let blockStart = 0;
|
|
188
|
+
const blocks = [];
|
|
189
|
+
|
|
190
|
+
for (let i = 0; i < lines.length; i++) {
|
|
191
|
+
if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
|
|
192
|
+
if (!inErrorBlock) {
|
|
193
|
+
inErrorBlock = true;
|
|
194
|
+
blockStart = Math.max(0, i - 2);
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
if (inErrorBlock) {
|
|
198
|
+
if (lines[i].trim() === '') continue;
|
|
199
|
+
const blockEnd = Math.min(lines.length - 1, i + 2);
|
|
200
|
+
blocks.push(lines.slice(blockStart, blockEnd + 1).join('\\n'));
|
|
201
|
+
inErrorBlock = false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (inErrorBlock) {
|
|
206
|
+
blocks.push(lines.slice(blockStart).join('\\n'));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return blocks.length > 0 ? blocks.join('\\n\\n--- [Next Error Block] ---\\n\\n') : '';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function formatPrefix(isError, source) {
|
|
213
|
+
const d = new Date();
|
|
214
|
+
const ts = String(d.getHours()).padStart(2, '0') + ':' +
|
|
215
|
+
String(d.getMinutes()).padStart(2, '0') + ':' +
|
|
216
|
+
String(d.getSeconds()).padStart(2, '0');
|
|
217
|
+
|
|
218
|
+
const color = source === 'worker' ? '\\x1b[36m' : '\\x1b[35m';
|
|
219
|
+
const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
|
|
220
|
+
return \`\\x1b[90m[\${ts}]\\x1b[0m \${color}\${tag}\\x1b[0m \`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function createStreamHandler(isError, source) {
|
|
224
|
+
let remainder = '';
|
|
225
|
+
return (d) => {
|
|
226
|
+
const chunk = remainder + d.toString();
|
|
227
|
+
const lines = chunk.split('\\n');
|
|
228
|
+
remainder = lines.pop();
|
|
229
|
+
for (let line of lines) {
|
|
230
|
+
line = line.replace(/\\x1b\\[[23]J/g, '').replace(/\\x1b\\[[0-9;]*H/g, '').replace(/\\x1bc/g, '');
|
|
231
|
+
line = line.replace(/\\x1b\\[[0-9]*[A-G]/g, '').replace(/\\x1b\\[[0-9]*[K]/g, '');
|
|
232
|
+
line = line.replace(/\\r/g, '');
|
|
233
|
+
line = line.replace(/^\\[[^\\]]+\\]\\s*/, '');
|
|
234
|
+
|
|
235
|
+
if (line.trim() === '') continue;
|
|
236
|
+
|
|
237
|
+
const prefix = formatPrefix(isError, source);
|
|
238
|
+
const out = prefix + line + '\\n';
|
|
239
|
+
|
|
240
|
+
if (isError) process.stderr.write(out);
|
|
241
|
+
else process.stdout.write(out);
|
|
242
|
+
buffer += out;
|
|
243
|
+
}
|
|
244
|
+
if (buffer.length > 1000000) {
|
|
245
|
+
const idx = buffer.indexOf('\\n', buffer.length - 500000);
|
|
246
|
+
buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function startWorker() {
|
|
252
|
+
workerChild = spawn('npx', ['concurrently', '-c', 'cyan', '-n', 'worker', '"npm run dev --prefix worker"'], {
|
|
253
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
254
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
255
|
+
shell: true
|
|
256
|
+
});
|
|
257
|
+
workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
|
|
258
|
+
workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function startFrontend() {
|
|
262
|
+
frontendChild = spawn('npx', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"npm run dev --prefix frontend"'], {
|
|
263
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
264
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
265
|
+
shell: true
|
|
266
|
+
});
|
|
267
|
+
frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
|
|
268
|
+
frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function startServices() {
|
|
272
|
+
startWorker();
|
|
273
|
+
startFrontend();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
console.clear();
|
|
277
|
+
updateLegend();
|
|
278
|
+
startLegendUpdater();
|
|
279
|
+
startServices();
|
|
280
|
+
|
|
281
|
+
function copyToClipboard(text) {
|
|
282
|
+
const platform = process.platform;
|
|
283
|
+
let cmd = '';
|
|
284
|
+
if (platform === 'darwin') cmd = 'pbcopy';
|
|
285
|
+
else if (platform === 'win32') cmd = 'clip';
|
|
286
|
+
else cmd = 'xclip -selection clipboard';
|
|
287
|
+
try {
|
|
288
|
+
execSync(cmd, { input: text });
|
|
289
|
+
return true;
|
|
290
|
+
} catch (e) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (process.stdin.isTTY) {
|
|
296
|
+
process.stdin.setRawMode(true);
|
|
297
|
+
process.stdin.resume();
|
|
298
|
+
process.stdin.setEncoding('utf8');
|
|
299
|
+
process.stdin.on('data', k => {
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
if (k === 'c' || k === 'C') {
|
|
302
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
303
|
+
if (!clean.trim()) {
|
|
304
|
+
console.log('\\n\\x1b[33mβ οΈ Buffer is empty.\\x1b[0m\\n');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (copyToClipboard(clean)) console.log('\\n\\x1b[32mπ Copied ALL logs to clipboard!\\x1b[0m\\n');
|
|
308
|
+
else console.log('\\n\\x1b[31mβ οΈ Failed to copy. Ensure xclip/pbcopy is installed.\\x1b[0m\\n');
|
|
309
|
+
} else if (k === 's' || k === 'S') {
|
|
310
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
311
|
+
const shortLines = clean.split('\\n').slice(-500).join('\\n');
|
|
312
|
+
if (copyToClipboard(shortLines)) console.log('\\n\\x1b[32mπ Copied last 500 lines to clipboard!\\x1b[0m\\n');
|
|
313
|
+
else console.log('\\n\\x1b[31mβ οΈ Failed to copy.\\x1b[0m\\n');
|
|
314
|
+
} else if (k === 'w' || k === 'W') {
|
|
315
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
316
|
+
const workerLogs = clean.split('\\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\\n');
|
|
317
|
+
if (copyToClipboard(workerLogs)) console.log('\\n\\x1b[32mπ Copied BACKEND logs to clipboard!\\x1b[0m\\n');
|
|
318
|
+
else console.log('\\n\\x1b[31mβ οΈ Failed to copy.\\x1b[0m\\n');
|
|
319
|
+
} else if (k === 'f' || k === 'F') {
|
|
320
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
321
|
+
const frontendLogs = clean.split('\\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\\n');
|
|
322
|
+
if (copyToClipboard(frontendLogs)) console.log('\\n\\x1b[32mπ Copied FRONTEND logs to clipboard!\\x1b[0m\\n');
|
|
323
|
+
else console.log('\\n\\x1b[31mβ οΈ Failed to copy.\\x1b[0m\\n');
|
|
324
|
+
} else if (k === 'e' || k === 'E') {
|
|
325
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
326
|
+
const errors = extractErrors(clean);
|
|
327
|
+
if (errors) {
|
|
328
|
+
if (copyToClipboard(errors)) console.log('\\n\\x1b[31mπ¨ Errors copied to clipboard!\\x1b[0m\\n');
|
|
329
|
+
else console.log('\\n\\x1b[31mβ οΈ Failed to copy errors.\\x1b[0m\\n');
|
|
330
|
+
} else {
|
|
331
|
+
console.log('\\n\\x1b[32mβ
No errors detected.\\x1b[0m\\n');
|
|
332
|
+
}
|
|
333
|
+
} else if (k === 'r' || k === 'R') {
|
|
334
|
+
if (now - lastRestartTime < 1000) return;
|
|
335
|
+
lastRestartTime = now;
|
|
336
|
+
console.log('\\n\\x1b[33mπ Restarting ALL services...\\x1b[0m\\n');
|
|
337
|
+
if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
|
|
338
|
+
if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
|
|
339
|
+
startServices();
|
|
340
|
+
} else if (k === '1') {
|
|
341
|
+
if (now - lastRestartTime < 1000) return;
|
|
342
|
+
lastRestartTime = now;
|
|
343
|
+
console.log('\\n\\x1b[33mπ Restarting FRONTEND...\\x1b[0m\\n');
|
|
344
|
+
if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
|
|
345
|
+
startFrontend();
|
|
346
|
+
} else if (k === '2') {
|
|
347
|
+
if (now - lastRestartTime < 1000) return;
|
|
348
|
+
lastRestartTime = now;
|
|
349
|
+
console.log('\\n\\x1b[33mπ Restarting BACKEND...\\x1b[0m\\n');
|
|
350
|
+
if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
|
|
351
|
+
startWorker();
|
|
352
|
+
} else if (k === 'x' || k === 'X') {
|
|
353
|
+
console.clear();
|
|
354
|
+
buffer = '';
|
|
355
|
+
lastRows = 0;
|
|
356
|
+
updateLegend();
|
|
357
|
+
console.log('\\x1b[32mπ§Ή Terminal cleared.\\x1b[0m\\n');
|
|
358
|
+
} else if (k === '\\u0003' || k === 'q' || k === 'Q') {
|
|
359
|
+
console.log('\\n\\x1b[33mStopping services...\\x1b[0m');
|
|
360
|
+
cleanupLegend();
|
|
361
|
+
if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
|
|
362
|
+
if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
|
|
363
|
+
process.exit();
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Cleanup on unexpected exits
|
|
369
|
+
process.on('SIGINT', () => {
|
|
370
|
+
cleanupLegend();
|
|
371
|
+
if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
|
|
372
|
+
if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
|
|
373
|
+
process.exit();
|
|
374
|
+
});
|
|
375
|
+
process.on('exit', () => {
|
|
376
|
+
cleanupLegend();
|
|
377
|
+
if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
|
|
378
|
+
if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
|
|
379
|
+
});
|
|
380
|
+
EOF
|
|
381
|
+
|
|
382
|
+
node "$runnerPath"
|
|
383
|
+
|
|
384
|
+
`);
|
|
385
|
+
fs.chmodSync(path.resolve(process.cwd(), 'dev.sh'), '755');
|
|
386
|
+
|
|
51
387
|
writeFile('dev.ps1', `
|
|
52
|
-
# =============================================
|
|
53
|
-
#
|
|
54
|
-
# =============================================
|
|
55
|
-
# Starts the
|
|
56
|
-
# using concurrently in a single terminal window.
|
|
57
|
-
|
|
58
|
-
Write-Host ""
|
|
59
|
-
Write-Host "=============================================" -ForegroundColor Cyan
|
|
60
|
-
Write-Host " π
|
|
61
|
-
Write-Host "=============================================" -ForegroundColor Cyan
|
|
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
|
|
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 "
|
|
122
|
-
Remove-CacheDirectory "frontend/.next" "frontend Turbopack cache"
|
|
123
|
-
|
|
124
|
-
$ports = @(8787, 3000)
|
|
125
|
-
foreach ($port in $ports) {
|
|
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
|
|
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
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
process.stdout.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
process.stdout.write(
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
});
|
|
420
|
-
'
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
388
|
+
# =============================================
|
|
389
|
+
# Medizys Local Development Runner
|
|
390
|
+
# =============================================
|
|
391
|
+
# Starts the Worker and Next.js frontend
|
|
392
|
+
# using concurrently in a single terminal window.
|
|
393
|
+
|
|
394
|
+
Write-Host ""
|
|
395
|
+
Write-Host "=============================================" -ForegroundColor Cyan
|
|
396
|
+
Write-Host " π Medizys Local Dev Server" -ForegroundColor Cyan
|
|
397
|
+
Write-Host "=============================================" -ForegroundColor Cyan
|
|
398
|
+
Write-Host ""
|
|
399
|
+
Write-Host " Backend β http://localhost:8787" -ForegroundColor Green
|
|
400
|
+
Write-Host " Frontend β http://localhost:3000" -ForegroundColor Blue
|
|
401
|
+
Write-Host ""
|
|
402
|
+
Write-Host " Starting unified development environment..." -ForegroundColor Yellow
|
|
403
|
+
Write-Host "=============================================" -ForegroundColor Cyan
|
|
404
|
+
Write-Host ""
|
|
405
|
+
|
|
406
|
+
# Cleanup zombie worker processes and ports to prevent conflicts
|
|
407
|
+
Write-Host " Performing aggressive process cleanup..." -ForegroundColor Gray
|
|
408
|
+
|
|
409
|
+
# Define processes to kill
|
|
410
|
+
$procNames = @("workerd", "node", "concurrently")
|
|
411
|
+
|
|
412
|
+
foreach ($procName in $procNames) {
|
|
413
|
+
$procs = Get-Process $procName -ErrorAction SilentlyContinue
|
|
414
|
+
if ($procs) {
|
|
415
|
+
Write-Host " Stopping $procName ($($procs.Count) instances)..." -ForegroundColor Yellow
|
|
416
|
+
$procs | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
# Active verification loop to ensure all target processes have fully exited
|
|
421
|
+
$maxWaitSeconds = 5
|
|
422
|
+
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
|
423
|
+
while ($stopwatch.Elapsed.TotalSeconds -lt $maxWaitSeconds) {
|
|
424
|
+
$remainingProcs = Get-Process $procNames -ErrorAction SilentlyContinue
|
|
425
|
+
if (-not $remainingProcs) {
|
|
426
|
+
break
|
|
427
|
+
}
|
|
428
|
+
Start-Sleep -Milliseconds 200
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
# Function to robustly remove a directory with retry logic
|
|
432
|
+
function Remove-CacheDirectory($path, $label) {
|
|
433
|
+
if (Test-Path $path) {
|
|
434
|
+
Write-Host " Purging $label cache ($path)..." -ForegroundColor Gray
|
|
435
|
+
$attempts = 0
|
|
436
|
+
$maxAttempts = 5
|
|
437
|
+
$success = $false
|
|
438
|
+
|
|
439
|
+
while ($attempts -lt $maxAttempts -and -not $success) {
|
|
440
|
+
try {
|
|
441
|
+
Remove-Item -Recurse -Force $path -ErrorAction Stop
|
|
442
|
+
$success = $true
|
|
443
|
+
} catch {
|
|
444
|
+
$attempts++
|
|
445
|
+
if ($attempts -lt $maxAttempts) {
|
|
446
|
+
Write-Host " Lock detected on $path. Retrying ($attempts/$maxAttempts)..." -ForegroundColor DarkYellow
|
|
447
|
+
Start-Sleep -Milliseconds (300 * $attempts)
|
|
448
|
+
} else {
|
|
449
|
+
Write-Host " Warning: Could not fully purge $path. Some files may be locked." -ForegroundColor Red
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
# Hard clear of wrangler state and Next.js cache to prevent Turbopack panics and corrupted dependency graphs
|
|
457
|
+
Remove-CacheDirectory "worker/.wrangler" "wrangler state"
|
|
458
|
+
Remove-CacheDirectory "frontend/.next" "frontend Turbopack cache"
|
|
459
|
+
|
|
460
|
+
$ports = @(8787, 3000)
|
|
461
|
+
foreach ($port in $ports) {
|
|
462
|
+
Write-Host " Ensuring port $port is free..." -ForegroundColor Gray
|
|
463
|
+
$retryCount = 0
|
|
464
|
+
while ($retryCount -lt 5) {
|
|
465
|
+
$netstatOutput = netstat -ano | Select-String ":$port\\s+.*LISTENING\\s+(\\d+)"
|
|
466
|
+
if ($netstatOutput) {
|
|
467
|
+
$targetPid = $netstatOutput.Matches[0].Groups[1].Value
|
|
468
|
+
Write-Host " Port $port occupied by PID $targetPid. Terminating..." -ForegroundColor Red
|
|
469
|
+
Stop-Process -Id $targetPid -Force -ErrorAction SilentlyContinue
|
|
470
|
+
Start-Sleep -Seconds 1
|
|
471
|
+
$retryCount++
|
|
472
|
+
} else {
|
|
473
|
+
break
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
# Telegram watcher disabled by user request
|
|
479
|
+
# Write-Host " Starting Telegram watcher in background..." -ForegroundColor Magenta
|
|
480
|
+
# $watcherJob = Start-Job -ScriptBlock {
|
|
481
|
+
# & "$using:PSScriptRoot\\scripts\\telegram-watcher.ps1"
|
|
482
|
+
# }
|
|
483
|
+
# Write-Host " Telegram watcher running (Job ID: $($watcherJob.Id))" -ForegroundColor Magenta
|
|
484
|
+
# Write-Host " Reply to @mediisysbot on Telegram to queue instructions." -ForegroundColor DarkMagenta
|
|
485
|
+
# Write-Host ""
|
|
486
|
+
|
|
487
|
+
# Disable wrangler usage metrics prompt to prevent concurrently from hanging
|
|
488
|
+
$env:WRANGLER_SEND_METRICS = "false"
|
|
489
|
+
|
|
490
|
+
# Limit Node.js memory to prevent OOM crashes while providing 4GB for Next.js App Router
|
|
491
|
+
$env:NODE_OPTIONS = "--max-old-space-size=4096"
|
|
492
|
+
|
|
493
|
+
# Run the monorepo dev command through a Node.js wrapper to capture logs
|
|
494
|
+
# and listen for the 'c' keypress to copy output to the clipboard.
|
|
495
|
+
Write-Host "Starting development environment..." -ForegroundColor Green
|
|
496
|
+
$runnerScript = @'
|
|
497
|
+
const { spawn, execSync } = require('child_process');
|
|
498
|
+
let buffer = '';
|
|
499
|
+
let workerChild;
|
|
500
|
+
let frontendChild;
|
|
501
|
+
let lastRestartTime = 0;
|
|
502
|
+
const startTime = Date.now();
|
|
503
|
+
|
|
504
|
+
function formatSize(bytes) {
|
|
505
|
+
if (bytes < 1024) return bytes + 'B';
|
|
506
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
|
|
507
|
+
return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function formatUptime() {
|
|
511
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
512
|
+
const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
|
|
513
|
+
const s = (elapsed % 60).toString().padStart(2, '0');
|
|
514
|
+
const h = Math.floor(elapsed / 3600);
|
|
515
|
+
if (h > 0) return \`\${h}h \${m}m \${s}s\`;
|
|
516
|
+
return \`\${m}m \${s}s\`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
let lastRows = 0;
|
|
520
|
+
let lastCols = 0;
|
|
521
|
+
|
|
522
|
+
function updateLegend() {
|
|
523
|
+
if (!process.stdout.isTTY) return;
|
|
524
|
+
const rows = process.stdout.rows || 30;
|
|
525
|
+
const cols = process.stdout.columns || 80;
|
|
526
|
+
|
|
527
|
+
// Set scrolling region only if changed (DECSTBM homes the cursor to 1,1!)
|
|
528
|
+
if (rows !== lastRows || cols !== lastCols) {
|
|
529
|
+
process.stdout.write(\`\\x1b[1;\${rows - 2}r\`);
|
|
530
|
+
lastRows = rows;
|
|
531
|
+
lastCols = cols;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const lineCount = (buffer.match(/\\n/g) || []).length;
|
|
535
|
+
const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
|
|
536
|
+
const uptimeStr = formatUptime();
|
|
537
|
+
const legend1 = \` [Copy] 'c': All (\${lineCount}L / \${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors \`;
|
|
538
|
+
const legend2 = \` [System] Uptime: \${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit | 'Q': Clean & Quit \`;
|
|
539
|
+
|
|
540
|
+
// Draw legend
|
|
541
|
+
process.stdout.write(\`\\x1b[s\`); // save cursor
|
|
542
|
+
process.stdout.write(\`\\x1b[\${rows - 1};1H\\x1b[42m\\x1b[30m\${legend1.padEnd(cols)}\\x1b[0m\`);
|
|
543
|
+
process.stdout.write(\`\\x1b[\${rows};1H\\x1b[42m\\x1b[30m\${legend2.padEnd(cols)}\\x1b[0m\`);
|
|
544
|
+
process.stdout.write(\`\\x1b[u\`); // restore cursor
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
let legendInterval;
|
|
548
|
+
function startLegendUpdater() {
|
|
549
|
+
if (legendInterval) clearInterval(legendInterval);
|
|
550
|
+
legendInterval = setInterval(() => {
|
|
551
|
+
updateLegend();
|
|
552
|
+
}, 1000);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function cleanupLegend() {
|
|
556
|
+
if (legendInterval) clearInterval(legendInterval);
|
|
557
|
+
if (!process.stdout.isTTY) return;
|
|
558
|
+
process.stdout.write('\\x1b[r'); // reset scrolling region
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
process.stdout.on('resize', () => {
|
|
562
|
+
if (!process.stdout.isTTY) return;
|
|
563
|
+
cleanupLegend();
|
|
564
|
+
console.clear();
|
|
565
|
+
lastRows = 0;
|
|
566
|
+
updateLegend();
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
function extractErrors(text) {
|
|
570
|
+
const lines = text.split('\\n');
|
|
571
|
+
const errorPattern = /(error|exception|fail|β¨―|β|traceback)/i;
|
|
572
|
+
let inErrorBlock = false;
|
|
573
|
+
let blockStart = 0;
|
|
574
|
+
const blocks = [];
|
|
575
|
+
|
|
576
|
+
for (let i = 0; i < lines.length; i++) {
|
|
577
|
+
// Check for error keywords or stack trace lines
|
|
578
|
+
if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
|
|
579
|
+
if (!inErrorBlock) {
|
|
580
|
+
inErrorBlock = true;
|
|
581
|
+
blockStart = Math.max(0, i - 2); // 2 lines of context before
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
if (inErrorBlock) {
|
|
585
|
+
// If the next line is empty, we might just be between stack traces
|
|
586
|
+
if (lines[i].trim() === '') continue;
|
|
587
|
+
|
|
588
|
+
// End of error block
|
|
589
|
+
const blockEnd = Math.min(lines.length - 1, i + 2); // 2 lines of context after
|
|
590
|
+
blocks.push(lines.slice(blockStart, blockEnd + 1).join('\\n'));
|
|
591
|
+
inErrorBlock = false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (inErrorBlock) {
|
|
596
|
+
blocks.push(lines.slice(blockStart).join('\\n'));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return blocks.length > 0 ? blocks.join('\\n\\n--- [Next Error Block] ---\\n\\n') : '';
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function formatPrefix(isError, source) {
|
|
603
|
+
const d = new Date();
|
|
604
|
+
const ts = String(d.getHours()).padStart(2, '0') + ':' +
|
|
605
|
+
String(d.getMinutes()).padStart(2, '0') + ':' +
|
|
606
|
+
String(d.getSeconds()).padStart(2, '0');
|
|
607
|
+
|
|
608
|
+
// Cyan for worker, Magenta for frontend
|
|
609
|
+
const color = source === 'worker' ? '\\x1b[36m' : '\\x1b[35m';
|
|
610
|
+
const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
|
|
611
|
+
return \`\\x1b[90m[\${ts}]\\x1b[0m \${color}\${tag}\\x1b[0m \`;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function createStreamHandler(isError, source) {
|
|
615
|
+
let remainder = '';
|
|
616
|
+
return (d) => {
|
|
617
|
+
const chunk = remainder + d.toString();
|
|
618
|
+
const lines = chunk.split('\\n');
|
|
619
|
+
remainder = lines.pop();
|
|
620
|
+
for (let line of lines) {
|
|
621
|
+
// Strip terminal-clearing and cursor-resetting escape codes
|
|
622
|
+
line = line.replace(/\\x1b\\[[23]J/g, '').replace(/\\x1b\\[[0-9;]*H/g, '').replace(/\\x1bc/g, '');
|
|
623
|
+
// Strip cursor up/down and line clear codes
|
|
624
|
+
line = line.replace(/\\x1b\\[[0-9]*[A-G]/g, '').replace(/\\x1b\\[[0-9]*[K]/g, '');
|
|
625
|
+
// Strip carriage returns which cause overwrite from start of line
|
|
626
|
+
line = line.replace(/\\r/g, '');
|
|
627
|
+
|
|
628
|
+
// Remove concurrently's prefix so we can add our own clean one
|
|
629
|
+
line = line.replace(/^\\[[^\\]]+\\]\\s*/, '');
|
|
630
|
+
|
|
631
|
+
if (line.trim() === '') continue;
|
|
632
|
+
|
|
633
|
+
const prefix = formatPrefix(isError, source);
|
|
634
|
+
const out = prefix + line + '\\n';
|
|
635
|
+
|
|
636
|
+
if (isError) process.stderr.write(out);
|
|
637
|
+
else process.stdout.write(out);
|
|
638
|
+
buffer += out;
|
|
639
|
+
}
|
|
640
|
+
if (buffer.length > 1000000) {
|
|
641
|
+
const idx = buffer.indexOf('\\n', buffer.length - 500000);
|
|
642
|
+
buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function startWorker() {
|
|
648
|
+
workerChild = spawn('npx.cmd', ['concurrently', '-c', 'cyan', '-n', 'worker', '"npm run dev --prefix worker"'], {
|
|
649
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
650
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
651
|
+
windowsHide: true,
|
|
652
|
+
shell: true
|
|
653
|
+
});
|
|
654
|
+
workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
|
|
655
|
+
workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function startFrontend() {
|
|
659
|
+
frontendChild = spawn('npx.cmd', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"npm run dev --prefix frontend"'], {
|
|
660
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
661
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
662
|
+
windowsHide: true,
|
|
663
|
+
shell: true
|
|
664
|
+
});
|
|
665
|
+
frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
|
|
666
|
+
frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function startServices() {
|
|
670
|
+
startWorker();
|
|
671
|
+
startFrontend();
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
console.clear();
|
|
675
|
+
updateLegend();
|
|
676
|
+
startLegendUpdater();
|
|
677
|
+
startServices();
|
|
678
|
+
|
|
679
|
+
if (process.stdin.isTTY) {
|
|
680
|
+
process.stdin.setRawMode(true);
|
|
681
|
+
process.stdin.resume();
|
|
682
|
+
process.stdin.setEncoding('utf8');
|
|
683
|
+
process.stdin.on('data', k => {
|
|
684
|
+
const now = Date.now();
|
|
685
|
+
if (k === 'c' || k === 'C') {
|
|
686
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
687
|
+
if (!clean.trim()) {
|
|
688
|
+
console.log('\\n\\x1b[33mβ οΈ Buffer is empty.\\x1b[0m\\n');
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
execSync('clip', { input: clean });
|
|
693
|
+
console.log('\\n\\x1b[32mπ Copied ALL logs to clipboard!\\x1b[0m\\n');
|
|
694
|
+
} catch (e) {}
|
|
695
|
+
} else if (k === 's' || k === 'S') {
|
|
696
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
697
|
+
const shortLines = clean.split('\\n').slice(-500).join('\\n');
|
|
698
|
+
try {
|
|
699
|
+
execSync('clip', { input: shortLines });
|
|
700
|
+
console.log('\\n\\x1b[32mπ Copied last 500 lines to clipboard!\\x1b[0m\\n');
|
|
701
|
+
} catch (e) {}
|
|
702
|
+
} else if (k === 'w' || k === 'W') {
|
|
703
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
704
|
+
const workerLogs = clean.split('\\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\\n');
|
|
705
|
+
try {
|
|
706
|
+
execSync('clip', { input: workerLogs });
|
|
707
|
+
console.log('\\n\\x1b[32mπ Copied BACKEND logs to clipboard!\\x1b[0m\\n');
|
|
708
|
+
} catch (e) {}
|
|
709
|
+
} else if (k === 'f' || k === 'F') {
|
|
710
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
711
|
+
const frontendLogs = clean.split('\\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\\n');
|
|
712
|
+
try {
|
|
713
|
+
execSync('clip', { input: frontendLogs });
|
|
714
|
+
console.log('\\n\\x1b[32mπ Copied FRONTEND logs to clipboard!\\x1b[0m\\n');
|
|
715
|
+
} catch (e) {}
|
|
716
|
+
} else if (k === 'e' || k === 'E') {
|
|
717
|
+
const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
|
|
718
|
+
const errors = extractErrors(clean);
|
|
719
|
+
if (errors) {
|
|
720
|
+
try {
|
|
721
|
+
execSync('clip', { input: errors });
|
|
722
|
+
console.log('\\n\\x1b[31mπ¨ Errors copied to clipboard!\\x1b[0m\\n');
|
|
723
|
+
} catch (e) {}
|
|
724
|
+
} else {
|
|
725
|
+
console.log('\\n\\x1b[32mβ
No errors detected.\\x1b[0m\\n');
|
|
726
|
+
}
|
|
727
|
+
} else if (k === 'r' || k === 'R') {
|
|
728
|
+
if (now - lastRestartTime < 1000) return;
|
|
729
|
+
lastRestartTime = now;
|
|
730
|
+
console.log('\\n\\x1b[33mπ Restarting ALL services...\\x1b[0m\\n');
|
|
731
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
732
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
733
|
+
startServices();
|
|
734
|
+
} else if (k === '1') {
|
|
735
|
+
if (now - lastRestartTime < 1000) return;
|
|
736
|
+
lastRestartTime = now;
|
|
737
|
+
console.log('\\n\\x1b[33mπ Restarting FRONTEND...\\x1b[0m\\n');
|
|
738
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
739
|
+
startFrontend();
|
|
740
|
+
} else if (k === '2') {
|
|
741
|
+
if (now - lastRestartTime < 1000) return;
|
|
742
|
+
lastRestartTime = now;
|
|
743
|
+
console.log('\\n\\x1b[33mπ Restarting BACKEND...\\x1b[0m\\n');
|
|
744
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
745
|
+
startWorker();
|
|
746
|
+
} else if (k === 'x' || k === 'X') {
|
|
747
|
+
console.clear();
|
|
748
|
+
buffer = '';
|
|
749
|
+
lastRows = 0;
|
|
750
|
+
updateLegend();
|
|
751
|
+
console.log('\\x1b[32mπ§Ή Terminal cleared.\\x1b[0m\\n');
|
|
752
|
+
} else if (k === '\\u0003' || k === 'q') {
|
|
753
|
+
console.log('\\n\\x1b[33mStopping services...\\x1b[0m');
|
|
754
|
+
cleanupLegend();
|
|
755
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
756
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
757
|
+
process.exit();
|
|
758
|
+
} else if (k === 'Q') {
|
|
759
|
+
console.log('\\n\\x1b[31mπ§Ή Cleaning cache and dependencies, then stopping services...\\x1b[0m');
|
|
760
|
+
cleanupLegend();
|
|
761
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
762
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
763
|
+
|
|
764
|
+
console.log('\\x1b[33mRemoving node_modules, .next, and .wrangler...\\x1b[0m');
|
|
765
|
+
const fs = require('fs');
|
|
766
|
+
const pathsToClean = ['frontend/.next', 'worker/.wrangler', 'frontend/node_modules', 'worker/node_modules', 'node_modules'];
|
|
767
|
+
for (const p of pathsToClean) {
|
|
768
|
+
try { fs.rmSync(p, { recursive: true, force: true }); } catch(e) {}
|
|
769
|
+
}
|
|
770
|
+
console.log('\\x1b[32mCleanup complete.\\x1b[0m');
|
|
771
|
+
process.exit();
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Cleanup on unexpected exits
|
|
777
|
+
process.on('SIGINT', () => {
|
|
778
|
+
cleanupLegend();
|
|
779
|
+
process.exit();
|
|
780
|
+
});
|
|
781
|
+
process.on('exit', () => {
|
|
782
|
+
cleanupLegend();
|
|
783
|
+
console.clear();
|
|
784
|
+
});
|
|
785
|
+
'@
|
|
786
|
+
$runnerPath = Join-Path $env:TEMP "mediqueue-dev-runner.js"
|
|
787
|
+
Set-Content -Path $runnerPath -Value $runnerScript -Encoding UTF8
|
|
788
|
+
node $runnerPath
|
|
789
|
+
|
|
790
|
+
# Cleanup watcher on exit
|
|
791
|
+
if ($null -ne (Get-Variable -Name watcherJob -ErrorAction SilentlyContinue)) {
|
|
792
|
+
if ($watcherJob) {
|
|
793
|
+
Stop-Job -Job $watcherJob -ErrorAction SilentlyContinue
|
|
794
|
+
Remove-Job -Job $watcherJob -ErrorAction SilentlyContinue
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
`);
|
|
799
|
+
|
|
800
|
+
// 2. Deploy Scripts
|
|
427
801
|
writeFile('deploy.sh', `
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
npx wrangler deploy
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
npm run build
|
|
443
|
-
npx vercel deploy --prod
|
|
444
|
-
cd ..
|
|
445
|
-
|
|
446
|
-
echo -e "\\e[32mDeployment Complete!\\e[0m"
|
|
447
|
-
`);
|
|
448
|
-
fs.chmodSync(path.resolve(process.cwd(), 'deploy.sh'), '755');
|
|
449
|
-
|
|
802
|
+
TARGET=$(readlink -f ~/mediqueue_linux/mediqueue/frontend/node_modules/@cloudflare/next-on-pages/dist/index.js)
|
|
803
|
+
sed -i 's/await (0, import_esbuild.build)({/await (0, import_esbuild.build)({ logLevel: "error",/g' $TARGET
|
|
804
|
+
sed -i 's/await (0, import_esbuild2.build)({/await (0, import_esbuild2.build)({ logLevel: "error",/g' $TARGET
|
|
805
|
+
cd ~/mediqueue_linux/mediqueue/frontend
|
|
806
|
+
export NODE_OPTIONS="--max_old_space_size=16384"
|
|
807
|
+
npx @cloudflare/next-on-pages
|
|
808
|
+
cp -r .vercel /mnt/c/Users/shivg/Desktop/Hara-XY/mediqueue-main/mediqueue/frontend/
|
|
809
|
+
cd /mnt/c/Users/shivg/Desktop/Hara-XY/mediqueue-main/mediqueue/frontend
|
|
810
|
+
$env:WRANGLER_SEND_METRICS="false"
|
|
811
|
+
npx wrangler pages deploy .vercel/output/static
|
|
812
|
+
|
|
813
|
+
`);
|
|
814
|
+
fs.chmodSync(path.resolve(process.cwd(), 'deploy.sh'), '755');
|
|
815
|
+
|
|
450
816
|
writeFile('deploy.ps1', `
|
|
451
|
-
param(
|
|
452
|
-
[switch]$
|
|
453
|
-
[switch]$
|
|
454
|
-
[switch]$
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
$
|
|
466
|
-
|
|
467
|
-
$
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
$
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
$
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
$
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
$
|
|
534
|
-
$
|
|
535
|
-
|
|
536
|
-
$
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
$
|
|
550
|
-
|
|
551
|
-
if ($
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
$
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
$
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
-
|
|
683
|
-
|
|
684
|
-
-
|
|
685
|
-
-
|
|
686
|
-
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
817
|
+
param(
|
|
818
|
+
[switch]$SkipSentry = $false,
|
|
819
|
+
[switch]$SkipSeed = $false,
|
|
820
|
+
[switch]$SkipApp = $false,
|
|
821
|
+
[switch]$SkipRouter = $false,
|
|
822
|
+
[switch]$SkipFrontend = $false,
|
|
823
|
+
[switch]$SkipDBSync = $false,
|
|
824
|
+
[switch]$SkipVisual = $false,
|
|
825
|
+
[switch]$Fast = $false
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
if ($Fast) {
|
|
829
|
+
Write-Host "[FAST] Fast mode enabled. Skipping DB, Router, and App releases." -ForegroundColor Cyan
|
|
830
|
+
$SkipSeed = $true
|
|
831
|
+
$SkipApp = $true
|
|
832
|
+
$SkipRouter = $true
|
|
833
|
+
$SkipFrontend = $false
|
|
834
|
+
$SkipSentry = $true
|
|
835
|
+
$SkipDBSync = $true
|
|
836
|
+
$SkipVisual = $true
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
# Medizys Modernized Deployment Script
|
|
840
|
+
# Features: Schema Sync, Consolidated Reporting, Timestamped Logs, Clipboard Integration
|
|
841
|
+
|
|
842
|
+
$ErrorActionPreference = 'Stop'
|
|
843
|
+
|
|
844
|
+
# Ensure we are in the root of the project
|
|
845
|
+
Set-Location $PSScriptRoot
|
|
846
|
+
|
|
847
|
+
# Load .env and .env.local files if present
|
|
848
|
+
$envFiles = @(".env", ".env.local", "frontend\\.env.local")
|
|
849
|
+
foreach ($file in $envFiles) {
|
|
850
|
+
$envFile = Join-Path $PSScriptRoot $file
|
|
851
|
+
if (Test-Path $envFile) {
|
|
852
|
+
Write-Host "[RESILIENCE] Loading environment variables from $file..." -ForegroundColor Cyan
|
|
853
|
+
Get-Content $envFile | Where-Object { $_ -match '^\\s*([^#\\s][^=]+)=(.*)' } | ForEach-Object {
|
|
854
|
+
$name = $matches[1].Trim()
|
|
855
|
+
$value = $matches[2].Trim()
|
|
856
|
+
if ($value -match '^"(.*)"$') { $value = $matches[1] }
|
|
857
|
+
elseif ($value -match "^'(.*)'$") { $value = $matches[1] }
|
|
858
|
+
[Environment]::SetEnvironmentVariable($name, $value, "Process")
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
# PROACTIVE COLLISION PREVENTION: Kill competing deployment tasks to prevent file locks
|
|
864
|
+
Write-Host "[RESILIENCE] Terminating conflicting build processes to prevent locks..." -ForegroundColor Yellow
|
|
865
|
+
$conflictingProcs = Get-WmiObject Win32_Process | Where-Object {
|
|
866
|
+
$_.Name -match 'node.exe' -and
|
|
867
|
+
($_.CommandLine -match 'next build' -or $_.CommandLine -match 'tsc' -or $_.CommandLine -match 'wrangler deploy')
|
|
868
|
+
}
|
|
869
|
+
foreach ($p in $conflictingProcs) {
|
|
870
|
+
Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
$GlobalReport = [PSCustomObject]@{
|
|
874
|
+
StartTime = (Get-Date)
|
|
875
|
+
Status = "Running"
|
|
876
|
+
Steps = @()
|
|
877
|
+
Error = $null
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
# Create reports directory in root
|
|
881
|
+
$ReportsDir = Join-Path $PSScriptRoot "deployment_reports"
|
|
882
|
+
if (!(Test-Path $ReportsDir)) { New-Item -ItemType Directory -Path $ReportsDir | Out-Null }
|
|
883
|
+
|
|
884
|
+
function Add-Step {
|
|
885
|
+
param([string]$Name, [string]$Status, [string]$Details = "")
|
|
886
|
+
$GlobalReport.Steps += [PSCustomObject]@{
|
|
887
|
+
Name = $Name
|
|
888
|
+
Status = $Status
|
|
889
|
+
Details = $Details
|
|
890
|
+
Time = (Get-Date).ToString("T")
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function Generate-FinalReport {
|
|
895
|
+
param([string]$FinalStatus)
|
|
896
|
+
$GlobalReport.Status = $FinalStatus
|
|
897
|
+
|
|
898
|
+
# Get IP and IST Time
|
|
899
|
+
$ipStr = "Unknown"
|
|
900
|
+
try { $ipStr = (Invoke-RestMethod -Uri 'https://api.ipify.org' -TimeoutSec 3).Trim() } catch { }
|
|
901
|
+
|
|
902
|
+
$istTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("India Standard Time")
|
|
903
|
+
$istStartTime = [System.TimeZoneInfo]::ConvertTimeFromUtc($GlobalReport.StartTime.ToUniversalTime(), $istTimeZone)
|
|
904
|
+
|
|
905
|
+
$reportText = "=================================================\`n"
|
|
906
|
+
$reportText += "MEDIQUEUE DEPLOYMENT REPORT\`n"
|
|
907
|
+
$reportText += "=================================================\`n"
|
|
908
|
+
$reportText += "Status: $FinalStatus\`n"
|
|
909
|
+
$reportText += "IP: $ipStr\`n"
|
|
910
|
+
$reportText += "Timestamp: $($istStartTime.ToString('MM/dd/yyyy HH:mm:ss')) (IST)\`n"
|
|
911
|
+
$reportText += "Duration: $((Get-Date) - $GlobalReport.StartTime)\`n"
|
|
912
|
+
$reportText += "-------------------------------------------------\`n"
|
|
913
|
+
|
|
914
|
+
foreach ($step in $GlobalReport.Steps) {
|
|
915
|
+
$indicator = if ($step.Status -eq "SUCCESS") { "[OK]" } else { "[FAIL]" }
|
|
916
|
+
$reportText += "$indicator [$($step.Time)] $($step.Name): $($step.Status)\`n"
|
|
917
|
+
if ($step.Details) { $reportText += " > $($step.Details)\`n" }
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if ($GlobalReport.Error) {
|
|
921
|
+
$reportText += "-------------------------------------------------\`n"
|
|
922
|
+
$reportText += "CRITICAL ERROR:\`n$($GlobalReport.Error)\`n"
|
|
923
|
+
}
|
|
924
|
+
$reportText += "=================================================\`n"
|
|
925
|
+
|
|
926
|
+
# Save to file
|
|
927
|
+
$statusIndicator = if ($FinalStatus -eq "SUCCESS") { "SUCCESS" } else { "FAILED" }
|
|
928
|
+
$hour = (Get-Date).Hour
|
|
929
|
+
$timeOfDay = if ($hour -ge 5 -and $hour -lt 12) { "Morning" }
|
|
930
|
+
elseif ($hour -ge 12 -and $hour -lt 17) { "Afternoon" }
|
|
931
|
+
elseif ($hour -ge 17 -and $hour -lt 21) { "Evening" }
|
|
932
|
+
else { "Night" }
|
|
933
|
+
|
|
934
|
+
$humanTime = (Get-Date).ToString("ddMMM_hh-mmtt") # e.g. 30Mar_02-30PM
|
|
935
|
+
$fileName = "Deploy_\${statusIndicator}_\${timeOfDay}_$humanTime.txt"
|
|
936
|
+
$filePath = Join-Path $ReportsDir $fileName
|
|
937
|
+
$reportText | Out-File -FilePath $filePath
|
|
938
|
+
|
|
939
|
+
# Copy to clipboard
|
|
940
|
+
try { $reportText | Set-Clipboard } catch {}
|
|
941
|
+
|
|
942
|
+
return $reportText
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function Send-Notification {
|
|
946
|
+
param([string]$Status, [string]$Report)
|
|
947
|
+
try {
|
|
948
|
+
Write-Host "[NOTIF] Dispatching deployment pulse..." -ForegroundColor Magenta
|
|
949
|
+
$notifPayload = (@{
|
|
950
|
+
version = "v2.5.2-stable"
|
|
951
|
+
env = "production"
|
|
952
|
+
status = $Status
|
|
953
|
+
report = $Report
|
|
954
|
+
chatId = "-1003863805343"
|
|
955
|
+
} | ConvertTo-Json)
|
|
956
|
+
|
|
957
|
+
Invoke-RestMethod -Uri "https://api.mediqueue.hara-xy.com/api/platform/notif/deploy" -Method Post -Body $notifPayload -Headers @{ "X-Requested-With" = "XMLHttpRequest" } -ContentType "application/json"
|
|
958
|
+
Write-Host "Dispatch confirmed." -ForegroundColor Green
|
|
959
|
+
} catch {
|
|
960
|
+
Write-Host "Notification dispatch failed." -ForegroundColor Yellow
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function Send-AntigravityChat {
|
|
965
|
+
param([string]$Status, [string]$Report)
|
|
966
|
+
try {
|
|
967
|
+
Write-Host "[ANTIGRAVITY] Sending deployment report to chat..." -ForegroundColor Magenta
|
|
968
|
+
$emoji = if ($Status -eq "SUCCESS") { "β
" } else { "β" }
|
|
969
|
+
$chatMessage = "$emoji Medizys Deployment $Status\`n\`n$Report"
|
|
970
|
+
|
|
971
|
+
# Append newline so the Antigravity Bridge auto-submits the message
|
|
972
|
+
$payload = @{ text = "$chatMessage\`n" } | ConvertTo-Json
|
|
973
|
+
|
|
974
|
+
Invoke-RestMethod \`
|
|
975
|
+
-Uri "http://localhost:5000/send_command" \`
|
|
976
|
+
-Method Post \`
|
|
977
|
+
-Body $payload \`
|
|
978
|
+
-ContentType "application/json" \`
|
|
979
|
+
-ErrorAction SilentlyContinue | Out-Null
|
|
980
|
+
|
|
981
|
+
Write-Host "[ANTIGRAVITY] Report dispatched to chat." -ForegroundColor Green
|
|
982
|
+
} catch {
|
|
983
|
+
Write-Host "[ANTIGRAVITY] Chat dispatch skipped (bridge not running)." -ForegroundColor DarkGray
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function Invoke-ModernCommand {
|
|
988
|
+
param([string]$Step, [scriptblock]$Script, [switch]$NoExit)
|
|
989
|
+
Write-Host ""
|
|
990
|
+
Write-Host "[STEP] $($Step)..." -ForegroundColor Yellow
|
|
991
|
+
try {
|
|
992
|
+
# Execute script correctly
|
|
993
|
+
$global:LASTEXITCODE = 0
|
|
994
|
+
& $Script
|
|
995
|
+
$exitCode = $LASTEXITCODE
|
|
996
|
+
|
|
997
|
+
if ($exitCode -eq 0) {
|
|
998
|
+
Add-Step $Step "SUCCESS"
|
|
999
|
+
Write-Host "[OK] $Step completed successfully." -ForegroundColor Green
|
|
1000
|
+
} else {
|
|
1001
|
+
throw "Command failed with exit code $exitCode."
|
|
1002
|
+
}
|
|
1003
|
+
} catch {
|
|
1004
|
+
$errorMsg = $_.ToString()
|
|
1005
|
+
Add-Step $Step "FAILED" "Critical Error: See Global Report"
|
|
1006
|
+
$GlobalReport.Error = "Step: $Step\`nError: $errorMsg"
|
|
1007
|
+
|
|
1008
|
+
if ($NoExit) {
|
|
1009
|
+
Write-Host "[WARN] $Step failed, but continuing (NoExit enabled)." -ForegroundColor Yellow
|
|
1010
|
+
throw $errorMsg
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
$finalReport = Generate-FinalReport "FAILED"
|
|
1014
|
+
|
|
1015
|
+
# Send failure notification
|
|
1016
|
+
Send-Notification -Status "FAILED" -Report $finalReport
|
|
1017
|
+
Send-AntigravityChat -Status "FAILED" -Report $finalReport
|
|
1018
|
+
|
|
1019
|
+
Write-Host ""
|
|
1020
|
+
Write-Host "DEPLOYMENT FAILED!" -ForegroundColor Red
|
|
1021
|
+
Write-Host "-------------------------------------------------"
|
|
1022
|
+
Write-Host $errorMsg -ForegroundColor Gray
|
|
1023
|
+
Write-Host "-------------------------------------------------"
|
|
1024
|
+
Write-Host "[CLIPBOARD] Full error report copied to your clipboard!" -ForegroundColor Cyan
|
|
1025
|
+
|
|
1026
|
+
# Ensure we return to root before exiting
|
|
1027
|
+
Set-Location $PSScriptRoot
|
|
1028
|
+
exit 1
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
# Phase -1: Security Audits & Clean Install
|
|
1033
|
+
Invoke-ModernCommand "Lockfile Linting" { pnpm run lint:lockfile }
|
|
1034
|
+
Invoke-ModernCommand "Clean Installation (Strict Lockfile)" { pnpm install --frozen-lockfile }
|
|
1035
|
+
|
|
1036
|
+
# Phase 0: Pre-flight Sync
|
|
1037
|
+
Invoke-ModernCommand "Sync Frontend Routes" { node "$PSScriptRoot\\scripts\\sync-routes.js" }
|
|
1038
|
+
Invoke-ModernCommand "Sync Shared Types" { Copy-Item "$PSScriptRoot\\worker\\src\\shared-types.ts" "$PSScriptRoot\\frontend\\lib\\shared-types.ts" -Force }
|
|
1039
|
+
Invoke-ModernCommand "Worker ESLint Audit" { pnpm --filter mediqueue-worker run lint }
|
|
1040
|
+
|
|
1041
|
+
# Phase 1 & 2: Worker
|
|
1042
|
+
try {
|
|
1043
|
+
Push-Location worker
|
|
1044
|
+
# Database Schema Sync (using latest Drizzle patterns)
|
|
1045
|
+
if (!$SkipDBSync) {
|
|
1046
|
+
Invoke-ModernCommand "Database Enum Drift Audit" { pnpm run db:audit }
|
|
1047
|
+
Invoke-ModernCommand "Database Migrations (HTTP)" { pnpm run db:migrate }
|
|
1048
|
+
Invoke-ModernCommand "Worker Type Check" { pnpm run typecheck }
|
|
1049
|
+
} else {
|
|
1050
|
+
Write-Host "[SKIP] Database Schema Sync skipped." -ForegroundColor Gray
|
|
1051
|
+
Add-Step "Database Enum Drift Audit" "SKIPPED"
|
|
1052
|
+
Add-Step "Database Schema Sync" "SKIPPED"
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
# Seed initial data (Achievements, etc.)
|
|
1056
|
+
if (!$SkipSeed) {
|
|
1057
|
+
Invoke-ModernCommand "Database Seeding" { pnpm run db:seed }
|
|
1058
|
+
} else {
|
|
1059
|
+
Write-Host "[SKIP] Database Seeding skipped." -ForegroundColor Gray
|
|
1060
|
+
Add-Step "Database Seeding" "SKIPPED"
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
# Phase 2: Worker Deployment
|
|
1064
|
+
if ($SkipSentry) {
|
|
1065
|
+
Invoke-ModernCommand "Worker Deployment (Fast)" { pnpm run deploy:fast }
|
|
1066
|
+
} else {
|
|
1067
|
+
try {
|
|
1068
|
+
Invoke-ModernCommand "Worker Deployment (Sentry)" { pnpm run deploy:sentry } -NoExit
|
|
1069
|
+
} catch {
|
|
1070
|
+
Write-Host "[WARN] Sentry deployment failed (likely timeout or config). Falling back to fast deploy..." -ForegroundColor Yellow
|
|
1071
|
+
Invoke-ModernCommand "Worker Deployment (Fallback)" { pnpm run deploy:fast }
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
} finally {
|
|
1075
|
+
Pop-Location
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
# Phase 2.5: Router Worker Deployment
|
|
1079
|
+
if (!$SkipRouter) {
|
|
1080
|
+
try {
|
|
1081
|
+
Push-Location router-worker
|
|
1082
|
+
Invoke-ModernCommand "Router Worker Deployment" { npx wrangler@4.80.0 deploy }
|
|
1083
|
+
} finally {
|
|
1084
|
+
Pop-Location
|
|
1085
|
+
}
|
|
1086
|
+
} else {
|
|
1087
|
+
Write-Host "[SKIP] Router Worker Deployment skipped." -ForegroundColor Gray
|
|
1088
|
+
Add-Step "Router Worker Deployment" "SKIPPED"
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
# Phase 3: Frontend Deployment
|
|
1092
|
+
if (!$SkipFrontend) {
|
|
1093
|
+
|
|
1094
|
+
try {
|
|
1095
|
+
Push-Location frontend
|
|
1096
|
+
$env:NODE_OPTIONS = "--max-old-space-size=8192"
|
|
1097
|
+
Write-Host "Cleaning previous build artifacts..." -ForegroundColor Gray
|
|
1098
|
+
|
|
1099
|
+
# PROACTIVE LOCK CLEANUP: Remove .next/lock if it exists to prevent build timeouts
|
|
1100
|
+
$lockFile = Join-Path (Get-Location) ".next\\lock"
|
|
1101
|
+
if (Test-Path $lockFile) {
|
|
1102
|
+
Write-Host "[RESILIENCE] Removing stale .next/lock..." -ForegroundColor Yellow
|
|
1103
|
+
Remove-Item -Force $lockFile -ErrorAction SilentlyContinue
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
if (!$Fast) {
|
|
1107
|
+
Write-Host "Cleaning previous build artifacts (Full Mode)..." -ForegroundColor Gray
|
|
1108
|
+
if (Test-Path out) { Remove-Item -Recurse -Force out -ErrorAction SilentlyContinue }
|
|
1109
|
+
} else {
|
|
1110
|
+
Write-Host "Preserving build cache for faster iterations (Fast Mode)..." -ForegroundColor Cyan
|
|
1111
|
+
if (Test-Path out) { Remove-Item -Recurse -Force out -ErrorAction SilentlyContinue }
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
# Generate Deployment Versioning for Awareness
|
|
1115
|
+
$deployId = (Get-Date).ToString("yyyyMMddHHmmss")
|
|
1116
|
+
$versionJson = @{
|
|
1117
|
+
version = "v2.5.2-stable"
|
|
1118
|
+
deployId = $deployId
|
|
1119
|
+
timestamp = (Get-Date).ToString("o")
|
|
1120
|
+
notes = "Portal boundaries and operational context audit."
|
|
1121
|
+
} | ConvertTo-Json
|
|
1122
|
+
$versionJson | Out-File -FilePath "public/version.json" -Encoding utf8
|
|
1123
|
+
Write-Host "[OK] Deployment version $deployId generated." -ForegroundColor Green
|
|
1124
|
+
|
|
1125
|
+
Invoke-ModernCommand "Frontend Type Check" { pnpm run typecheck }
|
|
1126
|
+
|
|
1127
|
+
if (!$SkipVisual) {
|
|
1128
|
+
Write-Host "\`n[STEP] Frontend Visual Tests..." -ForegroundColor Yellow
|
|
1129
|
+
$global:LASTEXITCODE = 0
|
|
1130
|
+
& pnpm run test:visual
|
|
1131
|
+
if ($global:LASTEXITCODE -eq 0) {
|
|
1132
|
+
Add-Step "Frontend Visual Tests" "SUCCESS"
|
|
1133
|
+
Write-Host "[OK] Frontend Visual Tests completed successfully." -ForegroundColor Green
|
|
1134
|
+
} else {
|
|
1135
|
+
Add-Step "Frontend Visual Tests" "FAILED (Ignored)"
|
|
1136
|
+
Write-Host "[WARN] Frontend Visual Tests failed, but continuing deployment as requested." -ForegroundColor Yellow
|
|
1137
|
+
$global:LASTEXITCODE = 0
|
|
1138
|
+
}
|
|
1139
|
+
} else {
|
|
1140
|
+
Write-Host "[SKIP] Frontend Visual Tests skipped." -ForegroundColor Gray
|
|
1141
|
+
Add-Step "Frontend Visual Tests" "SKIPPED"
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
Invoke-ModernCommand "Frontend Build" { pnpm run build }
|
|
1145
|
+
|
|
1146
|
+
Write-Host "[RESILIENCE] Ensuring hidden public assets (e.g. .well-known) are copied to out..." -ForegroundColor Gray
|
|
1147
|
+
if (Test-Path "public\\.well-known") {
|
|
1148
|
+
if (!(Test-Path "out\\.well-known")) {
|
|
1149
|
+
New-Item -ItemType Directory -Path "out\\.well-known" | Out-Null
|
|
1150
|
+
}
|
|
1151
|
+
try {
|
|
1152
|
+
Copy-Item -Recurse -Force "public\\.well-known\\*" "out\\.well-known" -ErrorAction Stop
|
|
1153
|
+
} catch {
|
|
1154
|
+
Write-Host "[WARN] Could not copy .well-known: $($_.Exception.Message)" -ForegroundColor Yellow
|
|
1155
|
+
}
|
|
1156
|
+
Write-Host "[RESILIENCE] Copied .well-known to out directory." -ForegroundColor Green
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (!(Test-Path "out\\index.html")) { New-Item -Path "out\\index.html" -ItemType File -Force | Out-Null }
|
|
1160
|
+
Invoke-ModernCommand "Mobile App Sync (Capacitor)" { pnpm exec cap sync }
|
|
1161
|
+
# Ensure bash + pnpm are resolvable for @cloudflare/next-on-pages (shellac dependency)
|
|
1162
|
+
$gitBashBin = "C:\\Program Files\\Git\\bin"
|
|
1163
|
+
$pnpmBin1 = "C:\\Program Files\\nodejs"
|
|
1164
|
+
$pnpmBin2 = "$env:LOCALAPPDATA\\pnpm"
|
|
1165
|
+
$originalPath = $env:PATH
|
|
1166
|
+
$env:PATH = "$gitBashBin;$pnpmBin1;$pnpmBin2;$env:PATH"
|
|
1167
|
+
Get-ChildItem -Path "out" -Recurse -Filter "__next*" -File | Remove-Item -Force -ErrorAction SilentlyContinue; Invoke-ModernCommand "Frontend Pages Upload" { pnpm run deploy }
|
|
1168
|
+
$env:PATH = $originalPath
|
|
1169
|
+
} finally {
|
|
1170
|
+
Pop-Location
|
|
1171
|
+
}
|
|
1172
|
+
} else {
|
|
1173
|
+
Write-Host "[SKIP] Frontend Deployment skipped." -ForegroundColor Gray
|
|
1174
|
+
Add-Step "Frontend Deployment" "SKIPPED"
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
# Phase 3.5: Edge Verification
|
|
1178
|
+
if (!$SkipFrontend -and !$SkipRouter) {
|
|
1179
|
+
try {
|
|
1180
|
+
Push-Location frontend
|
|
1181
|
+
|
|
1182
|
+
# PROACTIVE PLAYWRIGHT RESILIENCE: Kill zombies and remove locks
|
|
1183
|
+
Write-Host "[RESILIENCE] Preparing clean state for Playwright..." -ForegroundColor Yellow
|
|
1184
|
+
$pwProcs = Get-WmiObject Win32_Process | Where-Object {
|
|
1185
|
+
$_.Name -match 'node.exe' -and
|
|
1186
|
+
($_.CommandLine -match 'playwright' -or $_.CommandLine -match 'next dev')
|
|
1187
|
+
}
|
|
1188
|
+
foreach ($p in $pwProcs) {
|
|
1189
|
+
Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
|
|
1190
|
+
}
|
|
1191
|
+
$lockFile = Join-Path (Get-Location) ".next\\lock"
|
|
1192
|
+
if (Test-Path $lockFile) {
|
|
1193
|
+
Remove-Item -Force $lockFile -ErrorAction SilentlyContinue
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
Invoke-ModernCommand "Edge Route Verification (Playwright)" { npx playwright test e2e/edge-router.spec.ts }
|
|
1197
|
+
} catch {
|
|
1198
|
+
Write-Host "[WARN] Edge Route Verification failed! Please check Cloudflare Worker logs." -ForegroundColor Red
|
|
1199
|
+
throw $_
|
|
1200
|
+
} finally {
|
|
1201
|
+
Pop-Location
|
|
1202
|
+
}
|
|
1203
|
+
} else {
|
|
1204
|
+
Write-Host "[SKIP] Edge Route Verification skipped (requires Frontend and Router sync)." -ForegroundColor Gray
|
|
1205
|
+
Add-Step "Edge Route Verification" "SKIPPED"
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
# Phase 4: Mobile App Release
|
|
1209
|
+
if (!$SkipApp) {
|
|
1210
|
+
Invoke-ModernCommand "Mobile App Release" { & "$PSScriptRoot\\scripts\\release-mobile.ps1" }
|
|
1211
|
+
} else {
|
|
1212
|
+
Write-Host "[SKIP] Mobile App Release skipped." -ForegroundColor Gray
|
|
1213
|
+
Add-Step "Mobile App Release" "SKIPPED"
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
# Phase 5: Finalize
|
|
1217
|
+
$finalReport = Generate-FinalReport "SUCCESS"
|
|
1218
|
+
Write-Host ""
|
|
1219
|
+
Write-Host "DEPLOYMENT SUCCESSFUL!" -ForegroundColor Green
|
|
1220
|
+
|
|
1221
|
+
# Telegram Deployment Notification
|
|
1222
|
+
Send-Notification -Status "SUCCESS" -Report $finalReport
|
|
1223
|
+
|
|
1224
|
+
# Antigravity Chat Notification
|
|
1225
|
+
Send-AntigravityChat -Status "SUCCESS" -Report $finalReport
|
|
1226
|
+
|
|
1227
|
+
Write-Host $finalReport
|
|
1228
|
+
Write-Host "[CLIPBOARD] Full report copied to your clipboard!" -ForegroundColor Cyan
|
|
1229
|
+
Write-Host ""
|
|
1230
|
+
Write-Host ">>> Deployment localized and verified." -ForegroundColor Cyan
|
|
1231
|
+
|
|
1232
|
+
`);
|
|
1233
|
+
|
|
1234
|
+
// 3. Agent Rules
|
|
1235
|
+
writeFile('AGENTS.md', `
|
|
1236
|
+
# High-Performance Engineering Standards
|
|
1237
|
+
1. **Ponytail, lazy senior dev mode**: The best code is the code never written. YAGNI. Question complex requests, reuse existing standards, prefer deletion over addition, and write the minimal correct code.
|
|
1238
|
+
2. **Edge-First Execution**: Backend logic must be optimized for serverless edge deployment (Cloudflare Workers).
|
|
1239
|
+
3. **Data Layer Optimization**: Never execute untuned queries. Use connection pooling and batching.
|
|
1240
|
+
4. **Minimalist Frontend Render**: Ensure frontend implementations are highly optimized for Next.js, favoring SSR where appropriate, avoiding heavy client bundles, and ensuring mobile-first responsive design.
|
|
1241
|
+
5. **Master Debugging**: Trace issues to their root cause before writing a fix. Use the "Five Whys" protocol.
|
|
1242
|
+
6. **Strict Typing**: TypeScript and Rust types must be explicit, avoid \`any\` or \`unwrap()\` where possible.
|
|
1243
|
+
7. **Structured Logging**: Implement structured logging, avoiding excessive console.logs in production.
|
|
1244
|
+
8. **Atomic Commits**: Ensure commits are small, atomic, and pass CI checks before pushing.
|
|
1245
|
+
9. **Error Boundaries**: Frontend must wrap critical components in Error Boundaries to prevent full app crashes.
|
|
1246
|
+
10. **No Placeholder Code**: Never write generic "TODO" or placeholder logic if the requirements are known.
|
|
1247
|
+
11. **Security Best Practices**: Enforce strict input validation, rate limiting, and secure credential handling by default.
|
|
1248
|
+
12. **Automated Testing & TDD**: Require unit and integration tests for critical paths. Code is not done until it's tested.
|
|
1249
|
+
13. **Strict Linting & Formatting**: Enforce ESLint, Prettier, and Clippy with zero warnings allowed.
|
|
1250
|
+
14. **Accessibility (a11y) First**: Require ARIA labels, semantic HTML, and keyboard navigation for all frontend components.
|
|
1251
|
+
15. **Documentation Standards**: Mandate JSDoc (frontend) and Rustdoc (backend) for all public APIs and reusable components.
|
|
1252
|
+
16. **CI/CD Pipeline Setup**: Ensure GitHub Actions (or similar) are configured for automated checks, tests, and deployments.
|
|
1253
|
+
17. **State Management Rules**: Prefer local state or React Context; enforce strict rules against unnecessary global state.
|
|
1254
|
+
18. **Environment Variable Validation**: Fail fast at startup if required \`.env\` variables are missing.
|
|
1255
|
+
19. **Kill AI Slop**: Remove aesthetic clichΓ©s (indigo gradients, glowing cards, excessive emojis) that serve no functional purpose.
|
|
1256
|
+
20. **Security First Principle**: Treat all external input as malicious; leverage the Security Review skill for all external-facing endpoints.
|
|
1257
|
+
|
|
1258
|
+
# Start Project Protocol
|
|
1259
|
+
When the user says "start project", you MUST immediately:
|
|
1260
|
+
1. Ask the user for the name of the project and a brief description of what they want to build.
|
|
1261
|
+
2. Wait for their response.
|
|
1262
|
+
3. Once they respond, use their details to initialize the Next.js frontend (in \`frontend/\`) and Rust backend (in \`backend/\`), adhering to all High-Performance Engineering Standards.
|
|
1263
|
+
4. Initialize a new git repository in the root directory (\`git init\`) and create an initial commit.
|
|
1264
|
+
`);
|
|
1265
|
+
|
|
1266
|
+
// 4. Workflows
|
|
1267
|
+
writeFile('.agents/workflows/git-it.md', `
|
|
1268
|
+
# Safe Git Sync Workflow
|
|
1269
|
+
1. Stage all changes (\`git add .\`).
|
|
1270
|
+
2. Write a descriptive, conventional commit message based on the recent changes.
|
|
1271
|
+
3. Commit the changes (\`git commit -m "..."\`).
|
|
1272
|
+
4. Pull with rebase (\`git pull --rebase\`).
|
|
1273
|
+
5. Push to the remote branch (\`git push\`).
|
|
1274
|
+
`);
|
|
1275
|
+
|
|
1276
|
+
writeFile('.agents/workflows/deploy-and-talk.md', `
|
|
1277
|
+
# Deploy and Talk Workflow
|
|
1278
|
+
1. Execute the deployment script (\`./deploy.ps1\` or \`./deploy.sh\`).
|
|
1279
|
+
2. Verify successful deployment output.
|
|
1280
|
+
3. Send a Telegram notification using \`Invoke-RestMethod\` to alert the team that the deployment is complete. Include a brief changelog.
|
|
1281
|
+
`);
|
|
1282
|
+
|
|
1283
|
+
// 5. Skills
|
|
1284
|
+
writeFile('.agents/skills/ponytail/SKILL.md', `
|
|
1285
|
+
---
|
|
1286
|
+
name: ponytail
|
|
1287
|
+
description: Enforces lazy senior dev principles. Efficient, not careless. Deletion over addition.
|
|
1288
|
+
---
|
|
1289
|
+
# Ponytail, lazy senior dev mode
|
|
1290
|
+
|
|
1291
|
+
You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
|
|
1292
|
+
|
|
1293
|
+
Before writing any code, stop at the first rung that holds:
|
|
1294
|
+
1. Does this need to be built at all? (YAGNI)
|
|
1295
|
+
2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
|
|
1296
|
+
3. Does the standard library already do this? Use it.
|
|
1297
|
+
4. Does a native platform feature cover it? Use it.
|
|
1298
|
+
5. Does an already-installed dependency solve it? Use it.
|
|
1299
|
+
6. Can this be one line? Make it one line.
|
|
1300
|
+
7. Only then: write the minimum code that works.
|
|
1301
|
+
|
|
1302
|
+
The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
|
|
1303
|
+
|
|
1304
|
+
Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once.
|
|
1305
|
+
|
|
1306
|
+
Rules:
|
|
1307
|
+
- No abstractions that weren't explicitly requested.
|
|
1308
|
+
- No new dependency if it can be avoided.
|
|
1309
|
+
- No boilerplate nobody asked for.
|
|
1310
|
+
- Deletion over addition. Boring over clever. Fewest files possible.
|
|
1311
|
+
- Shortest working diff wins, but only once you understand the problem.
|
|
1312
|
+
- Question complex requests: "Do you actually need X, or does Y cover it?"
|
|
1313
|
+
- Mark deliberate simplifications that cut a real corner with a known ceiling with a \`ponytail:\` comment naming the ceiling and upgrade path.
|
|
1314
|
+
`);
|
|
1315
|
+
|
|
1316
|
+
writeFile('.agents/skills/ui-sniper-telegram/SKILL.md', `
|
|
1317
|
+
---
|
|
1318
|
+
name: ui-sniper-telegram
|
|
1319
|
+
description: Automates the setup of a UI Sniper system integrated with a Telegram bot for capturing frontend UI feedback directly to a Telegram group.
|
|
1320
|
+
---
|
|
1321
|
+
# UI Sniper Telegram Integration Skill
|
|
1322
|
+
1. **Ask for Credentials**: Prompt the user for their \`TELEGRAM_BOT_TOKEN\` and \`TELEGRAM_CHAT_ID\`. Wait for their response.
|
|
1323
|
+
2. **Install**: Install \`ui-sniper\` and scaffold the Next.js API route (\`app/api/ui-sniper-webhook/route.ts\`), and set up the \`UISniperWrapper.tsx\` component.
|
|
1324
|
+
3. **Configure**: Add the wrapper to \`app/layout.tsx\` and place the credentials in \`.env.local\`.
|
|
1325
|
+
`);
|
|
1326
|
+
|
|
1327
|
+
writeFile('.agents/skills/kill-ai-slop/SKILL.md', `
|
|
1328
|
+
---
|
|
1329
|
+
name: kill-ai-slop
|
|
1330
|
+
description: Scans a web project to detect and strip out AI-generated aesthetic clichΓ©s (indigo gradients, excessive emojis, glowing cards) replacing them with clean, tasteful, and accessible design patterns.
|
|
1331
|
+
---
|
|
1332
|
+
|
|
1333
|
+
# Kill AI Slop Skill
|
|
1334
|
+
Run the following command in the terminal to execute the scanner:
|
|
1335
|
+
\`\`\`bash
|
|
1336
|
+
npx --yes skills add yetone/kill-ai-slop
|
|
1337
|
+
\`\`\`
|
|
1338
|
+
Or execute the scanner directly on the project directory:
|
|
1339
|
+
\`\`\`bash
|
|
1340
|
+
npx --yes kill-ai-slop .
|
|
1341
|
+
\`\`\`
|
|
1342
|
+
Review the reported "tells" and apply the suggested clean fixes (e.g., removing indigo gradients, replacing emojis with proper SVG icons, removing glowing cards in favor of paper/ink aesthetics).
|
|
1343
|
+
`);
|
|
1344
|
+
|
|
1345
|
+
writeFile('.agents/skills/security-review/SKILL.md', `
|
|
1346
|
+
---
|
|
1347
|
+
name: security-review
|
|
1348
|
+
description: 'AI-powered codebase security scanner that reasons about code like a security researcher β tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.'
|
|
1349
|
+
---
|
|
1350
|
+
|
|
1351
|
+
# Security Review
|
|
1352
|
+
|
|
1353
|
+
An AI-powered security scanner that reasons about your codebase the way a human security researcher would β tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.
|
|
1354
|
+
|
|
1355
|
+
## Execution Workflow
|
|
1356
|
+
1. **Scope Resolution**: Determine what to scan (specific path or entire project).
|
|
1357
|
+
2. **Dependency Audit**: Audit dependencies first (check package.json, Cargo.toml for known vulnerable packages).
|
|
1358
|
+
3. **Secrets & Exposure Scan**: Scan ALL files for hardcoded API keys, tokens, passwords, and \`.env\` files.
|
|
1359
|
+
4. **Vulnerability Deep Scan**: Check for Injection Flaws, Authentication/Access Control issues, Data Handling weaknesses, and Cryptography issues.
|
|
1360
|
+
5. **Cross-File Data Flow Analysis**: Trace user-controlled input from entry points to dangerous sinks.
|
|
1361
|
+
6. **Self-Verification Pass**: Filter false positives.
|
|
1362
|
+
7. **Generate Security Report**: Output a findings summary table and structured report with severities (CRITICAL, HIGH, MEDIUM, LOW, INFO).
|
|
1363
|
+
8. **Propose Patches**: For every CRITICAL and HIGH finding, generate a concrete patch for human review. **Never auto-apply any patch.**
|
|
1364
|
+
`);
|
|
1365
|
+
|
|
1366
|
+
console.log('β
All skills, workflows, dev scripts, and rules generated successfully.');
|
|
1367
|
+
console.log('');
|
|
1368
|
+
console.log('π¦ Installing additional NPM packages in the current project...');
|
|
1369
|
+
|
|
1370
|
+
try {
|
|
1371
|
+
// If there is a package.json, install UI dependencies
|
|
1372
|
+
if (fs.existsSync('package.json')) {
|
|
1373
|
+
console.log(' - Installing ui-sniper and html2canvas...');
|
|
1374
|
+
execSync('npm install ui-sniper html2canvas', { stdio: 'inherit' });
|
|
1375
|
+
} else {
|
|
1376
|
+
console.log(' - No package.json found. Skipping ui-sniper installation.');
|
|
1377
|
+
}
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
console.warn('β οΈ Could not install npm dependencies automatically.');
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
console.log('');
|
|
1383
|
+
console.log("\nπ Scaffolding Complete! π");
|
|
1384
|
+
console.log("π The workspace is fully loaded with Ponytail, Security Review, and Kill AI Slop skills.");
|
|
1385
|
+
console.log("π Go to your coding agent and say: 'start project' to begin! π");
|