@absolutejs/absolute 0.15.12 → 0.15.14

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/dist/cli/index.js CHANGED
@@ -1,10 +1,250 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
 
4
- // src/cli/index.ts
4
+ // src/cli/scripts/dev.ts
5
+ var {$: $2 } = globalThis.Bun;
6
+ var {env } = globalThis.Bun;
7
+ import { existsSync as existsSync2 } from "fs";
8
+ import { resolve as resolve2 } from "path";
9
+
10
+ // src/constants.ts
11
+ var SECONDS_IN_A_MINUTE = 60;
12
+ var MILLISECONDS_IN_A_SECOND = 1000;
13
+ var MILLISECONDS_IN_A_MINUTE = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE;
14
+ var MINUTES_IN_AN_HOUR = 60;
15
+ var HOURS_IN_DAY = 24;
16
+ var MILLISECONDS_IN_A_DAY = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR * HOURS_IN_DAY;
17
+ var TWO_THIRDS = 2 / 3;
18
+ var DEFAULT_PORT = 3000;
19
+
20
+ // src/cli/interactive.ts
21
+ import { openSync } from "fs";
22
+ import { ReadStream } from "tty";
23
+ var SHORTCUTS = {
24
+ c: "clear",
25
+ h: "help",
26
+ o: "open",
27
+ p: "pause",
28
+ q: "quit",
29
+ r: "restart"
30
+ };
31
+ var WORD_COMMANDS = {
32
+ clear: "clear",
33
+ help: "help",
34
+ open: "open",
35
+ pause: "pause",
36
+ quit: "quit",
37
+ restart: "restart",
38
+ resume: "pause"
39
+ };
40
+ var openTtyStream = () => {
41
+ if (typeof process.stdin.setRawMode === "function") {
42
+ try {
43
+ process.stdin.setRawMode(true);
44
+ return process.stdin;
45
+ } catch {}
46
+ }
47
+ try {
48
+ const ttyStream = new ReadStream(openSync("/dev/tty", "r"));
49
+ ttyStream.setRawMode(true);
50
+ return ttyStream;
51
+ } catch {
52
+ return null;
53
+ }
54
+ };
55
+ var createInteractiveHandler = (actions) => {
56
+ let buffer = "";
57
+ let shellMode = false;
58
+ let needsPrompt = true;
59
+ let escapeSeq = "";
60
+ const history = [];
61
+ let historyIndex = -1;
62
+ const renderLine = (value) => {
63
+ const prefix = shellMode ? "\x1B[33m$ \x1B[0m" : "\x1B[90m> \x1B[0m";
64
+ process.stdout.write(`\r\x1B[2K${prefix}${value}`);
65
+ buffer = value;
66
+ needsPrompt = false;
67
+ };
68
+ const handleLine = async (line) => {
69
+ const trimmed = line.trim();
70
+ if (trimmed === "") {
71
+ needsPrompt = true;
72
+ return;
73
+ }
74
+ if (trimmed === "$") {
75
+ shellMode = true;
76
+ needsPrompt = true;
77
+ return;
78
+ }
79
+ if (trimmed.startsWith("$")) {
80
+ const cmd = trimmed.slice(1).trim();
81
+ if (cmd.length > 0) {
82
+ try {
83
+ await actions.shell(cmd);
84
+ } catch {}
85
+ needsPrompt = true;
86
+ return;
87
+ }
88
+ }
89
+ const wordAction = WORD_COMMANDS[trimmed.toLowerCase()];
90
+ if (wordAction) {
91
+ await actions[wordAction]();
92
+ needsPrompt = true;
93
+ return;
94
+ }
95
+ if (trimmed.length === 1) {
96
+ const shortcutAction = SHORTCUTS[trimmed];
97
+ if (shortcutAction) {
98
+ await actions[shortcutAction]();
99
+ needsPrompt = true;
100
+ return;
101
+ }
102
+ }
103
+ console.log(`\x1B[31mUnknown command: ${trimmed}\x1B[0m (press h + enter for help)`);
104
+ needsPrompt = true;
105
+ };
106
+ const handleShellLine = async (line) => {
107
+ const trimmed = line.trim();
108
+ if (trimmed === "") {
109
+ shellMode = false;
110
+ needsPrompt = true;
111
+ return;
112
+ }
113
+ try {
114
+ await actions.shell(trimmed);
115
+ } catch {}
116
+ shellMode = false;
117
+ needsPrompt = true;
118
+ };
119
+ const handleArrow = (arrow) => {
120
+ if (arrow === "A" && history.length > 0) {
121
+ if (historyIndex < history.length - 1) {
122
+ historyIndex++;
123
+ }
124
+ const entry = history[history.length - 1 - historyIndex];
125
+ if (entry)
126
+ renderLine(entry);
127
+ }
128
+ if (arrow === "B") {
129
+ if (historyIndex > 0) {
130
+ historyIndex--;
131
+ const entry = history[history.length - 1 - historyIndex];
132
+ if (entry)
133
+ renderLine(entry);
134
+ } else {
135
+ historyIndex = -1;
136
+ renderLine("");
137
+ }
138
+ }
139
+ };
140
+ const handleChar = (char) => {
141
+ if (char === "\x03") {
142
+ if (shellMode) {
143
+ shellMode = false;
144
+ buffer = "";
145
+ historyIndex = -1;
146
+ needsPrompt = true;
147
+ process.stdout.write(`
148
+ `);
149
+ } else {
150
+ buffer = "";
151
+ historyIndex = -1;
152
+ process.stdout.write(`
153
+ `);
154
+ actions.quit();
155
+ }
156
+ return;
157
+ }
158
+ if (char === "\x7F" || char === "\b") {
159
+ if (buffer.length > 0) {
160
+ renderLine(buffer.slice(0, -1));
161
+ }
162
+ return;
163
+ }
164
+ if (char === "\r" || char === `
165
+ `) {
166
+ process.stdout.write(`
167
+ `);
168
+ const line = buffer;
169
+ buffer = "";
170
+ historyIndex = -1;
171
+ if (line.trim().length > 0) {
172
+ history.push(line);
173
+ }
174
+ if (shellMode) {
175
+ handleShellLine(line);
176
+ } else {
177
+ handleLine(line);
178
+ }
179
+ return;
180
+ }
181
+ if (char.charCodeAt(0) < 32) {
182
+ return;
183
+ }
184
+ if (needsPrompt) {
185
+ process.stdout.write(`
186
+ `);
187
+ }
188
+ renderLine(buffer + char);
189
+ };
190
+ const onData = (data) => {
191
+ const str = data.toString();
192
+ for (let idx = 0;idx < str.length; idx++) {
193
+ const char = str.charAt(idx);
194
+ if (escapeSeq.length > 0) {
195
+ escapeSeq += char;
196
+ if (escapeSeq.length === 2) {
197
+ if (char !== "[") {
198
+ escapeSeq = "";
199
+ }
200
+ continue;
201
+ }
202
+ if (escapeSeq.length === 3) {
203
+ handleArrow(char);
204
+ escapeSeq = "";
205
+ continue;
206
+ }
207
+ continue;
208
+ }
209
+ if (char === "\x1B") {
210
+ escapeSeq = "\x1B";
211
+ continue;
212
+ }
213
+ handleChar(char);
214
+ }
215
+ };
216
+ const ttyStream = openTtyStream();
217
+ const input = ttyStream ?? process.stdin;
218
+ input.resume();
219
+ input.on("data", onData);
220
+ const dispose = () => {
221
+ input.removeListener("data", onData);
222
+ if (ttyStream) {
223
+ try {
224
+ ttyStream.setRawMode(false);
225
+ } catch {}
226
+ }
227
+ if (ttyStream && ttyStream !== process.stdin) {
228
+ ttyStream.destroy();
229
+ }
230
+ process.stdin.pause();
231
+ };
232
+ return { dispose };
233
+ };
234
+
235
+ // src/cli/utils.ts
5
236
  var {$ } = globalThis.Bun;
