vibeman 0.0.6 → 0.0.7
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/api.js +28 -34
- package/dist/index.js +237 -0
- package/dist/ui/assets/index-C_kQPI1m.js +9 -0
- package/dist/ui/index.html +5 -3
- package/package.json +7 -4
- package/dist/cli.js +0 -135
- package/dist/ui/index-gnk6rhxs.js +0 -9
- package/dist/ui/index.js +0 -2
package/dist/api.js
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
|
-
// @bun
|
|
2
1
|
// ../api/src/index.ts
|
|
3
|
-
import { readdir } from "fs/promises";
|
|
4
|
-
import {
|
|
5
|
-
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { resolve, normalize } from "node:path";
|
|
5
|
+
var port = Number(process.env.API_PORT ?? 6970);
|
|
6
6
|
var startedAt = Date.now();
|
|
7
7
|
var root = process.cwd();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const message = error instanceof Error ? error.message : "Unable to read directory";
|
|
22
|
-
return json({ error: message }, 400);
|
|
23
|
-
}
|
|
8
|
+
var server = createServer(async (req, res) => {
|
|
9
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
10
|
+
if (url.pathname === "/api/health") {
|
|
11
|
+
return sendJson(res, { ok: true, uptimeMs: Date.now() - startedAt, root });
|
|
12
|
+
}
|
|
13
|
+
if (url.pathname === "/api/files") {
|
|
14
|
+
const rawPath = url.searchParams.get("path") ?? ".";
|
|
15
|
+
try {
|
|
16
|
+
const entries = await listFiles(rawPath);
|
|
17
|
+
return sendJson(res, { path: rawPath, entries });
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const message = error instanceof Error ? error.message : "Unable to read directory";
|
|
20
|
+
return sendJson(res, { error: message }, 400);
|
|
24
21
|
}
|
|
25
|
-
return new Response("Not found", { status: 404 });
|
|
26
|
-
},
|
|
27
|
-
development: {
|
|
28
|
-
hmr: true,
|
|
29
|
-
console: true
|
|
30
22
|
}
|
|
23
|
+
res.statusCode = 404;
|
|
24
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
25
|
+
res.end("Not found");
|
|
26
|
+
});
|
|
27
|
+
server.listen(port, () => {
|
|
28
|
+
console.log(`API ready on http://localhost:${port}`);
|
|
31
29
|
});
|
|
32
|
-
console.log(`API ready on http://localhost:${port}`);
|
|
33
30
|
async function listFiles(pathInput) {
|
|
34
31
|
const target = normalize(resolve(root, pathInput));
|
|
35
32
|
const entries = await readdir(target, { withFileTypes: true });
|
|
@@ -38,12 +35,9 @@ async function listFiles(pathInput) {
|
|
|
38
35
|
kind: entry.isDirectory() ? "dir" : entry.isFile() ? "file" : "other"
|
|
39
36
|
}));
|
|
40
37
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"cache-control": "no-store"
|
|
47
|
-
}
|
|
48
|
-
});
|
|
38
|
+
function sendJson(res, payload, status = 200) {
|
|
39
|
+
res.statusCode = status;
|
|
40
|
+
res.setHeader("content-type", "application/json");
|
|
41
|
+
res.setHeader("cache-control", "no-store");
|
|
42
|
+
res.end(JSON.stringify(payload));
|
|
49
43
|
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { createReadStream, existsSync } from "node:fs";
|
|
6
|
+
import { stat } from "node:fs/promises";
|
|
7
|
+
import { createServer, request } from "node:http";
|
|
8
|
+
import { request as requestSecure } from "node:https";
|
|
9
|
+
import { basename, dirname, extname, normalize, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
var args = process.argv.slice(2);
|
|
12
|
+
var command = args[0];
|
|
13
|
+
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
14
|
+
printHelp();
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
if (command !== "start") {
|
|
18
|
+
console.error(`Unknown command: ${command}`);
|
|
19
|
+
printHelp();
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
var options = parseStartArgs(args.slice(1));
|
|
23
|
+
await startApps(options);
|
|
24
|
+
function parseStartArgs(argv) {
|
|
25
|
+
const output = {
|
|
26
|
+
path: ".",
|
|
27
|
+
apiPort: 6970,
|
|
28
|
+
uiPort: 6969,
|
|
29
|
+
open: true
|
|
30
|
+
};
|
|
31
|
+
for (let i = 0;i < argv.length; i += 1) {
|
|
32
|
+
const arg = argv[i];
|
|
33
|
+
if (!arg)
|
|
34
|
+
continue;
|
|
35
|
+
if (arg === "--port") {
|
|
36
|
+
const mainPort = Number(argv[i + 1]);
|
|
37
|
+
output.uiPort = mainPort;
|
|
38
|
+
output.apiPort = mainPort + 1;
|
|
39
|
+
i += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (arg === "--no-open") {
|
|
43
|
+
output.open = false;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!arg.startsWith("-") && output.path === ".") {
|
|
47
|
+
output.path = arg;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return output;
|
|
51
|
+
}
|
|
52
|
+
async function startApps(options2) {
|
|
53
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
54
|
+
const distRoot = resolveDistRoot(moduleDir);
|
|
55
|
+
const distApi = distRoot ? resolve(distRoot, "api.js") : "";
|
|
56
|
+
const distUiRoot = distRoot ? resolve(distRoot, "ui") : "";
|
|
57
|
+
const nodePath = resolveNodePath();
|
|
58
|
+
if (!distRoot || !existsSync(distApi) || !existsSync(resolve(distUiRoot, "index.html"))) {
|
|
59
|
+
console.error("Missing dist runtime files. Run the build first.");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const apiProcess = spawn(nodePath, [distApi], {
|
|
63
|
+
cwd: process.cwd(),
|
|
64
|
+
env: {
|
|
65
|
+
...process.env,
|
|
66
|
+
API_PORT: String(options2.apiPort)
|
|
67
|
+
},
|
|
68
|
+
stdio: "inherit"
|
|
69
|
+
});
|
|
70
|
+
const uiServer = createUiServer({
|
|
71
|
+
apiBase: `http://localhost:${options2.apiPort}`,
|
|
72
|
+
uiRoot: distUiRoot
|
|
73
|
+
});
|
|
74
|
+
uiServer.listen(options2.uiPort, () => {
|
|
75
|
+
console.log(`UI ready on http://localhost:${options2.uiPort}`);
|
|
76
|
+
});
|
|
77
|
+
const url = new URL(`http://localhost:${options2.uiPort}/`);
|
|
78
|
+
url.searchParams.set("path", options2.path);
|
|
79
|
+
if (options2.open) {
|
|
80
|
+
await openBrowser(url.toString());
|
|
81
|
+
}
|
|
82
|
+
console.log(`
|
|
83
|
+
Vibeman running:`);
|
|
84
|
+
console.log(` API: http://localhost:${options2.apiPort}`);
|
|
85
|
+
console.log(` UI: ${url.toString()}`);
|
|
86
|
+
console.log("Press Ctrl+C to stop.");
|
|
87
|
+
const stop = () => {
|
|
88
|
+
apiProcess.kill();
|
|
89
|
+
uiServer.close();
|
|
90
|
+
};
|
|
91
|
+
process.on("SIGINT", () => {
|
|
92
|
+
stop();
|
|
93
|
+
process.exit(0);
|
|
94
|
+
});
|
|
95
|
+
process.on("SIGTERM", () => {
|
|
96
|
+
stop();
|
|
97
|
+
process.exit(0);
|
|
98
|
+
});
|
|
99
|
+
const exitCode = await waitForExit(apiProcess);
|
|
100
|
+
stop();
|
|
101
|
+
process.exit(exitCode ?? 1);
|
|
102
|
+
}
|
|
103
|
+
async function openBrowser(target) {
|
|
104
|
+
const platform = process.platform;
|
|
105
|
+
if (platform === "darwin") {
|
|
106
|
+
spawn("open", [target], { stdio: "ignore", detached: true }).unref();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (platform === "win32") {
|
|
110
|
+
spawn("cmd", ["/c", "start", target], { stdio: "ignore", detached: true }).unref();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
spawn("xdg-open", [target], { stdio: "ignore", detached: true }).unref();
|
|
114
|
+
}
|
|
115
|
+
function printHelp() {
|
|
116
|
+
console.log(`vibeman CLI
|
|
117
|
+
|
|
118
|
+
Usage:
|
|
119
|
+
vibeman start [path] [--port <port>] [--no-open]
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
vibeman start .
|
|
123
|
+
vibeman start ./notes --port 7010
|
|
124
|
+
`);
|
|
125
|
+
}
|
|
126
|
+
function waitForExit(child) {
|
|
127
|
+
return new Promise((resolvePromise) => {
|
|
128
|
+
child.once("exit", (code) => resolvePromise(code));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function resolveDistRoot(selfDir) {
|
|
132
|
+
const execDir = dirname(process.execPath);
|
|
133
|
+
const candidates = [
|
|
134
|
+
selfDir,
|
|
135
|
+
resolve(execDir, ".."),
|
|
136
|
+
resolve(execDir, "../.."),
|
|
137
|
+
resolve(selfDir, ".."),
|
|
138
|
+
resolve(selfDir, "../..")
|
|
139
|
+
];
|
|
140
|
+
for (const candidate of candidates) {
|
|
141
|
+
if (existsSync(resolve(candidate, "api.js")) && existsSync(resolve(candidate, "ui", "index.html"))) {
|
|
142
|
+
return candidate;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
function resolveNodePath() {
|
|
148
|
+
const execBase = basename(process.execPath);
|
|
149
|
+
return execBase === "node" || execBase === "node.exe" ? process.execPath : "node";
|
|
150
|
+
}
|
|
151
|
+
function createUiServer({ apiBase, uiRoot }) {
|
|
152
|
+
return createServer(async (req, res) => {
|
|
153
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
154
|
+
if (url.pathname.startsWith("/api")) {
|
|
155
|
+
proxyApi(req, res, url, apiBase);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const filePath = resolveStaticPath(uiRoot, url.pathname);
|
|
160
|
+
const fileStat = await stat(filePath);
|
|
161
|
+
if (fileStat.isDirectory()) {
|
|
162
|
+
return serveFile(resolve(filePath, "index.html"), res);
|
|
163
|
+
}
|
|
164
|
+
return serveFile(filePath, res);
|
|
165
|
+
} catch {
|
|
166
|
+
return serveFile(resolve(uiRoot, "index.html"), res);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function resolveStaticPath(uiRoot, pathname) {
|
|
171
|
+
const normalized = normalize(pathname).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
172
|
+
const target = resolve(uiRoot, normalized.slice(1));
|
|
173
|
+
if (!target.startsWith(uiRoot)) {
|
|
174
|
+
throw new Error("Invalid path");
|
|
175
|
+
}
|
|
176
|
+
return target;
|
|
177
|
+
}
|
|
178
|
+
function serveFile(filePath, res) {
|
|
179
|
+
if (!existsSync(filePath)) {
|
|
180
|
+
res.statusCode = 404;
|
|
181
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
182
|
+
res.end("Not found");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
res.statusCode = 200;
|
|
186
|
+
res.setHeader("content-type", contentType(filePath));
|
|
187
|
+
createReadStream(filePath).pipe(res);
|
|
188
|
+
}
|
|
189
|
+
function contentType(filePath) {
|
|
190
|
+
switch (extname(filePath)) {
|
|
191
|
+
case ".html":
|
|
192
|
+
return "text/html; charset=utf-8";
|
|
193
|
+
case ".js":
|
|
194
|
+
return "text/javascript; charset=utf-8";
|
|
195
|
+
case ".css":
|
|
196
|
+
return "text/css; charset=utf-8";
|
|
197
|
+
case ".json":
|
|
198
|
+
return "application/json; charset=utf-8";
|
|
199
|
+
case ".svg":
|
|
200
|
+
return "image/svg+xml";
|
|
201
|
+
case ".png":
|
|
202
|
+
return "image/png";
|
|
203
|
+
case ".jpg":
|
|
204
|
+
case ".jpeg":
|
|
205
|
+
return "image/jpeg";
|
|
206
|
+
case ".ico":
|
|
207
|
+
return "image/x-icon";
|
|
208
|
+
default:
|
|
209
|
+
return "application/octet-stream";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function proxyApi(req, res, url, apiBase) {
|
|
213
|
+
const target = new URL(apiBase);
|
|
214
|
+
target.pathname = url.pathname;
|
|
215
|
+
target.search = url.search;
|
|
216
|
+
const isSecure = target.protocol === "https:";
|
|
217
|
+
const proxyRequest = (isSecure ? requestSecure : request)({
|
|
218
|
+
protocol: target.protocol,
|
|
219
|
+
hostname: target.hostname,
|
|
220
|
+
port: target.port,
|
|
221
|
+
method: req.method,
|
|
222
|
+
path: target.pathname + target.search,
|
|
223
|
+
headers: {
|
|
224
|
+
...req.headers,
|
|
225
|
+
host: target.host
|
|
226
|
+
}
|
|
227
|
+
}, (proxyRes) => {
|
|
228
|
+
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
|
|
229
|
+
proxyRes.pipe(res);
|
|
230
|
+
});
|
|
231
|
+
proxyRequest.on("error", (error) => {
|
|
232
|
+
res.statusCode = 502;
|
|
233
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
234
|
+
res.end(`Proxy error: ${error instanceof Error ? error.message : "unknown"}`);
|
|
235
|
+
});
|
|
236
|
+
req.pipe(proxyRequest);
|
|
237
|
+
}
|