@helipod/vite 0.1.0
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/index.d.ts +94 -0
- package/dist/index.js +359 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/** Resolve an OS-assigned free TCP port (listen on 0, read it, release it). */
|
|
4
|
+
declare function freePort(): Promise<number>;
|
|
5
|
+
|
|
6
|
+
interface ResolvedCli {
|
|
7
|
+
command: string;
|
|
8
|
+
baseArgs: string[];
|
|
9
|
+
}
|
|
10
|
+
/** How to invoke the helipod CLI: an explicit override (split on whitespace), else the app's local
|
|
11
|
+
* `node_modules/.bin/helipod`, else `npx helipod`. */
|
|
12
|
+
declare function resolveCli(cwd: string, override?: string): ResolvedCli;
|
|
13
|
+
/** Assemble the `dev` argv: `[...baseArgs, "dev", "--port", <port>, "--dir", <functionsDir>, ...extra]`. */
|
|
14
|
+
declare function buildDevArgs(baseArgs: string[], port: number, functionsDir: string, extra: string[]): string[];
|
|
15
|
+
|
|
16
|
+
interface SpawnedChild {
|
|
17
|
+
stdout: NodeJS.ReadableStream | null;
|
|
18
|
+
stderr: NodeJS.ReadableStream | null;
|
|
19
|
+
on(ev: "exit", cb: (code: number | null) => void): void;
|
|
20
|
+
kill(signal?: NodeJS.Signals): void;
|
|
21
|
+
}
|
|
22
|
+
type SpawnFn = (command: string, args: string[], opts: {
|
|
23
|
+
cwd: string;
|
|
24
|
+
env: NodeJS.ProcessEnv;
|
|
25
|
+
}) => SpawnedChild;
|
|
26
|
+
type ProbeFn = (port: number) => Promise<boolean>;
|
|
27
|
+
interface StartBackendOptions {
|
|
28
|
+
command: string;
|
|
29
|
+
args: string[];
|
|
30
|
+
cwd: string;
|
|
31
|
+
port: number;
|
|
32
|
+
readinessTimeoutMs?: number;
|
|
33
|
+
pollIntervalMs?: number;
|
|
34
|
+
onLog?: (line: string) => void;
|
|
35
|
+
}
|
|
36
|
+
interface Backend {
|
|
37
|
+
stop: () => void;
|
|
38
|
+
}
|
|
39
|
+
/** True once something accepts a TCP connection on the port (the backend is up). */
|
|
40
|
+
declare function probePort(port: number, host?: string): Promise<boolean>;
|
|
41
|
+
/** Spawn the backend child, pipe its output line-wise to `onLog`, and resolve once it's ready.
|
|
42
|
+
* Rejects if the child exits before ready or readiness times out. `stop` kills the child once. */
|
|
43
|
+
declare function startBackend(opts: StartBackendOptions, deps: {
|
|
44
|
+
spawn: SpawnFn;
|
|
45
|
+
probe: ProbeFn;
|
|
46
|
+
}): Promise<Backend>;
|
|
47
|
+
interface CleanupProc {
|
|
48
|
+
once(ev: string, cb: (...a: unknown[]) => void): void;
|
|
49
|
+
}
|
|
50
|
+
/** Ensure the child is killed on Vite/process teardown. Kills on `exit` (backstop) and re-exits on
|
|
51
|
+
* SIGINT/SIGTERM after killing (so the child never orphans). Injectable proc/exit for tests. */
|
|
52
|
+
declare function installSignalCleanup(stop: () => void, proc?: CleanupProc, exit?: (code: number) => void): void;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The `embed` mode plugin. Only a `configureServer` hook — no `config` proxy (the engine is served
|
|
56
|
+
* in-process, so there is no second origin to proxy to).
|
|
57
|
+
*/
|
|
58
|
+
declare function embedPlugin(options: HelipodVitePluginOptions): Plugin;
|
|
59
|
+
|
|
60
|
+
declare const DEFAULT_FUNCTIONS_DIR = "helipod";
|
|
61
|
+
interface HelipodVitePluginOptions {
|
|
62
|
+
/**
|
|
63
|
+
* How the backend is run alongside Vite:
|
|
64
|
+
* - `"proxy"` (default): spawn `helipod dev` as a child and proxy the engine-owned prefixes to
|
|
65
|
+
* it (Phase 1 — unchanged). Node builtins only; no `@helipod/*` runtime dependency.
|
|
66
|
+
* - `"embed"`: boot the engine INSIDE Vite's own process as connect-middleware + a `/api/sync`
|
|
67
|
+
* WebSocket — no child, no proxy hop (Phase 2). Reaches `@helipod/cli` via a dynamic import,
|
|
68
|
+
* so proxy-mode users never pull it. See `docs/superpowers/specs/2026-05-15-vite-phase2-embed-design.md`.
|
|
69
|
+
*/
|
|
70
|
+
mode?: "proxy" | "embed";
|
|
71
|
+
/** App functions dir → `--dir` (default "helipod"). Shared by both modes. */
|
|
72
|
+
functionsDir?: string;
|
|
73
|
+
/** Backend port to proxy to (default: an OS-assigned free port). */
|
|
74
|
+
port?: number;
|
|
75
|
+
/** How to invoke the CLI (default: local node_modules/.bin/helipod, else `npx helipod`). */
|
|
76
|
+
command?: string;
|
|
77
|
+
/** Extra flags forwarded to `helipod dev` (e.g. ["--database-url", "postgres://…"]). */
|
|
78
|
+
args?: string[];
|
|
79
|
+
/** SQLite file for the in-process engine (default `<root>/.helipod/dev.db`). Ignored in proxy mode. */
|
|
80
|
+
dataPath?: string;
|
|
81
|
+
/** Postgres connection string for the in-process engine (opt-in; SQLite otherwise). Ignored in proxy mode. */
|
|
82
|
+
databaseUrl?: string;
|
|
83
|
+
/** Admin key for the in-process engine's `/_admin` API (default: a per-run ephemeral key). Ignored in proxy mode. */
|
|
84
|
+
adminKey?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Single-origin dev: `vite` alone serves the frontend AND the Helipod backend on one browser origin
|
|
88
|
+
* (no manual proxy, no CORS). Two modes behind one surface (default `"proxy"` — Phase 1, unchanged):
|
|
89
|
+
* - `"proxy"`: spawn `helipod dev` and proxy the engine-owned prefixes to it.
|
|
90
|
+
* - `"embed"`: boot the engine in Vite's own process (no child, no proxy hop).
|
|
91
|
+
*/
|
|
92
|
+
declare function helipod(options?: HelipodVitePluginOptions): Plugin;
|
|
93
|
+
|
|
94
|
+
export { type Backend, type CleanupProc, DEFAULT_FUNCTIONS_DIR, type HelipodVitePluginOptions, type ProbeFn, type ResolvedCli, type SpawnFn, type SpawnedChild, type StartBackendOptions, buildDevArgs, embedPlugin, freePort, helipod, installSignalCleanup, probePort, resolveCli, startBackend };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// src/free-port.ts
|
|
2
|
+
import { createServer } from "net";
|
|
3
|
+
function freePort() {
|
|
4
|
+
return new Promise((resolve2, reject) => {
|
|
5
|
+
const srv = createServer();
|
|
6
|
+
srv.on("error", reject);
|
|
7
|
+
srv.listen(0, () => {
|
|
8
|
+
const addr = srv.address();
|
|
9
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
10
|
+
srv.close(() => port ? resolve2(port) : reject(new Error("could not resolve a free port")));
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/resolve-cli.ts
|
|
16
|
+
import { existsSync } from "fs";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
function resolveCli(cwd, override) {
|
|
19
|
+
if (override && override.trim()) {
|
|
20
|
+
const [command, ...baseArgs] = override.trim().split(/\s+/);
|
|
21
|
+
return { command, baseArgs };
|
|
22
|
+
}
|
|
23
|
+
const localBin = join(cwd, "node_modules", ".bin", "helipod");
|
|
24
|
+
if (existsSync(localBin)) return { command: localBin, baseArgs: [] };
|
|
25
|
+
return { command: "npx", baseArgs: ["helipod"] };
|
|
26
|
+
}
|
|
27
|
+
function buildDevArgs(baseArgs, port, functionsDir, extra) {
|
|
28
|
+
return [...baseArgs, "dev", "--port", String(port), "--dir", functionsDir, ...extra];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/child.ts
|
|
32
|
+
import { connect } from "net";
|
|
33
|
+
function probePort(port, host = "127.0.0.1") {
|
|
34
|
+
return new Promise((resolve2) => {
|
|
35
|
+
const sock = connect({ port, host });
|
|
36
|
+
const done = (ok) => {
|
|
37
|
+
sock.destroy();
|
|
38
|
+
resolve2(ok);
|
|
39
|
+
};
|
|
40
|
+
sock.once("connect", () => done(true));
|
|
41
|
+
sock.once("error", () => done(false));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async function startBackend(opts, deps) {
|
|
45
|
+
const child = deps.spawn(opts.command, opts.args, { cwd: opts.cwd, env: process.env });
|
|
46
|
+
let stopped = false;
|
|
47
|
+
const stop = () => {
|
|
48
|
+
if (stopped) return;
|
|
49
|
+
stopped = true;
|
|
50
|
+
try {
|
|
51
|
+
child.kill("SIGTERM");
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const pipe = (stream) => {
|
|
56
|
+
stream?.on("data", (d) => {
|
|
57
|
+
for (const line of d.toString().split("\n")) if (line.trim()) opts.onLog?.(line);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
pipe(child.stdout);
|
|
61
|
+
pipe(child.stderr);
|
|
62
|
+
let exited = false;
|
|
63
|
+
let exitCode = null;
|
|
64
|
+
child.on("exit", (code) => {
|
|
65
|
+
exited = true;
|
|
66
|
+
exitCode = code;
|
|
67
|
+
});
|
|
68
|
+
const timeout = opts.readinessTimeoutMs ?? 3e4;
|
|
69
|
+
const interval = opts.pollIntervalMs ?? 200;
|
|
70
|
+
const deadline = Date.now() + timeout;
|
|
71
|
+
while (Date.now() < deadline) {
|
|
72
|
+
if (exited) {
|
|
73
|
+
stop();
|
|
74
|
+
throw new Error(`helipod dev exited before becoming ready (code ${exitCode})`);
|
|
75
|
+
}
|
|
76
|
+
if (await deps.probe(opts.port)) return { stop };
|
|
77
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
78
|
+
}
|
|
79
|
+
stop();
|
|
80
|
+
throw new Error(`helipod dev did not become ready on port ${opts.port} within ${timeout}ms`);
|
|
81
|
+
}
|
|
82
|
+
function installSignalCleanup(stop, proc = process, exit = (c) => process.exit(c)) {
|
|
83
|
+
proc.once("exit", () => stop());
|
|
84
|
+
proc.once("SIGINT", () => {
|
|
85
|
+
stop();
|
|
86
|
+
exit(130);
|
|
87
|
+
});
|
|
88
|
+
proc.once("SIGTERM", () => {
|
|
89
|
+
stop();
|
|
90
|
+
exit(143);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/embed.ts
|
|
95
|
+
import { randomBytes } from "crypto";
|
|
96
|
+
import { dirname, join as join2, resolve, sep } from "path";
|
|
97
|
+
var SYNC_PATH = "/api/sync";
|
|
98
|
+
var STORAGE_PREFIX = "/api/storage/";
|
|
99
|
+
var MAX_BODY_BYTES = 5 * 1024 * 1024;
|
|
100
|
+
var ENGINE_PREFIXES = ["/api", "/_admin", "/_dashboard"];
|
|
101
|
+
function isEnginePath(path) {
|
|
102
|
+
return ENGINE_PREFIXES.some((p) => path === p || path.startsWith(p + "/"));
|
|
103
|
+
}
|
|
104
|
+
function methodHasBody(method) {
|
|
105
|
+
return method === "POST" || method === "PUT" || method === "PATCH";
|
|
106
|
+
}
|
|
107
|
+
function readBodyBytes(req) {
|
|
108
|
+
return new Promise((resolvePromise, reject) => {
|
|
109
|
+
const chunks = [];
|
|
110
|
+
let total = 0;
|
|
111
|
+
req.on("data", (c) => {
|
|
112
|
+
total += c.length;
|
|
113
|
+
if (total > MAX_BODY_BYTES) {
|
|
114
|
+
req.destroy();
|
|
115
|
+
reject(new Error("request body too large"));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
chunks.push(c);
|
|
119
|
+
});
|
|
120
|
+
req.on("end", () => resolvePromise(Buffer.concat(chunks)));
|
|
121
|
+
req.on("error", reject);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function matchStorageRoute(routes, method, path) {
|
|
125
|
+
if (!path.startsWith(STORAGE_PREFIX)) return void 0;
|
|
126
|
+
return routes.find((r) => r.method === method && path.startsWith(r.pathPrefix));
|
|
127
|
+
}
|
|
128
|
+
function matchComponentRoute(routes, method, path) {
|
|
129
|
+
return routes.find((r) => r.method === method && path.startsWith(r.pathPrefix));
|
|
130
|
+
}
|
|
131
|
+
async function writeResponse(res, response) {
|
|
132
|
+
const outHeaders = {};
|
|
133
|
+
response.headers.forEach((v, k) => {
|
|
134
|
+
outHeaders[k] = v;
|
|
135
|
+
});
|
|
136
|
+
res.writeHead(response.status, outHeaders);
|
|
137
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
138
|
+
}
|
|
139
|
+
function embedPlugin(options) {
|
|
140
|
+
return {
|
|
141
|
+
name: "helipod:embed",
|
|
142
|
+
async configureServer(server) {
|
|
143
|
+
const log = server.config.logger;
|
|
144
|
+
const root = server.config.root;
|
|
145
|
+
let cli;
|
|
146
|
+
try {
|
|
147
|
+
cli = await import("@helipod/cli");
|
|
148
|
+
} catch (e) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`@helipod/vite embed mode requires @helipod/cli to be installed \u2014 ${e instanceof Error ? e.message : String(e)}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const {
|
|
154
|
+
bootProject,
|
|
155
|
+
writeGenerated,
|
|
156
|
+
handleHttpRequest,
|
|
157
|
+
push,
|
|
158
|
+
loadFunctionsDir,
|
|
159
|
+
loadConfig,
|
|
160
|
+
withStorageModules,
|
|
161
|
+
DEFAULT_FUNCTIONS_DIR: DEFAULT_FUNCTIONS_DIR2
|
|
162
|
+
} = cli;
|
|
163
|
+
const functionsDir = resolve(root, options.functionsDir ?? DEFAULT_FUNCTIONS_DIR2);
|
|
164
|
+
const generatedDir = join2(functionsDir, "_generated");
|
|
165
|
+
const dataPath = options.dataPath ? resolve(root, options.dataPath) : join2(root, ".helipod", "dev.db");
|
|
166
|
+
const adminKey = options.adminKey ?? randomBytes(24).toString("hex");
|
|
167
|
+
const boot = await bootProject({
|
|
168
|
+
functionsDir,
|
|
169
|
+
dataPath,
|
|
170
|
+
...options.databaseUrl !== void 0 ? { databaseUrl: options.databaseUrl } : {},
|
|
171
|
+
adminKey
|
|
172
|
+
});
|
|
173
|
+
const { runtime, adminApi, project, generated, store, storageRoutes, componentRoutes } = boot;
|
|
174
|
+
writeGenerated(generated.files, generatedDir);
|
|
175
|
+
const config = await loadConfig(dirname(functionsDir));
|
|
176
|
+
let currentRoutes = project.routes;
|
|
177
|
+
server.middlewares.use((req, res, next) => {
|
|
178
|
+
const rawUrl = req.url ?? "/";
|
|
179
|
+
const path = rawUrl.split("?")[0] ?? "/";
|
|
180
|
+
if (!isEnginePath(path)) return next();
|
|
181
|
+
void (async () => {
|
|
182
|
+
try {
|
|
183
|
+
const method = req.method ?? "GET";
|
|
184
|
+
const url = new URL(rawUrl, "http://x");
|
|
185
|
+
const query = {};
|
|
186
|
+
url.searchParams.forEach((val, key) => {
|
|
187
|
+
query[key] = val;
|
|
188
|
+
});
|
|
189
|
+
const authorization = typeof req.headers.authorization === "string" ? req.headers.authorization : void 0;
|
|
190
|
+
const headers = Object.fromEntries(
|
|
191
|
+
Object.entries(req.headers).filter((e) => typeof e[1] === "string")
|
|
192
|
+
);
|
|
193
|
+
const isStorage = path.startsWith(STORAGE_PREFIX);
|
|
194
|
+
const needsBody = methodHasBody(method);
|
|
195
|
+
const bodyBytes = needsBody ? await readBodyBytes(req) : void 0;
|
|
196
|
+
const storageRoute = matchStorageRoute(storageRoutes, method, path);
|
|
197
|
+
if (storageRoute) {
|
|
198
|
+
const h = new Headers(headers);
|
|
199
|
+
if (authorization && !h.has("authorization")) h.set("authorization", authorization);
|
|
200
|
+
const request = new Request(`http://${h.get("host") ?? "localhost"}${rawUrl}`, {
|
|
201
|
+
method,
|
|
202
|
+
headers: h,
|
|
203
|
+
...bodyBytes !== void 0 ? { body: new Uint8Array(bodyBytes) } : {}
|
|
204
|
+
});
|
|
205
|
+
await writeResponse(res, await storageRoute.handler(request));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const componentRoute = matchComponentRoute(componentRoutes, method, path);
|
|
209
|
+
if (componentRoute) {
|
|
210
|
+
const h = new Headers(headers);
|
|
211
|
+
if (authorization && !h.has("authorization")) h.set("authorization", authorization);
|
|
212
|
+
const request = new Request(`http://${h.get("host") ?? "localhost"}${rawUrl}`, {
|
|
213
|
+
method,
|
|
214
|
+
headers: h,
|
|
215
|
+
...!isStorage && bodyBytes !== void 0 ? { body: bodyBytes.toString("utf8") } : {}
|
|
216
|
+
});
|
|
217
|
+
await writeResponse(res, await componentRoute.handler(request));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const body = !isStorage && bodyBytes !== void 0 ? bodyBytes.toString("utf8") : void 0;
|
|
221
|
+
const info = { functions: runtime.functionPaths(), tables: runtime.tableNames() };
|
|
222
|
+
const response = await handleHttpRequest(
|
|
223
|
+
runtime,
|
|
224
|
+
{ method, path, body, query, authorization, headers },
|
|
225
|
+
info,
|
|
226
|
+
{ api: adminApi, key: adminKey },
|
|
227
|
+
currentRoutes
|
|
228
|
+
);
|
|
229
|
+
res.writeHead(response.status, response.headers);
|
|
230
|
+
res.end(response.body);
|
|
231
|
+
} catch (e) {
|
|
232
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
233
|
+
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
234
|
+
}
|
|
235
|
+
})();
|
|
236
|
+
});
|
|
237
|
+
const { WebSocketServer } = await import("ws");
|
|
238
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
239
|
+
let sessionCounter = 0;
|
|
240
|
+
const onUpgrade = (req, socket, head) => {
|
|
241
|
+
if ((req.url ?? "").split("?")[0] !== SYNC_PATH) return;
|
|
242
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
243
|
+
const sessionId = `vite-embed-ws-${++sessionCounter}`;
|
|
244
|
+
const syncSocket = {
|
|
245
|
+
send: (data) => ws.send(data),
|
|
246
|
+
get bufferedAmount() {
|
|
247
|
+
return ws.bufferedAmount;
|
|
248
|
+
},
|
|
249
|
+
close: () => ws.close(),
|
|
250
|
+
ping: (onPong) => {
|
|
251
|
+
ws.once("pong", onPong);
|
|
252
|
+
ws.ping();
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
runtime.handler.connect(sessionId, syncSocket);
|
|
256
|
+
ws.on("message", (data) => void runtime.handler.handleMessage(sessionId, data.toString("utf8")));
|
|
257
|
+
ws.on("close", () => runtime.handler.disconnect(sessionId));
|
|
258
|
+
ws.on("error", () => runtime.handler.disconnect(sessionId));
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
if (!server.httpServer) {
|
|
262
|
+
server.config.logger.warn(
|
|
263
|
+
"[helipod] embed mode needs Vite's own HTTP server \u2014 under `server.middlewareMode` the reactive /api/sync WebSocket and engine cleanup do NOT wire up. Use proxy mode, or run Vite without middlewareMode."
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
server.httpServer?.on("upgrade", onUpgrade);
|
|
267
|
+
let reloadTimer;
|
|
268
|
+
const scheduleReload = (file) => {
|
|
269
|
+
if (!file.startsWith(functionsDir + sep)) return;
|
|
270
|
+
if (file.includes(sep + "_generated" + sep)) return;
|
|
271
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
272
|
+
reloadTimer = setTimeout(() => {
|
|
273
|
+
void reload();
|
|
274
|
+
}, 50);
|
|
275
|
+
};
|
|
276
|
+
const reload = async () => {
|
|
277
|
+
try {
|
|
278
|
+
const next = push(await loadFunctionsDir(functionsDir), config.components);
|
|
279
|
+
writeGenerated(next.generated.files, generatedDir);
|
|
280
|
+
runtime.setModules(withStorageModules(next.project.moduleMap));
|
|
281
|
+
currentRoutes = next.project.routes;
|
|
282
|
+
log.info(`[helipod] \u21BB pushed (${Object.keys(next.project.moduleMap).length} functions)`);
|
|
283
|
+
} catch (e) {
|
|
284
|
+
log.error(`[helipod] \u2717 reload failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
server.watcher.on("add", scheduleReload);
|
|
288
|
+
server.watcher.on("change", scheduleReload);
|
|
289
|
+
server.watcher.on("unlink", scheduleReload);
|
|
290
|
+
let stopped = false;
|
|
291
|
+
const stop = async () => {
|
|
292
|
+
if (stopped) return;
|
|
293
|
+
stopped = true;
|
|
294
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
295
|
+
server.httpServer?.off("upgrade", onUpgrade);
|
|
296
|
+
for (const client of wss.clients) client.terminate();
|
|
297
|
+
wss.close();
|
|
298
|
+
await runtime.stopDrivers();
|
|
299
|
+
if (boot.objectStoreRelease) await boot.objectStoreRelease();
|
|
300
|
+
await store.close();
|
|
301
|
+
};
|
|
302
|
+
server.httpServer?.once("close", () => void stop());
|
|
303
|
+
log.info(`[helipod] embed \u2192 engine in-process on Vite's origin (admin key: ${adminKey})`);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/index.ts
|
|
309
|
+
import { spawn as nodeChildSpawn } from "child_process";
|
|
310
|
+
var DEFAULT_FUNCTIONS_DIR = "helipod";
|
|
311
|
+
var nodeSpawn = (command, args, opts) => nodeChildSpawn(command, args, { cwd: opts.cwd, env: opts.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
312
|
+
function helipod(options = {}) {
|
|
313
|
+
if ((options.mode ?? "proxy") === "embed") return embedPlugin(options);
|
|
314
|
+
return proxyPlugin(options);
|
|
315
|
+
}
|
|
316
|
+
function proxyPlugin(options) {
|
|
317
|
+
let port;
|
|
318
|
+
let backend;
|
|
319
|
+
return {
|
|
320
|
+
name: "helipod",
|
|
321
|
+
async config() {
|
|
322
|
+
port = options.port ?? await freePort();
|
|
323
|
+
const target = `http://127.0.0.1:${port}`;
|
|
324
|
+
return {
|
|
325
|
+
server: {
|
|
326
|
+
proxy: {
|
|
327
|
+
"/api": { target, ws: true, changeOrigin: true },
|
|
328
|
+
"/_dashboard": { target, changeOrigin: true },
|
|
329
|
+
"/_admin": { target, changeOrigin: true }
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
async configureServer(server) {
|
|
335
|
+
const root = server.config.root;
|
|
336
|
+
const cli = resolveCli(root, options.command);
|
|
337
|
+
const args = buildDevArgs(cli.baseArgs, port, options.functionsDir ?? DEFAULT_FUNCTIONS_DIR, options.args ?? []);
|
|
338
|
+
backend = await startBackend(
|
|
339
|
+
{ command: cli.command, args, cwd: root, port, onLog: (l) => server.config.logger.info(`[helipod] ${l}`) },
|
|
340
|
+
{ spawn: nodeSpawn, probe: probePort }
|
|
341
|
+
);
|
|
342
|
+
const stop = () => backend?.stop();
|
|
343
|
+
server.httpServer?.once("close", stop);
|
|
344
|
+
installSignalCleanup(stop);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
export {
|
|
349
|
+
DEFAULT_FUNCTIONS_DIR,
|
|
350
|
+
buildDevArgs,
|
|
351
|
+
embedPlugin,
|
|
352
|
+
freePort,
|
|
353
|
+
helipod,
|
|
354
|
+
installSignalCleanup,
|
|
355
|
+
probePort,
|
|
356
|
+
resolveCli,
|
|
357
|
+
startBackend
|
|
358
|
+
};
|
|
359
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/free-port.ts","../src/resolve-cli.ts","../src/child.ts","../src/embed.ts","../src/index.ts"],"sourcesContent":["import { createServer } from \"node:net\";\n\n/** Resolve an OS-assigned free TCP port (listen on 0, read it, release it). */\nexport function freePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const srv = createServer();\n srv.on(\"error\", reject);\n srv.listen(0, () => {\n const addr = srv.address();\n const port = typeof addr === \"object\" && addr ? addr.port : 0;\n srv.close(() => (port ? resolve(port) : reject(new Error(\"could not resolve a free port\"))));\n });\n });\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface ResolvedCli {\n command: string;\n baseArgs: string[];\n}\n\n/** How to invoke the helipod CLI: an explicit override (split on whitespace), else the app's local\n * `node_modules/.bin/helipod`, else `npx helipod`. */\nexport function resolveCli(cwd: string, override?: string): ResolvedCli {\n if (override && override.trim()) {\n const [command, ...baseArgs] = override.trim().split(/\\s+/);\n return { command: command!, baseArgs };\n }\n const localBin = join(cwd, \"node_modules\", \".bin\", \"helipod\");\n if (existsSync(localBin)) return { command: localBin, baseArgs: [] };\n return { command: \"npx\", baseArgs: [\"helipod\"] };\n}\n\n/** Assemble the `dev` argv: `[...baseArgs, \"dev\", \"--port\", <port>, \"--dir\", <functionsDir>, ...extra]`. */\nexport function buildDevArgs(baseArgs: string[], port: number, functionsDir: string, extra: string[]): string[] {\n return [...baseArgs, \"dev\", \"--port\", String(port), \"--dir\", functionsDir, ...extra];\n}\n","import { connect } from \"node:net\";\n\nexport interface SpawnedChild {\n stdout: NodeJS.ReadableStream | null;\n stderr: NodeJS.ReadableStream | null;\n on(ev: \"exit\", cb: (code: number | null) => void): void;\n kill(signal?: NodeJS.Signals): void;\n}\nexport type SpawnFn = (command: string, args: string[], opts: { cwd: string; env: NodeJS.ProcessEnv }) => SpawnedChild;\nexport type ProbeFn = (port: number) => Promise<boolean>;\n\nexport interface StartBackendOptions {\n command: string;\n args: string[];\n cwd: string;\n port: number;\n readinessTimeoutMs?: number;\n pollIntervalMs?: number;\n onLog?: (line: string) => void;\n}\nexport interface Backend { stop: () => void; }\n\n/** True once something accepts a TCP connection on the port (the backend is up). */\nexport function probePort(port: number, host = \"127.0.0.1\"): Promise<boolean> {\n return new Promise((resolve) => {\n const sock = connect({ port, host });\n const done = (ok: boolean) => { sock.destroy(); resolve(ok); };\n sock.once(\"connect\", () => done(true));\n sock.once(\"error\", () => done(false));\n });\n}\n\n/** Spawn the backend child, pipe its output line-wise to `onLog`, and resolve once it's ready.\n * Rejects if the child exits before ready or readiness times out. `stop` kills the child once. */\nexport async function startBackend(opts: StartBackendOptions, deps: { spawn: SpawnFn; probe: ProbeFn }): Promise<Backend> {\n const child = deps.spawn(opts.command, opts.args, { cwd: opts.cwd, env: process.env });\n let stopped = false;\n const stop = () => { if (stopped) return; stopped = true; try { child.kill(\"SIGTERM\"); } catch { /* already gone */ } };\n\n const pipe = (stream: NodeJS.ReadableStream | null) => {\n stream?.on(\"data\", (d: Buffer | string) => {\n for (const line of d.toString().split(\"\\n\")) if (line.trim()) opts.onLog?.(line);\n });\n };\n pipe(child.stdout);\n pipe(child.stderr);\n\n let exited = false;\n let exitCode: number | null = null;\n child.on(\"exit\", (code) => { exited = true; exitCode = code; });\n\n const timeout = opts.readinessTimeoutMs ?? 30_000;\n const interval = opts.pollIntervalMs ?? 200;\n const deadline = Date.now() + timeout;\n while (Date.now() < deadline) {\n if (exited) { stop(); throw new Error(`helipod dev exited before becoming ready (code ${exitCode})`); }\n if (await deps.probe(opts.port)) return { stop };\n await new Promise((r) => setTimeout(r, interval));\n }\n stop();\n throw new Error(`helipod dev did not become ready on port ${opts.port} within ${timeout}ms`);\n}\n\nexport interface CleanupProc { once(ev: string, cb: (...a: unknown[]) => void): void; }\n\n/** Ensure the child is killed on Vite/process teardown. Kills on `exit` (backstop) and re-exits on\n * SIGINT/SIGTERM after killing (so the child never orphans). Injectable proc/exit for tests. */\nexport function installSignalCleanup(\n stop: () => void,\n proc: CleanupProc = process,\n exit: (code: number) => void = (c) => process.exit(c),\n): void {\n proc.once(\"exit\", () => stop());\n proc.once(\"SIGINT\", () => { stop(); exit(130); });\n proc.once(\"SIGTERM\", () => { stop(); exit(143); });\n}\n","/**\n * Phase 2 — in-process embed. Boot the Helipod engine INSIDE Vite's own dev-server process (no\n * child `helipod dev`, no proxy hop) and serve it as connect-middleware + a `/api/sync` WebSocket\n * on Vite's own origin. Opt-in via `helipod({ mode: \"embed\" })`; the default proxy mode\n * (`index.ts`) is untouched.\n *\n * The engine boot, HTTP dispatch, codegen, and sync wiring are all REUSED from `@helipod/cli`\n * (reached via a dynamic import so proxy-mode users never pull it — see the design note,\n * `docs/superpowers/specs/2026-05-15-vite-phase2-embed-design.md`). What's Vite-shaped and lives here:\n * the connect `req`/`res` ↔ engine `HttpRequest`/`HttpResponse` translation, the storage/component\n * route prefix-match, and the `/api/sync`-only upgrade listener that COEXISTS with Vite's HMR ws.\n */\nimport { randomBytes } from \"node:crypto\";\nimport { dirname, join, resolve, sep } from \"node:path\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Duplex } from \"node:stream\";\nimport type { Plugin } from \"vite\";\nimport type { HelipodVitePluginOptions } from \"./index\";\n\nconst SYNC_PATH = \"/api/sync\";\nconst STORAGE_PREFIX = \"/api/storage/\";\nconst MAX_BODY_BYTES = 5 * 1024 * 1024;\n/** Engine-owned URL prefixes served in-process; everything else falls through to `next()` (Vite). */\nconst ENGINE_PREFIXES = [\"/api\", \"/_admin\", \"/_dashboard\"];\n\nfunction isEnginePath(path: string): boolean {\n return ENGINE_PREFIXES.some((p) => path === p || path.startsWith(p + \"/\"));\n}\n\nfunction methodHasBody(method: string | undefined): boolean {\n return method === \"POST\" || method === \"PUT\" || method === \"PATCH\";\n}\n\n/** Read the raw request body as bytes, capped. Binary-safe (storage uploads may be arbitrary bytes). */\nfunction readBodyBytes(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolvePromise, reject) => {\n const chunks: Buffer[] = [];\n let total = 0;\n req.on(\"data\", (c: Buffer) => {\n total += c.length;\n if (total > MAX_BODY_BYTES) {\n req.destroy();\n reject(new Error(\"request body too large\"));\n return;\n }\n chunks.push(c);\n });\n req.on(\"end\", () => resolvePromise(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\n/** A minimal `{ method, pathPrefix, handler }` engine route (storage or component). */\ninterface EngineRoute {\n method: string;\n pathPrefix: string;\n handler: (request: Request) => Response | Promise<Response>;\n}\n\nfunction matchStorageRoute(routes: EngineRoute[], method: string, path: string): EngineRoute | undefined {\n if (!path.startsWith(STORAGE_PREFIX)) return undefined;\n return routes.find((r) => r.method === method && path.startsWith(r.pathPrefix));\n}\nfunction matchComponentRoute(routes: EngineRoute[], method: string, path: string): EngineRoute | undefined {\n return routes.find((r) => r.method === method && path.startsWith(r.pathPrefix));\n}\n\n/** Stream a native `Response` (storage/component route) back through the connect `res`. */\nasync function writeResponse(res: ServerResponse, response: Response): Promise<void> {\n const outHeaders: Record<string, string> = {};\n response.headers.forEach((v, k) => { outHeaders[k] = v; });\n res.writeHead(response.status, outHeaders);\n res.end(Buffer.from(await response.arrayBuffer()));\n}\n\n/**\n * The `embed` mode plugin. Only a `configureServer` hook — no `config` proxy (the engine is served\n * in-process, so there is no second origin to proxy to).\n */\nexport function embedPlugin(options: HelipodVitePluginOptions): Plugin {\n return {\n name: \"helipod:embed\",\n async configureServer(server) {\n const log = server.config.logger;\n const root = server.config.root;\n\n // Reach the CLI's shared boot core ONLY here, via a dynamic import — proxy mode never pulls it\n // (also where DEFAULT_FUNCTIONS_DIR comes from: a static top-level import of `@helipod/cli`\n // would defeat the point, forcing it to resolve even for proxy-mode-only consumers who never\n // installed it — it's an optional peer, see package.json).\n let cli: typeof import(\"@helipod/cli\");\n try {\n cli = await import(\"@helipod/cli\");\n } catch (e) {\n throw new Error(\n `@helipod/vite embed mode requires @helipod/cli to be installed — ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n const {\n bootProject,\n writeGenerated,\n handleHttpRequest,\n push,\n loadFunctionsDir,\n loadConfig,\n withStorageModules,\n DEFAULT_FUNCTIONS_DIR,\n } = cli;\n\n const functionsDir = resolve(root, options.functionsDir ?? DEFAULT_FUNCTIONS_DIR);\n const generatedDir = join(functionsDir, \"_generated\");\n const dataPath = options.dataPath ? resolve(root, options.dataPath) : join(root, \".helipod\", \"dev.db\");\n const adminKey = options.adminKey ?? randomBytes(24).toString(\"hex\");\n\n const boot = await bootProject({\n functionsDir,\n dataPath,\n ...(options.databaseUrl !== undefined ? { databaseUrl: options.databaseUrl } : {}),\n adminKey,\n });\n const { runtime, adminApi, project, generated, store, storageRoutes, componentRoutes } = boot;\n writeGenerated(generated.files, generatedDir);\n const config = await loadConfig(dirname(functionsDir));\n\n // Mutable across hot-reloads: only the user `http.ts` routes swap (the component set — and thus\n // storage/component routes — is fixed at boot, exactly as `helipod dev`/`serve` behave).\n let currentRoutes = project.routes;\n\n // ── HTTP: engine paths in-process, everything else to Vite ────────────────────────────────\n server.middlewares.use((req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => {\n const rawUrl = req.url ?? \"/\";\n const path = rawUrl.split(\"?\")[0] ?? \"/\";\n if (!isEnginePath(path)) return next();\n void (async () => {\n try {\n const method = req.method ?? \"GET\";\n const url = new URL(rawUrl, \"http://x\");\n const query: Record<string, string> = {};\n url.searchParams.forEach((val, key) => { query[key] = val; });\n const authorization = typeof req.headers.authorization === \"string\" ? req.headers.authorization : undefined;\n const headers = Object.fromEntries(\n Object.entries(req.headers).filter((e): e is [string, string] => typeof e[1] === \"string\"),\n );\n const isStorage = path.startsWith(STORAGE_PREFIX);\n const needsBody = methodHasBody(method);\n const bodyBytes = needsBody ? await readBodyBytes(req) : undefined;\n\n // Storage routes (binary-safe body) → native Request/Response.\n const storageRoute = matchStorageRoute(storageRoutes, method, path);\n if (storageRoute) {\n const h = new Headers(headers);\n if (authorization && !h.has(\"authorization\")) h.set(\"authorization\", authorization);\n const request = new Request(`http://${h.get(\"host\") ?? \"localhost\"}${rawUrl}`, {\n method,\n headers: h,\n ...(bodyBytes !== undefined ? { body: new Uint8Array(bodyBytes) } : {}),\n });\n await writeResponse(res, await storageRoute.handler(request));\n return;\n }\n // Component-contributed routes (e.g. auth's OAuth callbacks) → native Request/Response.\n const componentRoute = matchComponentRoute(componentRoutes, method, path);\n if (componentRoute) {\n const h = new Headers(headers);\n if (authorization && !h.has(\"authorization\")) h.set(\"authorization\", authorization);\n const request = new Request(`http://${h.get(\"host\") ?? \"localhost\"}${rawUrl}`, {\n method,\n headers: h,\n ...(!isStorage && bodyBytes !== undefined ? { body: bodyBytes.toString(\"utf8\") } : {}),\n });\n await writeResponse(res, await componentRoute.handler(request));\n return;\n }\n // The pure dispatcher (health, /api/run, /_admin/*, /_dashboard status page).\n const body = !isStorage && bodyBytes !== undefined ? bodyBytes.toString(\"utf8\") : undefined;\n const info = { functions: runtime.functionPaths(), tables: runtime.tableNames() };\n const response = await handleHttpRequest(\n runtime,\n { method, path, body, query, authorization, headers },\n info,\n { api: adminApi, key: adminKey },\n currentRoutes,\n );\n res.writeHead(response.status, response.headers);\n res.end(response.body);\n } catch (e) {\n res.writeHead(500, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));\n }\n })();\n });\n\n // ── WebSocket: /api/sync only, COEXISTING with Vite's HMR ws on the same httpServer ───────\n // Node emits 'upgrade' to EVERY listener. Vite's HMR listener handles only `vite-hmr`/`vite-ping`\n // upgrades at the HMR base and never touches foreign sockets; ours handles ONLY `/api/sync` and\n // likewise never destroys a socket it doesn't own — so the two listeners coexist. (This is the\n // one deliberate divergence from `server.ts`'s Node backend, which owns the whole server and so\n // destroys non-sync upgrades. See the design note's coexistence section.)\n const { WebSocketServer } = await import(\"ws\");\n const wss = new WebSocketServer({ noServer: true });\n let sessionCounter = 0;\n const onUpgrade = (req: IncomingMessage, socket: Duplex, head: Buffer) => {\n if ((req.url ?? \"\").split(\"?\")[0] !== SYNC_PATH) return; // NOT ours — leave it for Vite's HMR listener.\n wss.handleUpgrade(req, socket, head, (ws) => {\n const sessionId = `vite-embed-ws-${++sessionCounter}`;\n const syncSocket = {\n send: (data: string) => ws.send(data),\n get bufferedAmount() { return ws.bufferedAmount; },\n close: () => ws.close(),\n ping: (onPong: () => void) => { ws.once(\"pong\", onPong); ws.ping(); },\n };\n runtime.handler.connect(sessionId, syncSocket);\n ws.on(\"message\", (data: Buffer) => void runtime.handler.handleMessage(sessionId, data.toString(\"utf8\")));\n ws.on(\"close\", () => runtime.handler.disconnect(sessionId));\n ws.on(\"error\", () => runtime.handler.disconnect(sessionId));\n });\n };\n // Under `server.middlewareMode` (SSR hosts) Vite exposes no `httpServer`, so the sync WS can't\n // attach and `stop()` (below) would never run — the engine + store would leak silently. Fail\n // LOUD instead: warn so the operator knows the reactive socket is inert in this configuration.\n if (!server.httpServer) {\n server.config.logger.warn(\n \"[helipod] embed mode needs Vite's own HTTP server — under `server.middlewareMode` the reactive /api/sync WebSocket and engine cleanup do NOT wire up. Use proxy mode, or run Vite without middlewareMode.\",\n );\n }\n server.httpServer?.on(\"upgrade\", onUpgrade);\n\n // ── Hot-reload the functions dir — an INDEPENDENT loop from Vite HMR ─────────────────────────\n let reloadTimer: ReturnType<typeof setTimeout> | undefined;\n const scheduleReload = (file: string) => {\n if (!file.startsWith(functionsDir + sep)) return;\n if (file.includes(sep + \"_generated\" + sep)) return;\n if (reloadTimer) clearTimeout(reloadTimer);\n reloadTimer = setTimeout(() => { void reload(); }, 50);\n };\n const reload = async () => {\n try {\n const next = push(await loadFunctionsDir(functionsDir), config.components);\n writeGenerated(next.generated.files, generatedDir);\n runtime.setModules(withStorageModules(next.project.moduleMap));\n currentRoutes = next.project.routes;\n log.info(`[helipod] ↻ pushed (${Object.keys(next.project.moduleMap).length} functions)`);\n } catch (e) {\n log.error(`[helipod] ✗ reload failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n };\n server.watcher.on(\"add\", scheduleReload);\n server.watcher.on(\"change\", scheduleReload);\n server.watcher.on(\"unlink\", scheduleReload);\n\n // ── Cleanup on Vite shutdown (idempotent) ──────────────────────────────────────────────────\n let stopped = false;\n const stop = async () => {\n if (stopped) return;\n stopped = true;\n if (reloadTimer) clearTimeout(reloadTimer);\n server.httpServer?.off(\"upgrade\", onUpgrade);\n for (const client of wss.clients) client.terminate();\n wss.close();\n // Stop drivers BEFORE the store closes (a driver timer must never fire against a closed store —\n // the same ordering `server.ts`'s close() enforces), then release any object-store lease.\n await runtime.stopDrivers();\n if (boot.objectStoreRelease) await boot.objectStoreRelease();\n await store.close();\n };\n server.httpServer?.once(\"close\", () => void stop());\n\n log.info(`[helipod] embed → engine in-process on Vite's origin (admin key: ${adminKey})`);\n },\n };\n}\n\n// Re-exported for tests: the pure engine-path predicate.\nexport { isEnginePath };\n","export { freePort } from \"./free-port\";\nexport { resolveCli, buildDevArgs, type ResolvedCli } from \"./resolve-cli\";\nexport { probePort, startBackend, installSignalCleanup } from \"./child\";\nexport type { SpawnFn, ProbeFn, Backend, SpawnedChild, StartBackendOptions, CleanupProc } from \"./child\";\nexport { embedPlugin } from \"./embed\";\n\nimport { spawn as nodeChildSpawn } from \"node:child_process\";\nimport type { Plugin } from \"vite\";\nimport { freePort } from \"./free-port\";\nimport { resolveCli, buildDevArgs } from \"./resolve-cli\";\nimport { startBackend, probePort, installSignalCleanup, type SpawnFn, type Backend } from \"./child\";\nimport { embedPlugin } from \"./embed\";\n\n// Mirrors `DEFAULT_FUNCTIONS_DIR` from `@helipod/cli` (`packages/cli/src/functions-dir.ts`).\n// NOT imported: `@helipod/cli` is an optional peer dependency (see package.json) so that\n// proxy-mode-only consumers — who spawn `helipod dev` as a child process and never touch the\n// package's own JS — aren't forced to have it installed. embed.ts reaches the real constant\n// through its own dynamic import instead, for the same reason. Exported (not just module-local) so\n// a test can assert this literal hasn't drifted from `@helipod/cli`'s own constant — see\n// `test/plugin.test.ts`'s \"DEFAULT_FUNCTIONS_DIR guard\" — without a static top-level import of\n// `@helipod/cli` here in the shipped proxy path itself.\nexport const DEFAULT_FUNCTIONS_DIR = \"helipod\";\n\nexport interface HelipodVitePluginOptions {\n /**\n * How the backend is run alongside Vite:\n * - `\"proxy\"` (default): spawn `helipod dev` as a child and proxy the engine-owned prefixes to\n * it (Phase 1 — unchanged). Node builtins only; no `@helipod/*` runtime dependency.\n * - `\"embed\"`: boot the engine INSIDE Vite's own process as connect-middleware + a `/api/sync`\n * WebSocket — no child, no proxy hop (Phase 2). Reaches `@helipod/cli` via a dynamic import,\n * so proxy-mode users never pull it. See `docs/superpowers/specs/2026-05-15-vite-phase2-embed-design.md`.\n */\n mode?: \"proxy\" | \"embed\";\n /** App functions dir → `--dir` (default \"helipod\"). Shared by both modes. */\n functionsDir?: string;\n\n // ── proxy mode ──────────────────────────────────────────────────────────────────────────────\n /** Backend port to proxy to (default: an OS-assigned free port). */\n port?: number;\n /** How to invoke the CLI (default: local node_modules/.bin/helipod, else `npx helipod`). */\n command?: string;\n /** Extra flags forwarded to `helipod dev` (e.g. [\"--database-url\", \"postgres://…\"]). */\n args?: string[];\n\n // ── embed mode ──────────────────────────────────────────────────────────────────────────────\n /** SQLite file for the in-process engine (default `<root>/.helipod/dev.db`). Ignored in proxy mode. */\n dataPath?: string;\n /** Postgres connection string for the in-process engine (opt-in; SQLite otherwise). Ignored in proxy mode. */\n databaseUrl?: string;\n /** Admin key for the in-process engine's `/_admin` API (default: a per-run ephemeral key). Ignored in proxy mode. */\n adminKey?: string;\n}\n\n/** Adapt node's `spawn` to the SpawnFn seam. */\nconst nodeSpawn: SpawnFn = (command, args, opts) =>\n nodeChildSpawn(command, args, { cwd: opts.cwd, env: opts.env, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\n/**\n * Single-origin dev: `vite` alone serves the frontend AND the Helipod backend on one browser origin\n * (no manual proxy, no CORS). Two modes behind one surface (default `\"proxy\"` — Phase 1, unchanged):\n * - `\"proxy\"`: spawn `helipod dev` and proxy the engine-owned prefixes to it.\n * - `\"embed\"`: boot the engine in Vite's own process (no child, no proxy hop).\n */\nexport function helipod(options: HelipodVitePluginOptions = {}): Plugin {\n if ((options.mode ?? \"proxy\") === \"embed\") return embedPlugin(options);\n return proxyPlugin(options);\n}\n\n/** Phase 1 (default): spawn `helipod dev` as a child and inject Vite's proxy. Byte-for-byte the\n * behavior that shipped in Phase 1 — the only change is that it's now reached via the mode switch. */\nfunction proxyPlugin(options: HelipodVitePluginOptions): Plugin {\n let port: number;\n let backend: Backend | undefined;\n return {\n name: \"helipod\",\n async config() {\n port = options.port ?? (await freePort());\n const target = `http://127.0.0.1:${port}`;\n return {\n server: {\n proxy: {\n \"/api\": { target, ws: true, changeOrigin: true },\n \"/_dashboard\": { target, changeOrigin: true },\n \"/_admin\": { target, changeOrigin: true },\n },\n },\n };\n },\n async configureServer(server) {\n const root = server.config.root;\n const cli = resolveCli(root, options.command);\n const args = buildDevArgs(cli.baseArgs, port, options.functionsDir ?? DEFAULT_FUNCTIONS_DIR, options.args ?? []);\n backend = await startBackend(\n { command: cli.command, args, cwd: root, port, onLog: (l) => server.config.logger.info(`[helipod] ${l}`) },\n { spawn: nodeSpawn, probe: probePort },\n );\n const stop = () => backend?.stop();\n server.httpServer?.once(\"close\", stop);\n installSignalCleanup(stop);\n },\n };\n}\n"],"mappings":"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","names":["resolve","resolve","join","DEFAULT_FUNCTIONS_DIR"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@helipod/vite",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"test": "vitest run --exclude 'test/*-e2e.test.ts'",
|
|
16
|
+
"test:e2e": "vitest run test/*-e2e.test.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"clean": "rm -rf dist .turbo"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"vite": ">=5.0.0",
|
|
22
|
+
"@helipod/cli": "0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependenciesMeta": {
|
|
25
|
+
"@helipod/cli": {
|
|
26
|
+
"optional": true
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@helipod/cli": "0.1.0",
|
|
31
|
+
"@helipod/client": "0.1.0",
|
|
32
|
+
"@helipod/executor": "0.1.0",
|
|
33
|
+
"@helipod/id-codec": "0.1.0",
|
|
34
|
+
"@helipod/values": "0.1.0",
|
|
35
|
+
"@types/node": "^22.10.5",
|
|
36
|
+
"@types/ws": "^8.5.13",
|
|
37
|
+
"tsup": "^8.3.5",
|
|
38
|
+
"typescript": "^5.7.2",
|
|
39
|
+
"vite": "^6.0.7",
|
|
40
|
+
"vitest": "^2.1.8",
|
|
41
|
+
"ws": "^8.18.0"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
],
|
|
46
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/helipod-sh/helipod.git",
|
|
53
|
+
"directory": "packages/vite"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/helipod-sh/helipod"
|
|
56
|
+
}
|