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/dev.sh ADDED
@@ -0,0 +1,359 @@
1
+ #!/usr/bin/env bash
2
+ # =============================================
3
+ # Medizys Local Development Runner
4
+ # =============================================
5
+ # Starts the Worker and Next.js frontend
6
+ # using concurrently in a single terminal window.
7
+
8
+ echo ""
9
+ echo -e "\e[36m=============================================\e[0m"
10
+ echo -e "\e[36m ๐Ÿš€ Medizys Local Dev Server\e[0m"
11
+ echo -e "\e[36m=============================================\e[0m"
12
+ echo ""
13
+ echo -e "\e[32m Backend โ†’ http://localhost:8787\e[0m"
14
+ echo -e "\e[34m Frontend โ†’ http://localhost:3000\e[0m"
15
+ echo ""
16
+ echo -e "\e[33m Starting unified development environment...\e[0m"
17
+ echo -e "\e[36m=============================================\e[0m"
18
+ echo ""
19
+
20
+ # Ensure Node 22 is used if nvm is available
21
+ if [ -s "$HOME/.nvm/nvm.sh" ]; then
22
+ export NVM_DIR="$HOME/.nvm"
23
+ source "$NVM_DIR/nvm.sh"
24
+ nvm use 22 > /dev/null 2>&1
25
+ fi
26
+
27
+ # Cleanup zombie worker processes and ports to prevent conflicts
28
+ echo -e "\e[90m Performing aggressive process cleanup...\e[0m"
29
+
30
+ # Define processes to kill
31
+ for procName in workerd node concurrently; do
32
+ if pgrep -x "$procName" > /dev/null; then
33
+ echo -e "\e[33m Stopping $procName...\e[0m"
34
+ pkill -f "$procName" 2>/dev/null
35
+ fi
36
+ done
37
+
38
+ # Active verification loop to ensure all target processes have fully exited
39
+ maxWaitSeconds=5
40
+ start=$(date +%s)
41
+ while [ $(( $(date +%s) - start )) -lt $maxWaitSeconds ]; do
42
+ if ! pgrep -f "workerd|concurrently" > /dev/null; then
43
+ break
44
+ fi
45
+ sleep 0.2
46
+ done
47
+
48
+ # Function to robustly remove a directory with retry logic
49
+ remove_cache_directory() {
50
+ local path=$1
51
+ local label=$2
52
+ if [ -d "$path" ]; then
53
+ echo -e "\e[90m Purging $label cache ($path)...\e[0m"
54
+ rm -rf "$path"
55
+ fi
56
+ }
57
+
58
+ # Hard clear of wrangler state and Next.js cache
59
+ remove_cache_directory "worker/.wrangler" "wrangler state"
60
+ remove_cache_directory "frontend/.next" "frontend Turbopack cache"
61
+
62
+ for port in 8787 3000; do
63
+ echo -e "\e[90m Ensuring port $port is free...\e[0m"
64
+ retryCount=0
65
+ while [ $retryCount -lt 5 ]; do
66
+ targetPid=$(lsof -ti :$port)
67
+ if [ ! -z "$targetPid" ]; then
68
+ echo -e "\e[31m Port $port occupied by PID $targetPid. Terminating...\e[0m"
69
+ kill -9 $targetPid 2>/dev/null
70
+ sleep 1
71
+ retryCount=$((retryCount+1))
72
+ else
73
+ break
74
+ fi
75
+ done
76
+ done
77
+
78
+ # Disable wrangler usage metrics prompt
79
+ export WRANGLER_SEND_METRICS="false"
80
+ # Limit Node.js memory
81
+ export NODE_OPTIONS="--max-old-space-size=4096"
82
+
83
+ echo -e "\e[32mStarting development environment...\e[0m"
84
+
85
+ runnerPath="/tmp/mediqueue-dev-runner.js"
86
+
87
+ cat << 'EOF' > "$runnerPath"
88
+ const { spawn, execSync } = require('child_process');
89
+ let buffer = '';
90
+ let workerChild;
91
+ let frontendChild;
92
+ let lastRestartTime = 0;
93
+ const startTime = Date.now();
94
+
95
+ function formatSize(bytes) {
96
+ if (bytes < 1024) return bytes + 'B';
97
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
98
+ return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
99
+ }
100
+
101
+ function formatUptime() {
102
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
103
+ const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
104
+ const s = (elapsed % 60).toString().padStart(2, '0');
105
+ const h = Math.floor(elapsed / 3600);
106
+ if (h > 0) return `${h}h ${m}m ${s}s`;
107
+ return `${m}m ${s}s`;
108
+ }
109
+
110
+ let lastRows = 0;
111
+ let lastCols = 0;
112
+
113
+ function updateLegend() {
114
+ if (!process.stdout.isTTY) return;
115
+ const rows = process.stdout.rows || 30;
116
+ const cols = process.stdout.columns || 80;
117
+
118
+ // Set scrolling region only if changed (DECSTBM homes the cursor to 1,1!)
119
+ if (rows !== lastRows || cols !== lastCols) {
120
+ process.stdout.write(`\x1b[1;${rows - 2}r`);
121
+ lastRows = rows;
122
+ lastCols = cols;
123
+ }
124
+
125
+ const lineCount = (buffer.match(/\n/g) || []).length;
126
+ const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
127
+ const uptimeStr = formatUptime();
128
+ const legend1 = ` [Copy] 'c': All (${lineCount}L / ${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors `;
129
+ const legend2 = ` [System] Uptime: ${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit `;
130
+
131
+ // Draw legend
132
+ process.stdout.write(`\x1b[s`); // save cursor
133
+ process.stdout.write(`\x1b[${rows - 1};1H\x1b[42m\x1b[30m${legend1.padEnd(cols)}\x1b[0m`);
134
+ process.stdout.write(`\x1b[${rows};1H\x1b[42m\x1b[30m${legend2.padEnd(cols)}\x1b[0m`);
135
+ process.stdout.write(`\x1b[u`); // restore cursor
136
+ }
137
+
138
+ let legendInterval;
139
+ function startLegendUpdater() {
140
+ if (legendInterval) clearInterval(legendInterval);
141
+ legendInterval = setInterval(() => {
142
+ updateLegend();
143
+ }, 1000);
144
+ }
145
+
146
+ function cleanupLegend() {
147
+ if (legendInterval) clearInterval(legendInterval);
148
+ if (!process.stdout.isTTY) return;
149
+ process.stdout.write('\x1b[r'); // reset scrolling region
150
+ }
151
+
152
+ process.stdout.on('resize', () => {
153
+ if (!process.stdout.isTTY) return;
154
+ cleanupLegend();
155
+ console.clear();
156
+ lastRows = 0;
157
+ updateLegend();
158
+ });
159
+
160
+ function extractErrors(text) {
161
+ const lines = text.split('\n');
162
+ const errorPattern = /(error|exception|fail|โจฏ|โœ—|traceback)/i;
163
+ let inErrorBlock = false;
164
+ let blockStart = 0;
165
+ const blocks = [];
166
+
167
+ for (let i = 0; i < lines.length; i++) {
168
+ if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
169
+ if (!inErrorBlock) {
170
+ inErrorBlock = true;
171
+ blockStart = Math.max(0, i - 2);
172
+ }
173
+ } else {
174
+ if (inErrorBlock) {
175
+ if (lines[i].trim() === '') continue;
176
+ const blockEnd = Math.min(lines.length - 1, i + 2);
177
+ blocks.push(lines.slice(blockStart, blockEnd + 1).join('\n'));
178
+ inErrorBlock = false;
179
+ }
180
+ }
181
+ }
182
+ if (inErrorBlock) {
183
+ blocks.push(lines.slice(blockStart).join('\n'));
184
+ }
185
+
186
+ return blocks.length > 0 ? blocks.join('\n\n--- [Next Error Block] ---\n\n') : '';
187
+ }
188
+
189
+ function formatPrefix(isError, source) {
190
+ const d = new Date();
191
+ const ts = String(d.getHours()).padStart(2, '0') + ':' +
192
+ String(d.getMinutes()).padStart(2, '0') + ':' +
193
+ String(d.getSeconds()).padStart(2, '0');
194
+
195
+ const color = source === 'worker' ? '\x1b[36m' : '\x1b[35m';
196
+ const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
197
+ return `\x1b[90m[${ts}]\x1b[0m ${color}${tag}\x1b[0m `;
198
+ }
199
+
200
+ function createStreamHandler(isError, source) {
201
+ let remainder = '';
202
+ return (d) => {
203
+ const chunk = remainder + d.toString();
204
+ const lines = chunk.split('\n');
205
+ remainder = lines.pop();
206
+ for (let line of lines) {
207
+ line = line.replace(/\x1b\[[23]J/g, '').replace(/\x1b\[[0-9;]*H/g, '').replace(/\x1bc/g, '');
208
+ line = line.replace(/\x1b\[[0-9]*[A-G]/g, '').replace(/\x1b\[[0-9]*[K]/g, '');
209
+ line = line.replace(/\r/g, '');
210
+ line = line.replace(/^\[[^\]]+\]\s*/, '');
211
+
212
+ if (line.trim() === '') continue;
213
+
214
+ const prefix = formatPrefix(isError, source);
215
+ const out = prefix + line + '\n';
216
+
217
+ if (isError) process.stderr.write(out);
218
+ else process.stdout.write(out);
219
+ buffer += out;
220
+ }
221
+ if (buffer.length > 1000000) {
222
+ const idx = buffer.indexOf('\n', buffer.length - 500000);
223
+ buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
224
+ }
225
+ };
226
+ }
227
+
228
+ function startWorker() {
229
+ workerChild = spawn('npx', ['concurrently', '-c', 'cyan', '-n', 'worker', '"npm run dev --prefix worker"'], {
230
+ env: { ...process.env, FORCE_COLOR: '1' },
231
+ stdio: ['ignore', 'pipe', 'pipe'],
232
+ shell: true
233
+ });
234
+ workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
235
+ workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
236
+ }
237
+
238
+ function startFrontend() {
239
+ frontendChild = spawn('npx', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"npm run dev --prefix frontend"'], {
240
+ env: { ...process.env, FORCE_COLOR: '1' },
241
+ stdio: ['ignore', 'pipe', 'pipe'],
242
+ shell: true
243
+ });
244
+ frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
245
+ frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
246
+ }
247
+
248
+ function startServices() {
249
+ startWorker();
250
+ startFrontend();
251
+ }
252
+
253
+ console.clear();
254
+ updateLegend();
255
+ startLegendUpdater();
256
+ startServices();
257
+
258
+ function copyToClipboard(text) {
259
+ const platform = process.platform;
260
+ let cmd = '';
261
+ if (platform === 'darwin') cmd = 'pbcopy';
262
+ else if (platform === 'win32') cmd = 'clip';
263
+ else cmd = 'xclip -selection clipboard';
264
+ try {
265
+ execSync(cmd, { input: text });
266
+ return true;
267
+ } catch (e) {
268
+ return false;
269
+ }
270
+ }
271
+
272
+ if (process.stdin.isTTY) {
273
+ process.stdin.setRawMode(true);
274
+ process.stdin.resume();
275
+ process.stdin.setEncoding('utf8');
276
+ process.stdin.on('data', k => {
277
+ const now = Date.now();
278
+ if (k === 'c' || k === 'C') {
279
+ const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
280
+ if (!clean.trim()) {
281
+ console.log('\n\x1b[33mโš ๏ธ Buffer is empty.\x1b[0m\n');
282
+ return;
283
+ }
284
+ if (copyToClipboard(clean)) console.log('\n\x1b[32m๐Ÿ“‹ Copied ALL logs to clipboard!\x1b[0m\n');
285
+ else console.log('\n\x1b[31mโš ๏ธ Failed to copy. Ensure xclip/pbcopy is installed.\x1b[0m\n');
286
+ } else if (k === 's' || k === 'S') {
287
+ const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
288
+ const shortLines = clean.split('\n').slice(-500).join('\n');
289
+ if (copyToClipboard(shortLines)) console.log('\n\x1b[32m๐Ÿ“‹ Copied last 500 lines to clipboard!\x1b[0m\n');
290
+ else console.log('\n\x1b[31mโš ๏ธ Failed to copy.\x1b[0m\n');
291
+ } else if (k === 'w' || k === 'W') {
292
+ const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
293
+ const workerLogs = clean.split('\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\n');
294
+ if (copyToClipboard(workerLogs)) console.log('\n\x1b[32m๐Ÿ“‹ Copied BACKEND logs to clipboard!\x1b[0m\n');
295
+ else console.log('\n\x1b[31mโš ๏ธ Failed to copy.\x1b[0m\n');
296
+ } else if (k === 'f' || k === 'F') {
297
+ const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
298
+ const frontendLogs = clean.split('\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\n');
299
+ if (copyToClipboard(frontendLogs)) console.log('\n\x1b[32m๐Ÿ“‹ Copied FRONTEND logs to clipboard!\x1b[0m\n');
300
+ else console.log('\n\x1b[31mโš ๏ธ Failed to copy.\x1b[0m\n');
301
+ } else if (k === 'e' || k === 'E') {
302
+ const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
303
+ const errors = extractErrors(clean);
304
+ if (errors) {
305
+ if (copyToClipboard(errors)) console.log('\n\x1b[31m๐Ÿšจ Errors copied to clipboard!\x1b[0m\n');
306
+ else console.log('\n\x1b[31mโš ๏ธ Failed to copy errors.\x1b[0m\n');
307
+ } else {
308
+ console.log('\n\x1b[32mโœ… No errors detected.\x1b[0m\n');
309
+ }
310
+ } else if (k === 'r' || k === 'R') {
311
+ if (now - lastRestartTime < 1000) return;
312
+ lastRestartTime = now;
313
+ console.log('\n\x1b[33m๐Ÿ”„ Restarting ALL services...\x1b[0m\n');
314
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
315
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
316
+ startServices();
317
+ } else if (k === '1') {
318
+ if (now - lastRestartTime < 1000) return;
319
+ lastRestartTime = now;
320
+ console.log('\n\x1b[33m๐Ÿ”„ Restarting FRONTEND...\x1b[0m\n');
321
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
322
+ startFrontend();
323
+ } else if (k === '2') {
324
+ if (now - lastRestartTime < 1000) return;
325
+ lastRestartTime = now;
326
+ console.log('\n\x1b[33m๐Ÿ”„ Restarting BACKEND...\x1b[0m\n');
327
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
328
+ startWorker();
329
+ } else if (k === 'x' || k === 'X') {
330
+ console.clear();
331
+ buffer = '';
332
+ lastRows = 0;
333
+ updateLegend();
334
+ console.log('\x1b[32m๐Ÿงน Terminal cleared.\x1b[0m\n');
335
+ } else if (k === '\u0003' || k === 'q' || k === 'Q') {
336
+ console.log('\n\x1b[33mStopping services...\x1b[0m');
337
+ cleanupLegend();
338
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
339
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
340
+ process.exit();
341
+ }
342
+ });
343
+ }
344
+
345
+ // Cleanup on unexpected exits
346
+ process.on('SIGINT', () => {
347
+ cleanupLegend();
348
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
349
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
350
+ process.exit();
351
+ });
352
+ process.on('exit', () => {
353
+ cleanupLegend();
354
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
355
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
356
+ });
357
+ EOF
358
+
359
+ node "$runnerPath"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haraps",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
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": {