@absolutejs/absolute 0.15.11 → 0.15.13
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/README.md +2 -2
- package/dist/cli/index.js +391 -16
- package/dist/index.js +64 -44
- package/dist/index.js.map +7 -7
- package/dist/src/cli/interactive.d.ts +2 -0
- package/dist/src/cli/scripts/dev.d.ts +1 -0
- package/dist/src/cli/utils.d.ts +11 -0
- package/dist/src/utils/logger.d.ts +22 -13
- package/dist/types/cli.d.ts +17 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
- package/types/cli.ts +20 -0
- package/types/index.ts +1 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Full‑stack, **type‑safe** batteries‑included platform that lets you **serv
|
|
|
4
4
|
|
|
5
5
|
[](https://bun.sh)
|
|
6
6
|
[](https://elysiajs.com)
|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -160,4 +160,4 @@ Pull requests and issues are welcome! Whether it’s a new plugin, framework han
|
|
|
160
160
|
|
|
161
161
|
## License
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
**Business Source License 1.1 (BSL-1.1)** – see [`LICENSE`](./LICENSE) for details.
|
package/dist/cli/index.js
CHANGED
|
@@ -1,10 +1,250 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
|
-
// src/cli/
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
75
|
-
|
|
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;
|