6
- import { existsSync } from "fs";
237
+ import { execSync } from "child_process";
238
+ import { existsSync, readFileSync } from "fs";
7
239
  import { resolve } from "path";
240
+ var isWSLEnvironment = () => {
241
+ try {
242
+ const release = readFileSync("/proc/version", "utf-8");
243
+ return /microsoft|wsl/i.test(release);
244
+ } catch {
245
+ return false;
246
+ }
247
+ };
8
248
  var COMPOSE_PATH = "db/docker-compose.db.yml";
9
249
  var DEFAULT_SERVER_ENTRY = "src/backend/server.ts";
10
250
  var readDbScripts = async () => {
@@ -38,27 +278,88 @@ var stopDatabase = async (scripts) => {
38
278
  Stopping database container...`);
39
279
  await $`${{ raw: scripts.downCommand }}`.quiet().nothrow();
40
280
  };
281
+ var printHelp = () => {
282
+ console.log("");
283
+ console.log("\x1B[1mShortcuts:\x1B[0m");
284
+ console.log(" \x1B[36mr\x1B[0m / restart \u2014 Restart server");
285
+ console.log(" \x1B[36mp\x1B[0m / pause \u2014 Pause/resume server");
286
+ console.log(" \x1B[36mo\x1B[0m / open \u2014 Open in browser");
287
+ console.log(" \x1B[36mc\x1B[0m / clear \u2014 Clear terminal");
288
+ console.log(" \x1B[36mq\x1B[0m / quit \u2014 Graceful shutdown");
289
+ console.log(" \x1B[36mh\x1B[0m / help \u2014 Show this help");
290
+ console.log(" \x1B[36m$\x1B[0m \u2014 Run a shell command");
291
+ console.log(" \x1B[36m\u2191\x1B[0m / \x1B[36m\u2193\x1B[0m \u2014 Command history");
292
+ console.log("");
293
+ };
294
+ var printHint = () => {
295
+ console.log("\x1B[90mpress h + enter to show shortcuts\x1B[0m");
296
+ };
297
+ var killStaleProcesses = (port) => {
298
+ try {
299
+ const output = execSync(`lsof -ti tcp:${port} -sTCP:LISTEN 2>/dev/null`, { encoding: "utf-8" }).trim();
300
+ if (!output)
301
+ return;
302
+ const pids = output.split(`
303
+ `).map(Number).filter((pid) => pid !== process.pid && pid > 0);
304
+ if (pids.length === 0)
305
+ return;
306
+ for (const pid of pids) {
307
+ try {
308
+ process.kill(pid, "SIGTERM");
309
+ } catch {}
310
+ }
311
+ console.log(`\x1B[33m[cli] Killed ${pids.length} stale process(es) on port ${port}.\x1B[0m`);
312
+ } catch {}
313
+ };
314
+
315
+ // src/cli/scripts/dev.ts
41
316
  var dev = async (serverEntry) => {
42
- const usesDocker = existsSync(resolve(COMPOSE_PATH));
317
+ const port = Number(env.PORT) || DEFAULT_PORT;
318
+ killStaleProcesses(port);
319
+ const usesDocker = existsSync2(resolve2(COMPOSE_PATH));
43
320
  const scripts = usesDocker ? await readDbScripts() : null;
44
321
  if (scripts)
45
322
  await startDatabase(scripts);
46
- const spawnServer = () => Bun.spawn(["bun", "--hot", "--no-clear-screen", serverEntry], {
47
- cwd: process.cwd(),
48
- env: {
49
- ...process.env,
50
- NODE_ENV: "development"
51
- },
52
- stdin: "inherit",
53
- stdout: "inherit",
54
- stderr: "inherit"
55
- });
56
- let serverProcess = spawnServer();
323
+ let paused = false;
57
324
  let cleaning = false;
325
+ let interactive = null;
326
+ const spawnServer = () => {
327
+ const proc = Bun.spawn(["bun", "--hot", "--no-clear-screen", serverEntry], {
328
+ cwd: process.cwd(),
329
+ env: {
330
+ ...process.env,
331
+ FORCE_COLOR: "1",
332
+ NODE_ENV: "development"
333
+ },
334
+ stdin: "ignore",
335
+ stdout: "pipe",
336
+ stderr: "pipe"
337
+ });
338
+ const forward = (stream, dest) => {
339
+ const reader = stream.getReader();
340
+ const pump = () => {
341
+ reader.read().then(({ done, value }) => {
342
+ if (done)
343
+ return;
344
+ dest.write(value);
345
+ pump();
346
+ }).catch(() => {});
347
+ };
348
+ pump();
349
+ };
350
+ forward(proc.stdout, process.stdout);
351
+ forward(proc.stderr, process.stderr);
352
+ return proc;
353
+ };
354
+ let serverProcess = spawnServer();
58
355
  const cleanup = async (exitCode = 0) => {
59
356
  if (cleaning)
60
357
  return;
61
358
  cleaning = true;
359
+ if (interactive)
360
+ interactive.dispose();
361
+ if (paused)
362
+ sendSignal("SIGCONT");
62
363
  try {
63
364
  serverProcess.kill();
64
365
  } catch {}
@@ -67,12 +368,84 @@ var dev = async (serverEntry) => {
67
368
  await stopDatabase(scripts);
68
369
  process.exit(exitCode);
69
370
  };
371
+ const restartServer = async () => {
372
+ console.log("\x1B[36m[cli] Restarting server...\x1B[0m");
373
+ const old = serverProcess;
374
+ if (paused) {
375
+ sendSignal("SIGCONT");
376
+ paused = false;
377
+ }
378
+ try {
379
+ old.kill();
380
+ } catch {}
381
+ serverProcess = spawnServer();
382
+ await old.exited;
383
+ console.log("\x1B[32m[cli] Server restarted.\x1B[0m");
384
+ };
385
+ const sendSignal = (signal) => {
386
+ try {
387
+ process.kill(-serverProcess.pid, signal);
388
+ } catch {
389
+ try {
390
+ process.kill(serverProcess.pid, signal);
391
+ } catch {}
392
+ }
393
+ };
394
+ const togglePause = () => {
395
+ if (paused) {
396
+ sendSignal("SIGCONT");
397
+ paused = false;
398
+ console.log("\x1B[32m[cli] Server resumed.\x1B[0m");
399
+ } else {
400
+ sendSignal("SIGSTOP");
401
+ paused = true;
402
+ console.log("\x1B[33m[cli] Server paused.\x1B[0m \x1B[90m[paused]\x1B[0m");
403
+ }
404
+ };
405
+ const runShellCommand = async (command) => {
406
+ await $2`${{ raw: command }}`.env({ ...process.env, FORCE_COLOR: "1" }).nothrow();
407
+ };
408
+ const openInBrowser = async () => {
409
+ const url = `http://localhost:${port}`;
410
+ const { platform } = process;
411
+ const isWSL = platform === "linux" && isWSLEnvironment();
412
+ const cmd = isWSL ? "cmd.exe" : platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
413
+ const args = isWSL ? ["/c", "start", url] : [url];
414
+ try {
415
+ Bun.spawn([cmd, ...args], {
416
+ stdout: "ignore",
417
+ stderr: "ignore"
418
+ });
419
+ console.log(`\x1B[36m[cli] Opening ${url}\x1B[0m`);
420
+ } catch {
421
+ console.log(`\x1B[33m[cli] Could not open browser. Visit ${url}\x1B[0m`);
422
+ }
423
+ };
424
+ interactive = createInteractiveHandler({
425
+ clear: () => {
426
+ process.stdout.write("\x1Bc");
427
+ },
428
+ help: () => {
429
+ printHelp();
430
+ },
431
+ open: () => openInBrowser(),
432
+ pause: () => {
433
+ togglePause();
434
+ },
435
+ quit: () => {
436
+ cleanup(0);
437
+ },
438
+ restart: () => restartServer(),
439
+ shell: runShellCommand
440
+ });
70
441
  process.on("SIGINT", () => cleanup(0));
71
442
  process.on("SIGTERM", () => cleanup(0));
443
+ printHint();
72
444
  const monitorServer = async () => {
73
445
  while (!cleaning) {
74
- const exitCode = await serverProcess.exited;
75
- if (cleaning)
446
+ const current = serverProcess;
447
+ const exitCode = await current.exited;
448
+ if (cleaning || serverProcess !== current)
76
449
  continue;
77
450
  if (exitCode === 130 || exitCode === 143) {
78
451
  await cleanup(0);
@@ -84,6 +457,8 @@ var dev = async (serverEntry) => {
84
457
  };
85
458
  await monitorServer();
86
459
  };
460
+
461
+ // src/cli/index.ts
87
462
  var command = process.argv[2];
88
463
  if (command === "dev") {
89
464
  const serverEntry = process.argv[3] ?? DEFAULT_SERVER_ENTRY;