everything-dev 1.6.0 → 1.7.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/api-contract.cjs +55 -8
- package/dist/api-contract.cjs.map +1 -1
- package/dist/api-contract.mjs +55 -8
- package/dist/api-contract.mjs.map +1 -1
- package/dist/app.cjs +26 -2
- package/dist/app.cjs.map +1 -1
- package/dist/app.mjs +27 -3
- package/dist/app.mjs.map +1 -1
- package/dist/cli/init.cjs +4 -4
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.mjs +4 -4
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/sync.cjs +2 -2
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +2 -2
- package/dist/cli/sync.mjs.map +1 -1
- package/dist/cli.cjs +0 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +0 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/components/streaming-view.cjs +0 -18
- package/dist/components/streaming-view.cjs.map +1 -1
- package/dist/components/streaming-view.mjs +0 -18
- package/dist/components/streaming-view.mjs.map +1 -1
- package/dist/config.cjs +21 -5
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +2 -1
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +2 -1
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +21 -6
- package/dist/config.mjs.map +1 -1
- package/dist/contract.cjs +8 -1
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +44 -8
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +44 -8
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.meta.cjs +1 -1
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +1 -1
- package/dist/contract.meta.d.mts +1 -1
- package/dist/contract.meta.mjs +1 -1
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +8 -1
- package/dist/contract.mjs.map +1 -1
- package/dist/dev-session.cjs +51 -66
- package/dist/dev-session.cjs.map +1 -1
- package/dist/dev-session.mjs +52 -67
- package/dist/dev-session.mjs.map +1 -1
- package/dist/fastkv.cjs +56 -0
- package/dist/fastkv.cjs.map +1 -1
- package/dist/fastkv.d.cts +45 -1
- package/dist/fastkv.d.cts.map +1 -1
- package/dist/fastkv.d.mts +45 -1
- package/dist/fastkv.d.mts.map +1 -1
- package/dist/fastkv.mjs +54 -1
- package/dist/fastkv.mjs.map +1 -1
- package/dist/host.cjs +1 -1
- package/dist/host.cjs.map +1 -1
- package/dist/host.mjs +1 -1
- package/dist/host.mjs.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +3 -3
- package/dist/near-cli.cjs +1 -1
- package/dist/near-cli.mjs +1 -1
- package/dist/orchestrator.cjs +55 -20
- package/dist/orchestrator.cjs.map +1 -1
- package/dist/orchestrator.d.cts +5 -4
- package/dist/orchestrator.d.cts.map +1 -1
- package/dist/orchestrator.d.mts +5 -4
- package/dist/orchestrator.d.mts.map +1 -1
- package/dist/orchestrator.mjs +55 -20
- package/dist/orchestrator.mjs.map +1 -1
- package/dist/plugin.cjs +135 -9
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +49 -8
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +49 -8
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +137 -11
- package/dist/plugin.mjs.map +1 -1
- package/dist/types.cjs +15 -5
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +60 -9
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +60 -9
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +15 -5
- package/dist/types.mjs.map +1 -1
- package/package.json +2 -2
- package/src/api-contract.ts +88 -9
- package/src/app.ts +55 -7
- package/src/cli/init.ts +6 -6
- package/src/cli/sync.ts +5 -3
- package/src/cli.ts +0 -1
- package/src/components/streaming-view.ts +0 -20
- package/src/config.ts +39 -23
- package/src/contract.meta.ts +4 -1
- package/src/contract.ts +7 -0
- package/src/dev-session.ts +85 -83
- package/src/fastkv.ts +95 -0
- package/src/host.ts +1 -1
- package/src/orchestrator.ts +61 -31
- package/src/plugin.ts +202 -5
- package/src/types.ts +38 -4
package/dist/dev-session.cjs
CHANGED
|
@@ -54,7 +54,8 @@ function formatLogLine(entry) {
|
|
|
54
54
|
const prefix = entry.isError ? "ERR" : "OUT";
|
|
55
55
|
return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;
|
|
56
56
|
}
|
|
57
|
-
const
|
|
57
|
+
const scopedProcess = (pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry) => effect.Effect.acquireRelease(require_orchestrator.makeDevProcess(pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry), (handle) => handle.kill.pipe(effect.Effect.ignore));
|
|
58
|
+
const runDevSession = (orchestrator, onShutdownReady) => effect.Effect.gen(function* () {
|
|
58
59
|
const configDir = require_config.getProjectRoot();
|
|
59
60
|
const orderedPackages = sortByOrder(orchestrator.packages);
|
|
60
61
|
const initialProcesses = orderedPackages.map((pkg) => {
|
|
@@ -70,41 +71,18 @@ const runDevSession = (orchestrator, onCleanupReady) => effect.Effect.gen(functi
|
|
|
70
71
|
const registry = yield* require_process_registry.makeProcessRegistry(configDir);
|
|
71
72
|
yield* registry.killAll().pipe(effect.Effect.ignore);
|
|
72
73
|
const logger = yield* effect.Effect.promise(() => require_dev_logs.createDevLogger(configDir, orchestrator.description));
|
|
73
|
-
const
|
|
74
|
+
const shutdown = yield* effect.Deferred.make();
|
|
75
|
+
onShutdownReady?.(() => {
|
|
76
|
+
effect.Effect.runPromise(effect.Deferred.succeed(shutdown, void 0));
|
|
77
|
+
});
|
|
74
78
|
const allLogs = [];
|
|
75
79
|
let view = null;
|
|
76
|
-
let
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
await handle.kill();
|
|
81
|
-
} catch {}
|
|
82
|
-
await effect.Effect.runPromise(registry.killAll(true)).catch(() => {});
|
|
83
|
-
};
|
|
84
|
-
const exportLogs = async () => {
|
|
85
|
-
console.log("\n");
|
|
86
|
-
console.log("═".repeat(70));
|
|
87
|
-
console.log(` SESSION LOGS: ${orchestrator.description}`);
|
|
88
|
-
console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);
|
|
89
|
-
console.log(` Total entries: ${allLogs.length}`);
|
|
90
|
-
console.log("═".repeat(70));
|
|
91
|
-
console.log("");
|
|
92
|
-
for (const entry of allLogs) console.log(formatLogLine(entry));
|
|
93
|
-
console.log("");
|
|
94
|
-
console.log("═".repeat(70));
|
|
95
|
-
console.log(` Full logs saved to: ${logger.logFile}`);
|
|
96
|
-
console.log("═".repeat(70));
|
|
97
|
-
console.log("");
|
|
98
|
-
};
|
|
99
|
-
const cleanup = async (showLogs = false) => {
|
|
100
|
-
if (shuttingDown) return;
|
|
101
|
-
shuttingDown = true;
|
|
102
|
-
view?.unmount();
|
|
103
|
-
await killAll();
|
|
104
|
-
if (showLogs) await exportLogs();
|
|
80
|
+
let shouldExportLogs = false;
|
|
81
|
+
const requestShutdownAndExport = () => {
|
|
82
|
+
shouldExportLogs = true;
|
|
83
|
+
effect.Effect.runPromise(effect.Deferred.succeed(shutdown, void 0));
|
|
105
84
|
};
|
|
106
|
-
|
|
107
|
-
view = orchestrator.interactive ?? isInteractiveSupported() ? require_dev_view.renderDevView(initialProcesses, orchestrator.description, orchestrator.env, () => cleanup(false), () => cleanup(true)) : require_streaming_view.renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () => cleanup(false));
|
|
85
|
+
view = orchestrator.interactive ?? isInteractiveSupported() ? require_dev_view.renderDevView(initialProcesses, orchestrator.description, orchestrator.env, () => void effect.Effect.runPromise(effect.Deferred.succeed(shutdown, void 0)), requestShutdownAndExport) : require_streaming_view.renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () => void effect.Effect.runPromise(effect.Deferred.succeed(shutdown, void 0)));
|
|
108
86
|
const callbacks = {
|
|
109
87
|
onStatus: (name, status, message) => {
|
|
110
88
|
view?.updateProcess(name, status, message);
|
|
@@ -124,7 +102,7 @@ const runDevSession = (orchestrator, onCleanupReady) => effect.Effect.gen(functi
|
|
|
124
102
|
};
|
|
125
103
|
const startProcess = (pkg) => {
|
|
126
104
|
const portOverride = pkg === "host" ? orchestrator.port : void 0;
|
|
127
|
-
return
|
|
105
|
+
return scopedProcess(pkg, orchestrator.env, callbacks, portOverride, orchestrator.bosConfig, orchestrator.runtimeConfig, registry);
|
|
128
106
|
};
|
|
129
107
|
const startGroup = (packages) => effect.Effect.forEach(packages, startProcess, { concurrency: "unbounded" });
|
|
130
108
|
const awaitReady = (pkg, handle) => effect.Effect.race(handle.waitForReady, effect.Effect.sleep("30 seconds").pipe(effect.Effect.andThen(effect.Effect.sync(() => {
|
|
@@ -133,61 +111,68 @@ const runDevSession = (orchestrator, onCleanupReady) => effect.Effect.gen(functi
|
|
|
133
111
|
const nonHostPackages = orderedPackages.filter((pkg) => pkg !== "host");
|
|
134
112
|
const hostPackages = orderedPackages.filter((pkg) => pkg === "host");
|
|
135
113
|
const nonHostHandles = yield* startGroup(nonHostPackages);
|
|
136
|
-
handles.push(...nonHostHandles);
|
|
137
114
|
yield* effect.Effect.forEach(nonHostHandles.map((handle, index) => ({
|
|
138
115
|
handle,
|
|
139
116
|
pkg: nonHostPackages[index] ?? handle.name
|
|
140
117
|
})), ({ handle, pkg }) => awaitReady(pkg, handle), { concurrency: "unbounded" });
|
|
141
118
|
const hostHandles = yield* startGroup(hostPackages);
|
|
142
|
-
handles.push(...hostHandles);
|
|
143
119
|
yield* effect.Effect.forEach(hostHandles.map((handle, index) => ({
|
|
144
120
|
handle,
|
|
145
121
|
pkg: hostPackages[index] ?? handle.name
|
|
146
122
|
})), ({ handle, pkg }) => awaitReady(pkg, handle), { concurrency: "unbounded" });
|
|
147
|
-
yield* effect.Effect.addFinalizer(() => effect.Effect.
|
|
148
|
-
|
|
123
|
+
yield* effect.Effect.addFinalizer(() => effect.Effect.gen(function* () {
|
|
124
|
+
view?.unmount();
|
|
125
|
+
if (shouldExportLogs) {
|
|
126
|
+
console.log("\n");
|
|
127
|
+
console.log("═".repeat(70));
|
|
128
|
+
console.log(` SESSION LOGS: ${orchestrator.description}`);
|
|
129
|
+
console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);
|
|
130
|
+
console.log(` Total entries: ${allLogs.length}`);
|
|
131
|
+
console.log("═".repeat(70));
|
|
132
|
+
console.log("");
|
|
133
|
+
for (const entry of allLogs) console.log(formatLogLine(entry));
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log("═".repeat(70));
|
|
136
|
+
console.log(` Full logs saved to: ${logger.logFile}`);
|
|
137
|
+
console.log("═".repeat(70));
|
|
138
|
+
console.log("");
|
|
139
|
+
}
|
|
140
|
+
yield* registry.killAll(true).pipe(effect.Effect.ignore);
|
|
141
|
+
}));
|
|
142
|
+
yield* effect.Deferred.await(shutdown);
|
|
149
143
|
});
|
|
150
144
|
const startApp = (orchestrator) => {
|
|
151
|
-
let
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
let requestShutdown = null;
|
|
146
|
+
let signalCount = 0;
|
|
147
|
+
let forceExitTimer = null;
|
|
148
|
+
const forceExit = () => {
|
|
149
|
+
console.log("\n[Dev] Force exit");
|
|
150
|
+
process.exit(0);
|
|
151
|
+
};
|
|
152
|
+
const program = effect.Effect.scoped(runDevSession(orchestrator, (shutdown) => {
|
|
153
|
+
requestShutdown = shutdown;
|
|
154
154
|
})).pipe(effect.Effect.catchAll((e) => effect.Effect.sync(() => {
|
|
155
155
|
if (e instanceof Error) {
|
|
156
156
|
console.error("App server error:", e.message);
|
|
157
157
|
if (e.stack) console.error(e.stack);
|
|
158
158
|
} else console.error("App server error:", e);
|
|
159
159
|
})));
|
|
160
|
-
const handleSignal =
|
|
161
|
-
if (activeCleanup) await activeCleanup();
|
|
162
|
-
};
|
|
163
|
-
const forceExit = () => {
|
|
164
|
-
console.log("\n[Dev] Force exit");
|
|
165
|
-
process.exit(0);
|
|
166
|
-
};
|
|
167
|
-
let signalCount = 0;
|
|
168
|
-
process.on("SIGINT", () => {
|
|
169
|
-
signalCount++;
|
|
170
|
-
if (signalCount > 1) {
|
|
171
|
-
forceExit();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const timeout = setTimeout(forceExit, 5e3);
|
|
175
|
-
handleSignal().finally(() => {
|
|
176
|
-
clearTimeout(timeout);
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
process.on("SIGTERM", () => {
|
|
160
|
+
const handleSignal = () => {
|
|
180
161
|
signalCount++;
|
|
181
162
|
if (signalCount > 1) {
|
|
182
163
|
forceExit();
|
|
183
164
|
return;
|
|
184
165
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
166
|
+
console.log("\n[Dev] Shutting down...");
|
|
167
|
+
forceExitTimer = setTimeout(forceExit, 8e3);
|
|
168
|
+
requestShutdown?.();
|
|
169
|
+
};
|
|
170
|
+
process.on("SIGINT", handleSignal);
|
|
171
|
+
process.on("SIGTERM", handleSignal);
|
|
172
|
+
effect.Effect.runPromiseExit(program).then((exit) => {
|
|
173
|
+
if (forceExitTimer) clearTimeout(forceExitTimer);
|
|
174
|
+
process.exit(effect.Exit.isSuccess(exit) ? 0 : 0);
|
|
189
175
|
});
|
|
190
|
-
effect.Effect.runPromise(program);
|
|
191
176
|
};
|
|
192
177
|
|
|
193
178
|
//#endregion
|
package/dist/dev-session.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-session.cjs","names":["Effect","getProjectRoot","getProcessConfig","makeProcessRegistry","createDevLogger","renderDevView","renderStreamingView","makeDevProcess"],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Effect } from \"effect\";\nimport {\n type DevViewHandle,\n type LogEntry,\n type ProcessState,\n renderDevView,\n} from \"./components/dev-view\";\nimport { renderStreamingView } from \"./components/streaming-view\";\nimport { getProjectRoot } from \"./config\";\nimport { createDevLogger } from \"./dev-logs\";\nimport {\n getProcessConfig,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { makeProcessRegistry } from \"./process-registry\";\nimport type { BosConfig, RuntimeConfig, SourceMode } from \"./types\";\n\nexport interface AppConfig {\n host: SourceMode;\n ui: SourceMode;\n api: SourceMode;\n proxy?: boolean;\n ssr?: boolean;\n}\n\nexport interface AppOrchestrator {\n packages: string[];\n env: Record<string, string>;\n description: string;\n appConfig: AppConfig;\n bosConfig: BosConfig;\n runtimeConfig: RuntimeConfig;\n port?: number;\n interactive?: boolean;\n noLogs?: boolean;\n}\n\nconst LOG_NOISE_PATTERNS = [\n /\\[ Federation Runtime \\] Version .* from (host|ui) of shared singleton module/,\n /Executing an Effect versioned \\d+\\.\\d+\\.\\d+ with a Runtime of version/,\n /you may want to dedupe the effect dependencies/,\n];\n\nconst SSR_LOG_ALLOWLIST = [\n /\\bready\\s+built in\\b/i,\n /\\bcompiled\\b.*successfully/i,\n /\\berror\\b/i,\n /\\bfailed\\b/i,\n /\\bexception\\b/i,\n];\n\nconst shouldDisplayLog = (source: string, line: string, isError?: boolean): boolean => {\n if (process.env.DEBUG === \"true\" || process.env.DEBUG === \"1\") return true;\n if (source === \"ui-ssr\") {\n if (isError) return true;\n return SSR_LOG_ALLOWLIST.some((pattern) => pattern.test(line));\n }\n return !LOG_NOISE_PATTERNS.some((pattern) => pattern.test(line));\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui-ssr\", \"ui\", \"api\", \"plugin\", \"host-build\", \"host\"];\n\nconst sortByOrder = (packages: string[]): string[] => {\n return [...packages].sort((a, b) => {\n const aIdx = a.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(a);\n const bIdx = b.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(b);\n if (aIdx === -1 && bIdx === -1) return 0;\n if (aIdx === -1) return 1;\n if (bIdx === -1) return -1;\n return aIdx - bIdx;\n });\n};\n\nfunction formatLogLine(entry: LogEntry): string {\n const ts = new Date(entry.timestamp).toISOString();\n const prefix = entry.isError ? \"ERR\" : \"OUT\";\n return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;\n}\n\nexport const runDevSession = (\n orchestrator: AppOrchestrator,\n onCleanupReady?: (cleanup: () => Promise<void>) => void,\n) =>\n Effect.gen(function* () {\n const configDir = getProjectRoot();\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: ProcessState[] = orderedPackages.map((pkg) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n const config = getProcessConfig(\n pkg,\n undefined,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n );\n const source =\n pkg === \"host\"\n ? orchestrator.appConfig.host\n : pkg === \"ui\"\n ? orchestrator.appConfig.ui\n : pkg === \"api\"\n ? orchestrator.appConfig.api\n : undefined;\n return {\n name: pkg,\n status: \"pending\" as const,\n port: config?.port ?? 0,\n source,\n };\n });\n\n const registry = yield* makeProcessRegistry(configDir);\n yield* registry.killAll().pipe(Effect.ignore);\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n const handles: ProcessHandle[] = [];\n const allLogs: LogEntry[] = [];\n let view: DevViewHandle | null = null;\n let shuttingDown = false;\n\n const killAll = async () => {\n const reversed = [...handles].reverse();\n for (const handle of reversed) {\n try {\n await handle.kill();\n } catch {}\n }\n await Effect.runPromise(registry.killAll(true)).catch(() => {});\n };\n\n const exportLogs = async () => {\n console.log(\"\\n\");\n console.log(\"═\".repeat(70));\n console.log(` SESSION LOGS: ${orchestrator.description}`);\n console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);\n console.log(` Total entries: ${allLogs.length}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n for (const entry of allLogs) {\n console.log(formatLogLine(entry));\n }\n console.log(\"\");\n console.log(\"═\".repeat(70));\n console.log(` Full logs saved to: ${logger.logFile}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n };\n\n const cleanup = async (showLogs = false) => {\n if (shuttingDown) return;\n shuttingDown = true;\n view?.unmount();\n await killAll();\n if (showLogs) {\n await exportLogs();\n }\n };\n\n onCleanupReady?.(cleanup);\n\n const useInteractive = orchestrator.interactive ?? isInteractiveSupported();\n view = useInteractive\n ? renderDevView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => cleanup(false),\n () => cleanup(true),\n )\n : renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () =>\n cleanup(false),\n );\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n const entry: LogEntry = {\n id: `${Date.now()}-${allLogs.length + 1}`,\n source: name,\n line,\n timestamp: Date.now(),\n isError,\n };\n allLogs.push(entry);\n if (shouldDisplayLog(name, line, isError)) {\n view?.addLog(name, line, isError);\n }\n if (!orchestrator.noLogs) {\n void logger.write(entry);\n }\n },\n };\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return makeDevProcess(\n pkg,\n orchestrator.env,\n callbacks,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n registry,\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n Effect.race(\n handle.waitForReady,\n Effect.sleep(\"30 seconds\").pipe(\n Effect.andThen(\n Effect.sync(() => {\n callbacks.onLog(pkg, \"Timeout waiting for ready, continuing...\", true);\n }),\n ),\n ),\n );\n\n const nonHostPackages = orderedPackages.filter((pkg) => pkg !== \"host\");\n const hostPackages = orderedPackages.filter((pkg) => pkg === \"host\");\n\n const nonHostHandles = yield* startGroup(nonHostPackages);\n handles.push(...nonHostHandles);\n\n yield* Effect.forEach(\n nonHostHandles.map((handle, index) => ({\n handle,\n pkg: nonHostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n const hostHandles = yield* startGroup(hostPackages);\n handles.push(...hostHandles);\n\n yield* Effect.forEach(\n hostHandles.map((handle, index) => ({ handle, pkg: hostPackages[index] ?? handle.name })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n yield* Effect.addFinalizer(() => Effect.promise(() => cleanup(false)));\n yield* Effect.never;\n });\n\nexport const startApp = (orchestrator: AppOrchestrator) => {\n let activeCleanup: (() => Promise<void>) | null = null;\n\n const program = Effect.scoped(\n runDevSession(orchestrator, (cleanup) => {\n activeCleanup = cleanup;\n }),\n ).pipe(\n Effect.catchAll((e) =>\n Effect.sync(() => {\n if (e instanceof Error) {\n console.error(\"App server error:\", e.message);\n if (e.stack) console.error(e.stack);\n } else {\n console.error(\"App server error:\", e);\n }\n }),\n ),\n );\n\n const handleSignal = async () => {\n if (activeCleanup) await activeCleanup();\n };\n\n const forceExit = () => {\n console.log(\"\\n[Dev] Force exit\");\n process.exit(0);\n };\n\n let signalCount = 0;\n process.on(\"SIGINT\", () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n const timeout = setTimeout(forceExit, 5000);\n void handleSignal().finally(() => {\n clearTimeout(timeout);\n });\n });\n process.on(\"SIGTERM\", () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n const timeout = setTimeout(forceExit, 5000);\n void handleSignal().finally(() => {\n clearTimeout(timeout);\n });\n });\n\n void Effect.runPromise(program);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dev-session.cjs","names":["Effect","makeDevProcess","getProjectRoot","getProcessConfig","makeProcessRegistry","createDevLogger","Deferred","renderDevView","renderStreamingView","Exit"],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Deferred, Effect, Exit } from \"effect\";\nimport {\n type DevViewHandle,\n type LogEntry,\n type ProcessState,\n renderDevView,\n} from \"./components/dev-view\";\nimport { renderStreamingView } from \"./components/streaming-view\";\nimport { getProjectRoot } from \"./config\";\nimport { createDevLogger } from \"./dev-logs\";\nimport {\n getProcessConfig,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { makeProcessRegistry, type ProcessRegistry } from \"./process-registry\";\nimport type { BosConfig, RuntimeConfig, SourceMode } from \"./types\";\n\nexport interface AppConfig {\n host: SourceMode;\n ui: SourceMode;\n api: SourceMode;\n proxy?: boolean;\n ssr?: boolean;\n}\n\nexport interface AppOrchestrator {\n packages: string[];\n env: Record<string, string>;\n description: string;\n appConfig: AppConfig;\n bosConfig: BosConfig;\n runtimeConfig: RuntimeConfig;\n port?: number;\n interactive?: boolean;\n noLogs?: boolean;\n}\n\nconst LOG_NOISE_PATTERNS = [\n /\\[ Federation Runtime \\] Version .* from (host|ui) of shared singleton module/,\n /Executing an Effect versioned \\d+\\.\\d+\\.\\d+ with a Runtime of version/,\n /you may want to dedupe the effect dependencies/,\n];\n\nconst SSR_LOG_ALLOWLIST = [\n /\\bready\\s+built in\\b/i,\n /\\bcompiled\\b.*successfully/i,\n /\\berror\\b/i,\n /\\bfailed\\b/i,\n /\\bexception\\b/i,\n];\n\nconst shouldDisplayLog = (source: string, line: string, isError?: boolean): boolean => {\n if (process.env.DEBUG === \"true\" || process.env.DEBUG === \"1\") return true;\n if (source === \"ui-ssr\") {\n if (isError) return true;\n return SSR_LOG_ALLOWLIST.some((pattern) => pattern.test(line));\n }\n return !LOG_NOISE_PATTERNS.some((pattern) => pattern.test(line));\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui-ssr\", \"ui\", \"api\", \"plugin\", \"host-build\", \"host\"];\n\nconst sortByOrder = (packages: string[]): string[] => {\n return [...packages].sort((a, b) => {\n const aIdx = a.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(a);\n const bIdx = b.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(b);\n if (aIdx === -1 && bIdx === -1) return 0;\n if (aIdx === -1) return 1;\n if (bIdx === -1) return -1;\n return aIdx - bIdx;\n });\n};\n\nfunction formatLogLine(entry: LogEntry): string {\n const ts = new Date(entry.timestamp).toISOString();\n const prefix = entry.isError ? \"ERR\" : \"OUT\";\n return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;\n}\n\nconst scopedProcess = (\n pkg: string,\n env: Record<string, string> | undefined,\n callbacks: ProcessCallbacks,\n portOverride: number | undefined,\n bosConfig: BosConfig | undefined,\n runtimeConfig: RuntimeConfig | undefined,\n registry: ProcessRegistry | undefined,\n) =>\n Effect.acquireRelease(\n makeDevProcess(pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry),\n (handle) => handle.kill.pipe(Effect.ignore),\n );\n\nexport const runDevSession = (\n orchestrator: AppOrchestrator,\n onShutdownReady?: (requestShutdown: () => void) => void,\n) =>\n Effect.gen(function* () {\n const configDir = getProjectRoot();\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: ProcessState[] = orderedPackages.map((pkg) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n const config = getProcessConfig(\n pkg,\n undefined,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n );\n const source =\n pkg === \"host\"\n ? orchestrator.appConfig.host\n : pkg === \"ui\"\n ? orchestrator.appConfig.ui\n : pkg === \"api\"\n ? orchestrator.appConfig.api\n : undefined;\n return {\n name: pkg,\n status: \"pending\" as const,\n port: config?.port ?? 0,\n source,\n };\n });\n\n const registry = yield* makeProcessRegistry(configDir);\n yield* registry.killAll().pipe(Effect.ignore);\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n\n const shutdown = yield* Deferred.make<void>();\n\n onShutdownReady?.(() => {\n void Effect.runPromise(Deferred.succeed(shutdown, undefined));\n });\n\n const allLogs: LogEntry[] = [];\n let view: DevViewHandle | null = null;\n let shouldExportLogs = false;\n\n const requestShutdownAndExport = () => {\n shouldExportLogs = true;\n void Effect.runPromise(Deferred.succeed(shutdown, undefined));\n };\n\n const useInteractive = orchestrator.interactive ?? isInteractiveSupported();\n view = useInteractive\n ? renderDevView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => void Effect.runPromise(Deferred.succeed(shutdown, undefined)),\n requestShutdownAndExport,\n )\n : renderStreamingView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => void Effect.runPromise(Deferred.succeed(shutdown, undefined)),\n );\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n const entry: LogEntry = {\n id: `${Date.now()}-${allLogs.length + 1}`,\n source: name,\n line,\n timestamp: Date.now(),\n isError,\n };\n allLogs.push(entry);\n if (shouldDisplayLog(name, line, isError)) {\n view?.addLog(name, line, isError);\n }\n if (!orchestrator.noLogs) {\n void logger.write(entry);\n }\n },\n };\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return scopedProcess(\n pkg,\n orchestrator.env,\n callbacks,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n registry,\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n Effect.race(\n handle.waitForReady,\n Effect.sleep(\"30 seconds\").pipe(\n Effect.andThen(\n Effect.sync(() => {\n callbacks.onLog(pkg, \"Timeout waiting for ready, continuing...\", true);\n }),\n ),\n ),\n );\n\n const nonHostPackages = orderedPackages.filter((pkg) => pkg !== \"host\");\n const hostPackages = orderedPackages.filter((pkg) => pkg === \"host\");\n\n const nonHostHandles = yield* startGroup(nonHostPackages);\n\n yield* Effect.forEach(\n nonHostHandles.map((handle, index) => ({\n handle,\n pkg: nonHostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n const hostHandles = yield* startGroup(hostPackages);\n\n yield* Effect.forEach(\n hostHandles.map((handle, index) => ({ handle, pkg: hostPackages[index] ?? handle.name })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n view?.unmount();\n\n if (shouldExportLogs) {\n console.log(\"\\n\");\n console.log(\"═\".repeat(70));\n console.log(` SESSION LOGS: ${orchestrator.description}`);\n console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);\n console.log(` Total entries: ${allLogs.length}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n for (const entry of allLogs) {\n console.log(formatLogLine(entry));\n }\n console.log(\"\");\n console.log(\"═\".repeat(70));\n console.log(` Full logs saved to: ${logger.logFile}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n }\n\n yield* registry.killAll(true).pipe(Effect.ignore);\n }),\n );\n\n yield* Deferred.await(shutdown);\n });\n\nexport const startApp = (orchestrator: AppOrchestrator) => {\n let requestShutdown: (() => void) | null = null;\n let signalCount = 0;\n let forceExitTimer: ReturnType<typeof setTimeout> | null = null;\n\n const forceExit = () => {\n console.log(\"\\n[Dev] Force exit\");\n process.exit(0);\n };\n\n const program = Effect.scoped(\n runDevSession(orchestrator, (shutdown) => {\n requestShutdown = shutdown;\n }),\n ).pipe(\n Effect.catchAll((e) =>\n Effect.sync(() => {\n if (e instanceof Error) {\n console.error(\"App server error:\", e.message);\n if (e.stack) console.error(e.stack);\n } else {\n console.error(\"App server error:\", e);\n }\n }),\n ),\n );\n\n const handleSignal = () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n console.log(\"\\n[Dev] Shutting down...\");\n forceExitTimer = setTimeout(forceExit, 8000);\n requestShutdown?.();\n };\n\n process.on(\"SIGINT\", handleSignal);\n process.on(\"SIGTERM\", handleSignal);\n\n Effect.runPromiseExit(program).then((exit) => {\n if (forceExitTimer) clearTimeout(forceExitTimer);\n process.exit(Exit.isSuccess(exit) ? 0 : 0);\n });\n};\n"],"mappings":"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"}
|
package/dist/dev-session.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { renderDevView } from "./components/dev-view.mjs";
|
|
|
4
4
|
import { renderStreamingView } from "./components/streaming-view.mjs";
|
|
5
5
|
import { createDevLogger } from "./dev-logs.mjs";
|
|
6
6
|
import { makeProcessRegistry } from "./process-registry.mjs";
|
|
7
|
-
import { Effect } from "effect";
|
|
7
|
+
import { Deferred, Effect, Exit } from "effect";
|
|
8
8
|
|
|
9
9
|
//#region src/dev-session.ts
|
|
10
10
|
const LOG_NOISE_PATTERNS = [
|
|
@@ -53,7 +53,8 @@ function formatLogLine(entry) {
|
|
|
53
53
|
const prefix = entry.isError ? "ERR" : "OUT";
|
|
54
54
|
return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;
|
|
55
55
|
}
|
|
56
|
-
const
|
|
56
|
+
const scopedProcess = (pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry) => Effect.acquireRelease(makeDevProcess(pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry), (handle) => handle.kill.pipe(Effect.ignore));
|
|
57
|
+
const runDevSession = (orchestrator, onShutdownReady) => Effect.gen(function* () {
|
|
57
58
|
const configDir = getProjectRoot();
|
|
58
59
|
const orderedPackages = sortByOrder(orchestrator.packages);
|
|
59
60
|
const initialProcesses = orderedPackages.map((pkg) => {
|
|
@@ -69,41 +70,18 @@ const runDevSession = (orchestrator, onCleanupReady) => Effect.gen(function* ()
|
|
|
69
70
|
const registry = yield* makeProcessRegistry(configDir);
|
|
70
71
|
yield* registry.killAll().pipe(Effect.ignore);
|
|
71
72
|
const logger = yield* Effect.promise(() => createDevLogger(configDir, orchestrator.description));
|
|
72
|
-
const
|
|
73
|
+
const shutdown = yield* Deferred.make();
|
|
74
|
+
onShutdownReady?.(() => {
|
|
75
|
+
Effect.runPromise(Deferred.succeed(shutdown, void 0));
|
|
76
|
+
});
|
|
73
77
|
const allLogs = [];
|
|
74
78
|
let view = null;
|
|
75
|
-
let
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
await handle.kill();
|
|
80
|
-
} catch {}
|
|
81
|
-
await Effect.runPromise(registry.killAll(true)).catch(() => {});
|
|
82
|
-
};
|
|
83
|
-
const exportLogs = async () => {
|
|
84
|
-
console.log("\n");
|
|
85
|
-
console.log("═".repeat(70));
|
|
86
|
-
console.log(` SESSION LOGS: ${orchestrator.description}`);
|
|
87
|
-
console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);
|
|
88
|
-
console.log(` Total entries: ${allLogs.length}`);
|
|
89
|
-
console.log("═".repeat(70));
|
|
90
|
-
console.log("");
|
|
91
|
-
for (const entry of allLogs) console.log(formatLogLine(entry));
|
|
92
|
-
console.log("");
|
|
93
|
-
console.log("═".repeat(70));
|
|
94
|
-
console.log(` Full logs saved to: ${logger.logFile}`);
|
|
95
|
-
console.log("═".repeat(70));
|
|
96
|
-
console.log("");
|
|
97
|
-
};
|
|
98
|
-
const cleanup = async (showLogs = false) => {
|
|
99
|
-
if (shuttingDown) return;
|
|
100
|
-
shuttingDown = true;
|
|
101
|
-
view?.unmount();
|
|
102
|
-
await killAll();
|
|
103
|
-
if (showLogs) await exportLogs();
|
|
79
|
+
let shouldExportLogs = false;
|
|
80
|
+
const requestShutdownAndExport = () => {
|
|
81
|
+
shouldExportLogs = true;
|
|
82
|
+
Effect.runPromise(Deferred.succeed(shutdown, void 0));
|
|
104
83
|
};
|
|
105
|
-
|
|
106
|
-
view = orchestrator.interactive ?? isInteractiveSupported() ? renderDevView(initialProcesses, orchestrator.description, orchestrator.env, () => cleanup(false), () => cleanup(true)) : renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () => cleanup(false));
|
|
84
|
+
view = orchestrator.interactive ?? isInteractiveSupported() ? renderDevView(initialProcesses, orchestrator.description, orchestrator.env, () => void Effect.runPromise(Deferred.succeed(shutdown, void 0)), requestShutdownAndExport) : renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () => void Effect.runPromise(Deferred.succeed(shutdown, void 0)));
|
|
107
85
|
const callbacks = {
|
|
108
86
|
onStatus: (name, status, message) => {
|
|
109
87
|
view?.updateProcess(name, status, message);
|
|
@@ -123,7 +101,7 @@ const runDevSession = (orchestrator, onCleanupReady) => Effect.gen(function* ()
|
|
|
123
101
|
};
|
|
124
102
|
const startProcess = (pkg) => {
|
|
125
103
|
const portOverride = pkg === "host" ? orchestrator.port : void 0;
|
|
126
|
-
return
|
|
104
|
+
return scopedProcess(pkg, orchestrator.env, callbacks, portOverride, orchestrator.bosConfig, orchestrator.runtimeConfig, registry);
|
|
127
105
|
};
|
|
128
106
|
const startGroup = (packages) => Effect.forEach(packages, startProcess, { concurrency: "unbounded" });
|
|
129
107
|
const awaitReady = (pkg, handle) => Effect.race(handle.waitForReady, Effect.sleep("30 seconds").pipe(Effect.andThen(Effect.sync(() => {
|
|
@@ -132,61 +110,68 @@ const runDevSession = (orchestrator, onCleanupReady) => Effect.gen(function* ()
|
|
|
132
110
|
const nonHostPackages = orderedPackages.filter((pkg) => pkg !== "host");
|
|
133
111
|
const hostPackages = orderedPackages.filter((pkg) => pkg === "host");
|
|
134
112
|
const nonHostHandles = yield* startGroup(nonHostPackages);
|
|
135
|
-
handles.push(...nonHostHandles);
|
|
136
113
|
yield* Effect.forEach(nonHostHandles.map((handle, index) => ({
|
|
137
114
|
handle,
|
|
138
115
|
pkg: nonHostPackages[index] ?? handle.name
|
|
139
116
|
})), ({ handle, pkg }) => awaitReady(pkg, handle), { concurrency: "unbounded" });
|
|
140
117
|
const hostHandles = yield* startGroup(hostPackages);
|
|
141
|
-
handles.push(...hostHandles);
|
|
142
118
|
yield* Effect.forEach(hostHandles.map((handle, index) => ({
|
|
143
119
|
handle,
|
|
144
120
|
pkg: hostPackages[index] ?? handle.name
|
|
145
121
|
})), ({ handle, pkg }) => awaitReady(pkg, handle), { concurrency: "unbounded" });
|
|
146
|
-
yield* Effect.addFinalizer(() => Effect.
|
|
147
|
-
|
|
122
|
+
yield* Effect.addFinalizer(() => Effect.gen(function* () {
|
|
123
|
+
view?.unmount();
|
|
124
|
+
if (shouldExportLogs) {
|
|
125
|
+
console.log("\n");
|
|
126
|
+
console.log("═".repeat(70));
|
|
127
|
+
console.log(` SESSION LOGS: ${orchestrator.description}`);
|
|
128
|
+
console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);
|
|
129
|
+
console.log(` Total entries: ${allLogs.length}`);
|
|
130
|
+
console.log("═".repeat(70));
|
|
131
|
+
console.log("");
|
|
132
|
+
for (const entry of allLogs) console.log(formatLogLine(entry));
|
|
133
|
+
console.log("");
|
|
134
|
+
console.log("═".repeat(70));
|
|
135
|
+
console.log(` Full logs saved to: ${logger.logFile}`);
|
|
136
|
+
console.log("═".repeat(70));
|
|
137
|
+
console.log("");
|
|
138
|
+
}
|
|
139
|
+
yield* registry.killAll(true).pipe(Effect.ignore);
|
|
140
|
+
}));
|
|
141
|
+
yield* Deferred.await(shutdown);
|
|
148
142
|
});
|
|
149
143
|
const startApp = (orchestrator) => {
|
|
150
|
-
let
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
let requestShutdown = null;
|
|
145
|
+
let signalCount = 0;
|
|
146
|
+
let forceExitTimer = null;
|
|
147
|
+
const forceExit = () => {
|
|
148
|
+
console.log("\n[Dev] Force exit");
|
|
149
|
+
process.exit(0);
|
|
150
|
+
};
|
|
151
|
+
const program = Effect.scoped(runDevSession(orchestrator, (shutdown) => {
|
|
152
|
+
requestShutdown = shutdown;
|
|
153
153
|
})).pipe(Effect.catchAll((e) => Effect.sync(() => {
|
|
154
154
|
if (e instanceof Error) {
|
|
155
155
|
console.error("App server error:", e.message);
|
|
156
156
|
if (e.stack) console.error(e.stack);
|
|
157
157
|
} else console.error("App server error:", e);
|
|
158
158
|
})));
|
|
159
|
-
const handleSignal =
|
|
160
|
-
if (activeCleanup) await activeCleanup();
|
|
161
|
-
};
|
|
162
|
-
const forceExit = () => {
|
|
163
|
-
console.log("\n[Dev] Force exit");
|
|
164
|
-
process.exit(0);
|
|
165
|
-
};
|
|
166
|
-
let signalCount = 0;
|
|
167
|
-
process.on("SIGINT", () => {
|
|
168
|
-
signalCount++;
|
|
169
|
-
if (signalCount > 1) {
|
|
170
|
-
forceExit();
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const timeout = setTimeout(forceExit, 5e3);
|
|
174
|
-
handleSignal().finally(() => {
|
|
175
|
-
clearTimeout(timeout);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
process.on("SIGTERM", () => {
|
|
159
|
+
const handleSignal = () => {
|
|
179
160
|
signalCount++;
|
|
180
161
|
if (signalCount > 1) {
|
|
181
162
|
forceExit();
|
|
182
163
|
return;
|
|
183
164
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
165
|
+
console.log("\n[Dev] Shutting down...");
|
|
166
|
+
forceExitTimer = setTimeout(forceExit, 8e3);
|
|
167
|
+
requestShutdown?.();
|
|
168
|
+
};
|
|
169
|
+
process.on("SIGINT", handleSignal);
|
|
170
|
+
process.on("SIGTERM", handleSignal);
|
|
171
|
+
Effect.runPromiseExit(program).then((exit) => {
|
|
172
|
+
if (forceExitTimer) clearTimeout(forceExitTimer);
|
|
173
|
+
process.exit(Exit.isSuccess(exit) ? 0 : 0);
|
|
188
174
|
});
|
|
189
|
-
Effect.runPromise(program);
|
|
190
175
|
};
|
|
191
176
|
|
|
192
177
|
//#endregion
|
package/dist/dev-session.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-session.mjs","names":[],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Effect } from \"effect\";\nimport {\n type DevViewHandle,\n type LogEntry,\n type ProcessState,\n renderDevView,\n} from \"./components/dev-view\";\nimport { renderStreamingView } from \"./components/streaming-view\";\nimport { getProjectRoot } from \"./config\";\nimport { createDevLogger } from \"./dev-logs\";\nimport {\n getProcessConfig,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { makeProcessRegistry } from \"./process-registry\";\nimport type { BosConfig, RuntimeConfig, SourceMode } from \"./types\";\n\nexport interface AppConfig {\n host: SourceMode;\n ui: SourceMode;\n api: SourceMode;\n proxy?: boolean;\n ssr?: boolean;\n}\n\nexport interface AppOrchestrator {\n packages: string[];\n env: Record<string, string>;\n description: string;\n appConfig: AppConfig;\n bosConfig: BosConfig;\n runtimeConfig: RuntimeConfig;\n port?: number;\n interactive?: boolean;\n noLogs?: boolean;\n}\n\nconst LOG_NOISE_PATTERNS = [\n /\\[ Federation Runtime \\] Version .* from (host|ui) of shared singleton module/,\n /Executing an Effect versioned \\d+\\.\\d+\\.\\d+ with a Runtime of version/,\n /you may want to dedupe the effect dependencies/,\n];\n\nconst SSR_LOG_ALLOWLIST = [\n /\\bready\\s+built in\\b/i,\n /\\bcompiled\\b.*successfully/i,\n /\\berror\\b/i,\n /\\bfailed\\b/i,\n /\\bexception\\b/i,\n];\n\nconst shouldDisplayLog = (source: string, line: string, isError?: boolean): boolean => {\n if (process.env.DEBUG === \"true\" || process.env.DEBUG === \"1\") return true;\n if (source === \"ui-ssr\") {\n if (isError) return true;\n return SSR_LOG_ALLOWLIST.some((pattern) => pattern.test(line));\n }\n return !LOG_NOISE_PATTERNS.some((pattern) => pattern.test(line));\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui-ssr\", \"ui\", \"api\", \"plugin\", \"host-build\", \"host\"];\n\nconst sortByOrder = (packages: string[]): string[] => {\n return [...packages].sort((a, b) => {\n const aIdx = a.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(a);\n const bIdx = b.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(b);\n if (aIdx === -1 && bIdx === -1) return 0;\n if (aIdx === -1) return 1;\n if (bIdx === -1) return -1;\n return aIdx - bIdx;\n });\n};\n\nfunction formatLogLine(entry: LogEntry): string {\n const ts = new Date(entry.timestamp).toISOString();\n const prefix = entry.isError ? \"ERR\" : \"OUT\";\n return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;\n}\n\nexport const runDevSession = (\n orchestrator: AppOrchestrator,\n onCleanupReady?: (cleanup: () => Promise<void>) => void,\n) =>\n Effect.gen(function* () {\n const configDir = getProjectRoot();\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: ProcessState[] = orderedPackages.map((pkg) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n const config = getProcessConfig(\n pkg,\n undefined,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n );\n const source =\n pkg === \"host\"\n ? orchestrator.appConfig.host\n : pkg === \"ui\"\n ? orchestrator.appConfig.ui\n : pkg === \"api\"\n ? orchestrator.appConfig.api\n : undefined;\n return {\n name: pkg,\n status: \"pending\" as const,\n port: config?.port ?? 0,\n source,\n };\n });\n\n const registry = yield* makeProcessRegistry(configDir);\n yield* registry.killAll().pipe(Effect.ignore);\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n const handles: ProcessHandle[] = [];\n const allLogs: LogEntry[] = [];\n let view: DevViewHandle | null = null;\n let shuttingDown = false;\n\n const killAll = async () => {\n const reversed = [...handles].reverse();\n for (const handle of reversed) {\n try {\n await handle.kill();\n } catch {}\n }\n await Effect.runPromise(registry.killAll(true)).catch(() => {});\n };\n\n const exportLogs = async () => {\n console.log(\"\\n\");\n console.log(\"═\".repeat(70));\n console.log(` SESSION LOGS: ${orchestrator.description}`);\n console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);\n console.log(` Total entries: ${allLogs.length}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n for (const entry of allLogs) {\n console.log(formatLogLine(entry));\n }\n console.log(\"\");\n console.log(\"═\".repeat(70));\n console.log(` Full logs saved to: ${logger.logFile}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n };\n\n const cleanup = async (showLogs = false) => {\n if (shuttingDown) return;\n shuttingDown = true;\n view?.unmount();\n await killAll();\n if (showLogs) {\n await exportLogs();\n }\n };\n\n onCleanupReady?.(cleanup);\n\n const useInteractive = orchestrator.interactive ?? isInteractiveSupported();\n view = useInteractive\n ? renderDevView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => cleanup(false),\n () => cleanup(true),\n )\n : renderStreamingView(initialProcesses, orchestrator.description, orchestrator.env, () =>\n cleanup(false),\n );\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n const entry: LogEntry = {\n id: `${Date.now()}-${allLogs.length + 1}`,\n source: name,\n line,\n timestamp: Date.now(),\n isError,\n };\n allLogs.push(entry);\n if (shouldDisplayLog(name, line, isError)) {\n view?.addLog(name, line, isError);\n }\n if (!orchestrator.noLogs) {\n void logger.write(entry);\n }\n },\n };\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return makeDevProcess(\n pkg,\n orchestrator.env,\n callbacks,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n registry,\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n Effect.race(\n handle.waitForReady,\n Effect.sleep(\"30 seconds\").pipe(\n Effect.andThen(\n Effect.sync(() => {\n callbacks.onLog(pkg, \"Timeout waiting for ready, continuing...\", true);\n }),\n ),\n ),\n );\n\n const nonHostPackages = orderedPackages.filter((pkg) => pkg !== \"host\");\n const hostPackages = orderedPackages.filter((pkg) => pkg === \"host\");\n\n const nonHostHandles = yield* startGroup(nonHostPackages);\n handles.push(...nonHostHandles);\n\n yield* Effect.forEach(\n nonHostHandles.map((handle, index) => ({\n handle,\n pkg: nonHostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n const hostHandles = yield* startGroup(hostPackages);\n handles.push(...hostHandles);\n\n yield* Effect.forEach(\n hostHandles.map((handle, index) => ({ handle, pkg: hostPackages[index] ?? handle.name })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n yield* Effect.addFinalizer(() => Effect.promise(() => cleanup(false)));\n yield* Effect.never;\n });\n\nexport const startApp = (orchestrator: AppOrchestrator) => {\n let activeCleanup: (() => Promise<void>) | null = null;\n\n const program = Effect.scoped(\n runDevSession(orchestrator, (cleanup) => {\n activeCleanup = cleanup;\n }),\n ).pipe(\n Effect.catchAll((e) =>\n Effect.sync(() => {\n if (e instanceof Error) {\n console.error(\"App server error:\", e.message);\n if (e.stack) console.error(e.stack);\n } else {\n console.error(\"App server error:\", e);\n }\n }),\n ),\n );\n\n const handleSignal = async () => {\n if (activeCleanup) await activeCleanup();\n };\n\n const forceExit = () => {\n console.log(\"\\n[Dev] Force exit\");\n process.exit(0);\n };\n\n let signalCount = 0;\n process.on(\"SIGINT\", () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n const timeout = setTimeout(forceExit, 5000);\n void handleSignal().finally(() => {\n clearTimeout(timeout);\n });\n });\n process.on(\"SIGTERM\", () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n const timeout = setTimeout(forceExit, 5000);\n void handleSignal().finally(() => {\n clearTimeout(timeout);\n });\n });\n\n void Effect.runPromise(program);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dev-session.mjs","names":[],"sources":["../src/dev-session.ts"],"sourcesContent":["import { Deferred, Effect, Exit } from \"effect\";\nimport {\n type DevViewHandle,\n type LogEntry,\n type ProcessState,\n renderDevView,\n} from \"./components/dev-view\";\nimport { renderStreamingView } from \"./components/streaming-view\";\nimport { getProjectRoot } from \"./config\";\nimport { createDevLogger } from \"./dev-logs\";\nimport {\n getProcessConfig,\n makeDevProcess,\n type ProcessCallbacks,\n type ProcessHandle,\n} from \"./orchestrator\";\nimport { makeProcessRegistry, type ProcessRegistry } from \"./process-registry\";\nimport type { BosConfig, RuntimeConfig, SourceMode } from \"./types\";\n\nexport interface AppConfig {\n host: SourceMode;\n ui: SourceMode;\n api: SourceMode;\n proxy?: boolean;\n ssr?: boolean;\n}\n\nexport interface AppOrchestrator {\n packages: string[];\n env: Record<string, string>;\n description: string;\n appConfig: AppConfig;\n bosConfig: BosConfig;\n runtimeConfig: RuntimeConfig;\n port?: number;\n interactive?: boolean;\n noLogs?: boolean;\n}\n\nconst LOG_NOISE_PATTERNS = [\n /\\[ Federation Runtime \\] Version .* from (host|ui) of shared singleton module/,\n /Executing an Effect versioned \\d+\\.\\d+\\.\\d+ with a Runtime of version/,\n /you may want to dedupe the effect dependencies/,\n];\n\nconst SSR_LOG_ALLOWLIST = [\n /\\bready\\s+built in\\b/i,\n /\\bcompiled\\b.*successfully/i,\n /\\berror\\b/i,\n /\\bfailed\\b/i,\n /\\bexception\\b/i,\n];\n\nconst shouldDisplayLog = (source: string, line: string, isError?: boolean): boolean => {\n if (process.env.DEBUG === \"true\" || process.env.DEBUG === \"1\") return true;\n if (source === \"ui-ssr\") {\n if (isError) return true;\n return SSR_LOG_ALLOWLIST.some((pattern) => pattern.test(line));\n }\n return !LOG_NOISE_PATTERNS.some((pattern) => pattern.test(line));\n};\n\nconst isInteractiveSupported = (): boolean => {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n};\n\nconst STARTUP_ORDER = [\"ui-ssr\", \"ui\", \"api\", \"plugin\", \"host-build\", \"host\"];\n\nconst sortByOrder = (packages: string[]): string[] => {\n return [...packages].sort((a, b) => {\n const aIdx = a.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(a);\n const bIdx = b.startsWith(\"plugin:\")\n ? STARTUP_ORDER.indexOf(\"plugin\")\n : STARTUP_ORDER.indexOf(b);\n if (aIdx === -1 && bIdx === -1) return 0;\n if (aIdx === -1) return 1;\n if (bIdx === -1) return -1;\n return aIdx - bIdx;\n });\n};\n\nfunction formatLogLine(entry: LogEntry): string {\n const ts = new Date(entry.timestamp).toISOString();\n const prefix = entry.isError ? \"ERR\" : \"OUT\";\n return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;\n}\n\nconst scopedProcess = (\n pkg: string,\n env: Record<string, string> | undefined,\n callbacks: ProcessCallbacks,\n portOverride: number | undefined,\n bosConfig: BosConfig | undefined,\n runtimeConfig: RuntimeConfig | undefined,\n registry: ProcessRegistry | undefined,\n) =>\n Effect.acquireRelease(\n makeDevProcess(pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry),\n (handle) => handle.kill.pipe(Effect.ignore),\n );\n\nexport const runDevSession = (\n orchestrator: AppOrchestrator,\n onShutdownReady?: (requestShutdown: () => void) => void,\n) =>\n Effect.gen(function* () {\n const configDir = getProjectRoot();\n const orderedPackages = sortByOrder(orchestrator.packages);\n const initialProcesses: ProcessState[] = orderedPackages.map((pkg) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n const config = getProcessConfig(\n pkg,\n undefined,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n );\n const source =\n pkg === \"host\"\n ? orchestrator.appConfig.host\n : pkg === \"ui\"\n ? orchestrator.appConfig.ui\n : pkg === \"api\"\n ? orchestrator.appConfig.api\n : undefined;\n return {\n name: pkg,\n status: \"pending\" as const,\n port: config?.port ?? 0,\n source,\n };\n });\n\n const registry = yield* makeProcessRegistry(configDir);\n yield* registry.killAll().pipe(Effect.ignore);\n\n const logger = yield* Effect.promise(() =>\n createDevLogger(configDir, orchestrator.description),\n );\n\n const shutdown = yield* Deferred.make<void>();\n\n onShutdownReady?.(() => {\n void Effect.runPromise(Deferred.succeed(shutdown, undefined));\n });\n\n const allLogs: LogEntry[] = [];\n let view: DevViewHandle | null = null;\n let shouldExportLogs = false;\n\n const requestShutdownAndExport = () => {\n shouldExportLogs = true;\n void Effect.runPromise(Deferred.succeed(shutdown, undefined));\n };\n\n const useInteractive = orchestrator.interactive ?? isInteractiveSupported();\n view = useInteractive\n ? renderDevView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => void Effect.runPromise(Deferred.succeed(shutdown, undefined)),\n requestShutdownAndExport,\n )\n : renderStreamingView(\n initialProcesses,\n orchestrator.description,\n orchestrator.env,\n () => void Effect.runPromise(Deferred.succeed(shutdown, undefined)),\n );\n\n const callbacks: ProcessCallbacks = {\n onStatus: (name, status, message) => {\n view?.updateProcess(name, status, message);\n },\n onLog: (name, line, isError) => {\n const entry: LogEntry = {\n id: `${Date.now()}-${allLogs.length + 1}`,\n source: name,\n line,\n timestamp: Date.now(),\n isError,\n };\n allLogs.push(entry);\n if (shouldDisplayLog(name, line, isError)) {\n view?.addLog(name, line, isError);\n }\n if (!orchestrator.noLogs) {\n void logger.write(entry);\n }\n },\n };\n\n const startProcess = (pkg: string) => {\n const portOverride = pkg === \"host\" ? orchestrator.port : undefined;\n return scopedProcess(\n pkg,\n orchestrator.env,\n callbacks,\n portOverride,\n orchestrator.bosConfig,\n orchestrator.runtimeConfig,\n registry,\n );\n };\n\n const startGroup = (packages: string[]) =>\n Effect.forEach(packages, startProcess, { concurrency: \"unbounded\" });\n\n const awaitReady = (pkg: string, handle: ProcessHandle) =>\n Effect.race(\n handle.waitForReady,\n Effect.sleep(\"30 seconds\").pipe(\n Effect.andThen(\n Effect.sync(() => {\n callbacks.onLog(pkg, \"Timeout waiting for ready, continuing...\", true);\n }),\n ),\n ),\n );\n\n const nonHostPackages = orderedPackages.filter((pkg) => pkg !== \"host\");\n const hostPackages = orderedPackages.filter((pkg) => pkg === \"host\");\n\n const nonHostHandles = yield* startGroup(nonHostPackages);\n\n yield* Effect.forEach(\n nonHostHandles.map((handle, index) => ({\n handle,\n pkg: nonHostPackages[index] ?? handle.name,\n })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n const hostHandles = yield* startGroup(hostPackages);\n\n yield* Effect.forEach(\n hostHandles.map((handle, index) => ({ handle, pkg: hostPackages[index] ?? handle.name })),\n ({ handle, pkg }) => awaitReady(pkg, handle),\n { concurrency: \"unbounded\" },\n );\n\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n view?.unmount();\n\n if (shouldExportLogs) {\n console.log(\"\\n\");\n console.log(\"═\".repeat(70));\n console.log(` SESSION LOGS: ${orchestrator.description}`);\n console.log(` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`);\n console.log(` Total entries: ${allLogs.length}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n for (const entry of allLogs) {\n console.log(formatLogLine(entry));\n }\n console.log(\"\");\n console.log(\"═\".repeat(70));\n console.log(` Full logs saved to: ${logger.logFile}`);\n console.log(\"═\".repeat(70));\n console.log(\"\");\n }\n\n yield* registry.killAll(true).pipe(Effect.ignore);\n }),\n );\n\n yield* Deferred.await(shutdown);\n });\n\nexport const startApp = (orchestrator: AppOrchestrator) => {\n let requestShutdown: (() => void) | null = null;\n let signalCount = 0;\n let forceExitTimer: ReturnType<typeof setTimeout> | null = null;\n\n const forceExit = () => {\n console.log(\"\\n[Dev] Force exit\");\n process.exit(0);\n };\n\n const program = Effect.scoped(\n runDevSession(orchestrator, (shutdown) => {\n requestShutdown = shutdown;\n }),\n ).pipe(\n Effect.catchAll((e) =>\n Effect.sync(() => {\n if (e instanceof Error) {\n console.error(\"App server error:\", e.message);\n if (e.stack) console.error(e.stack);\n } else {\n console.error(\"App server error:\", e);\n }\n }),\n ),\n );\n\n const handleSignal = () => {\n signalCount++;\n if (signalCount > 1) {\n forceExit();\n return;\n }\n console.log(\"\\n[Dev] Shutting down...\");\n forceExitTimer = setTimeout(forceExit, 8000);\n requestShutdown?.();\n };\n\n process.on(\"SIGINT\", handleSignal);\n process.on(\"SIGTERM\", handleSignal);\n\n Effect.runPromiseExit(program).then((exit) => {\n if (forceExitTimer) clearTimeout(forceExitTimer);\n process.exit(Exit.isSuccess(exit) ? 0 : 0);\n });\n};\n"],"mappings":"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"}
|
package/dist/fastkv.cjs
CHANGED
|
@@ -59,6 +59,59 @@ async function fetchBosConfigFromFastKv(bosUrl) {
|
|
|
59
59
|
if (typeof value !== "object") throw new Error(`Invalid config value for ${bosUrl}`);
|
|
60
60
|
return value;
|
|
61
61
|
}
|
|
62
|
+
function parsePluginBosUrl(source) {
|
|
63
|
+
if (!source.startsWith("bos://")) return null;
|
|
64
|
+
const match = source.match(/^bos:\/\/([^/]+)\/plugins\/([^/]+)$/);
|
|
65
|
+
if (!match?.[1] || !match[2]) return null;
|
|
66
|
+
return {
|
|
67
|
+
accountId: match[1],
|
|
68
|
+
pluginName: match[2]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function fetchKvValue(accountId, key) {
|
|
72
|
+
const value = (await fetchJson(`${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId))}/${encodeURIComponent(accountId)}`, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
key,
|
|
76
|
+
limit: 1
|
|
77
|
+
})
|
|
78
|
+
}))?.entries?.find(Boolean)?.value;
|
|
79
|
+
if (value == null) return null;
|
|
80
|
+
if (typeof value === "string") try {
|
|
81
|
+
return JSON.parse(value);
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
async function fetchPluginFromRegistry(accountId, pluginName) {
|
|
88
|
+
const manifestKey = `plugins/${accountId}/${pluginName}/manifest.json`;
|
|
89
|
+
const metadataKey = `plugins/${accountId}/${pluginName}/metadata`;
|
|
90
|
+
const [rawManifest, rawMetadata] = await Promise.all([fetchKvValue(accountId, manifestKey), fetchKvValue(accountId, metadataKey)]);
|
|
91
|
+
if (!rawManifest || typeof rawManifest !== "object") return null;
|
|
92
|
+
return {
|
|
93
|
+
manifest: rawManifest,
|
|
94
|
+
metadata: rawMetadata ?? {
|
|
95
|
+
title: null,
|
|
96
|
+
description: null,
|
|
97
|
+
repoUrl: null,
|
|
98
|
+
version: "",
|
|
99
|
+
publishedAt: "",
|
|
100
|
+
cdnUrl: "",
|
|
101
|
+
integrity: null
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function fetchRemotePluginManifest(cdnUrl) {
|
|
106
|
+
try {
|
|
107
|
+
const baseUrl = cdnUrl.replace(/\/$/, "");
|
|
108
|
+
const response = await fetch(`${baseUrl}/plugin.manifest.json`);
|
|
109
|
+
if (!response.ok) return null;
|
|
110
|
+
return await response.json();
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
62
115
|
async function fetchJson(url, init) {
|
|
63
116
|
const controller = new AbortController();
|
|
64
117
|
const timeout = setTimeout(() => controller.abort(), FASTKV_TIMEOUT_MS);
|
|
@@ -83,7 +136,10 @@ async function fetchJson(url, init) {
|
|
|
83
136
|
exports.buildRegistryConfigUrl = buildRegistryConfigUrl;
|
|
84
137
|
exports.buildRegistryConfigUrlForNetwork = buildRegistryConfigUrlForNetwork;
|
|
85
138
|
exports.fetchBosConfigFromFastKv = fetchBosConfigFromFastKv;
|
|
139
|
+
exports.fetchPluginFromRegistry = fetchPluginFromRegistry;
|
|
140
|
+
exports.fetchRemotePluginManifest = fetchRemotePluginManifest;
|
|
86
141
|
exports.getFastKvBaseUrlForNetwork = getFastKvBaseUrlForNetwork;
|
|
87
142
|
exports.getRegistryNamespaceForAccount = getRegistryNamespaceForAccount;
|
|
88
143
|
exports.getRegistryNamespaceForNetwork = getRegistryNamespaceForNetwork;
|
|
144
|
+
exports.parsePluginBosUrl = parsePluginBosUrl;
|
|
89
145
|
//# sourceMappingURL=fastkv.cjs.map
|
package/dist/fastkv.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastkv.cjs","names":[],"sources":["../src/fastkv.ts"],"sourcesContent":["export type NetworkId = \"mainnet\" | \"testnet\";\n\ninterface FastKvEntry {\n value: unknown;\n}\n\ninterface FastKvListResponse {\n entries?: Array<FastKvEntry | null>;\n}\n\nconst FASTKV_TIMEOUT_MS = 10_000;\n\nfunction getNetworkIdForAccount(accountId: string): NetworkId {\n return accountId.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\";\n}\n\nexport function getFastKvBaseUrlForNetwork(network: NetworkId): string {\n return network === \"testnet\"\n ? process.env.REGISTRY_FASTKV_TESTNET_URL || \"https://kv.test.fastnear.com\"\n : process.env.REGISTRY_FASTKV_MAINNET_URL || \"https://kv.main.fastnear.com\";\n}\n\nfunction getFastKvBaseUrlForAccount(accountId: string): string {\n return getNetworkIdForAccount(accountId) === \"testnet\"\n ? getFastKvBaseUrlForNetwork(\"testnet\")\n : getFastKvBaseUrlForNetwork(\"mainnet\");\n}\n\nexport function buildRegistryConfigUrl(accountId: string, gatewayId: string): string {\n const baseUrl = getFastKvBaseUrlForAccount(accountId);\n const namespace = getRegistryNamespaceForAccount(accountId);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function buildRegistryConfigUrlForNetwork(\n network: NetworkId,\n accountId: string,\n gatewayId: string,\n): string {\n const baseUrl = getFastKvBaseUrlForNetwork(network);\n const namespace = getRegistryNamespaceForNetwork(network);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function getRegistryNamespaceForAccount(accountId: string): string {\n return accountId.endsWith(\".testnet\")\n ? process.env.REGISTRY_FASTKV_TESTNET_NAMESPACE || \"dev.everything.near\"\n : process.env.REGISTRY_FASTKV_MAINNET_NAMESPACE || accountId;\n}\n\nexport function getRegistryNamespaceForNetwork(network: NetworkId): string {\n return network === \"testnet\"\n ? process.env.REGISTRY_FASTKV_TESTNET_NAMESPACE || \"dev.everything.near\"\n : process.env.REGISTRY_FASTKV_MAINNET_NAMESPACE || \"dev.everything.near\";\n}\n\nfunction getRegistryConfigKey(\n accountId: string,\n gatewayId: string,\n pathSegments: string[] = [],\n): string {\n const suffix =\n pathSegments.length > 0\n ? `/${pathSegments.map((segment) => encodeURIComponent(segment)).join(\"/\")}`\n : \"\";\n return `apps/${accountId}/${gatewayId}${suffix}/bos.config.json`;\n}\n\nfunction parseBosUrl(bosUrl: string): {\n accountId: string;\n gatewayId: string;\n pathSegments: string[];\n} {\n const match = bosUrl.match(/^bos:\\/\\/([^/]+)\\/(.+)$/);\n if (!match?.[1] || !match[2]) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n const pathSegments = match[2]\n .split(\"/\")\n .filter(Boolean)\n .map((segment) => decodeURIComponent(segment));\n if (pathSegments.length === 0) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n const [gatewayId, ...pathSegmentsTail] = pathSegments;\n if (!gatewayId) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n return {\n accountId: match[1],\n gatewayId,\n pathSegments: pathSegmentsTail,\n };\n}\n\nexport async function fetchBosConfigFromFastKv<T>(bosUrl: string): Promise<T> {\n const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId))}/${encodeURIComponent(accountId)}`,\n {\n method: \"POST\",\n body: JSON.stringify({\n key: getRegistryConfigKey(accountId, gatewayId, pathSegments),\n limit: 1,\n }),\n },\n );\n const value = payload?.entries?.find(Boolean)?.value;\n\n if (!value) {\n throw new Error(`No config found for ${bosUrl}`);\n }\n\n if (typeof value === \"string\") {\n return JSON.parse(value) as T;\n }\n\n if (typeof value !== \"object\") {\n throw new Error(`Invalid config value for ${bosUrl}`);\n }\n\n return value as T;\n}\n\nasync function fetchJson<T>(url: string, init?: RequestInit): Promise<T | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FASTKV_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n ...init,\n headers: {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n ...(init?.headers ?? {}),\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n return null;\n }\n\n return (await response.json()) as T;\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fastkv.cjs","names":[],"sources":["../src/fastkv.ts"],"sourcesContent":["export type NetworkId = \"mainnet\" | \"testnet\";\n\ninterface FastKvEntry {\n value: unknown;\n}\n\ninterface FastKvListResponse {\n entries?: Array<FastKvEntry | null>;\n}\n\nconst FASTKV_TIMEOUT_MS = 10_000;\n\nfunction getNetworkIdForAccount(accountId: string): NetworkId {\n return accountId.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\";\n}\n\nexport function getFastKvBaseUrlForNetwork(network: NetworkId): string {\n return network === \"testnet\"\n ? process.env.REGISTRY_FASTKV_TESTNET_URL || \"https://kv.test.fastnear.com\"\n : process.env.REGISTRY_FASTKV_MAINNET_URL || \"https://kv.main.fastnear.com\";\n}\n\nfunction getFastKvBaseUrlForAccount(accountId: string): string {\n return getNetworkIdForAccount(accountId) === \"testnet\"\n ? getFastKvBaseUrlForNetwork(\"testnet\")\n : getFastKvBaseUrlForNetwork(\"mainnet\");\n}\n\nexport function buildRegistryConfigUrl(accountId: string, gatewayId: string): string {\n const baseUrl = getFastKvBaseUrlForAccount(accountId);\n const namespace = getRegistryNamespaceForAccount(accountId);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function buildRegistryConfigUrlForNetwork(\n network: NetworkId,\n accountId: string,\n gatewayId: string,\n): string {\n const baseUrl = getFastKvBaseUrlForNetwork(network);\n const namespace = getRegistryNamespaceForNetwork(network);\n const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId));\n return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`;\n}\n\nexport function getRegistryNamespaceForAccount(accountId: string): string {\n return accountId.endsWith(\".testnet\")\n ? process.env.REGISTRY_FASTKV_TESTNET_NAMESPACE || \"dev.everything.near\"\n : process.env.REGISTRY_FASTKV_MAINNET_NAMESPACE || accountId;\n}\n\nexport function getRegistryNamespaceForNetwork(network: NetworkId): string {\n return network === \"testnet\"\n ? process.env.REGISTRY_FASTKV_TESTNET_NAMESPACE || \"dev.everything.near\"\n : process.env.REGISTRY_FASTKV_MAINNET_NAMESPACE || \"dev.everything.near\";\n}\n\nfunction getRegistryConfigKey(\n accountId: string,\n gatewayId: string,\n pathSegments: string[] = [],\n): string {\n const suffix =\n pathSegments.length > 0\n ? `/${pathSegments.map((segment) => encodeURIComponent(segment)).join(\"/\")}`\n : \"\";\n return `apps/${accountId}/${gatewayId}${suffix}/bos.config.json`;\n}\n\nfunction parseBosUrl(bosUrl: string): {\n accountId: string;\n gatewayId: string;\n pathSegments: string[];\n} {\n const match = bosUrl.match(/^bos:\\/\\/([^/]+)\\/(.+)$/);\n if (!match?.[1] || !match[2]) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n const pathSegments = match[2]\n .split(\"/\")\n .filter(Boolean)\n .map((segment) => decodeURIComponent(segment));\n if (pathSegments.length === 0) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n const [gatewayId, ...pathSegmentsTail] = pathSegments;\n if (!gatewayId) {\n throw new Error(`Invalid BOS URL: ${bosUrl}`);\n }\n\n return {\n accountId: match[1],\n gatewayId,\n pathSegments: pathSegmentsTail,\n };\n}\n\nexport async function fetchBosConfigFromFastKv<T>(bosUrl: string): Promise<T> {\n const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl);\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId))}/${encodeURIComponent(accountId)}`,\n {\n method: \"POST\",\n body: JSON.stringify({\n key: getRegistryConfigKey(accountId, gatewayId, pathSegments),\n limit: 1,\n }),\n },\n );\n const value = payload?.entries?.find(Boolean)?.value;\n\n if (!value) {\n throw new Error(`No config found for ${bosUrl}`);\n }\n\n if (typeof value === \"string\") {\n return JSON.parse(value) as T;\n }\n\n if (typeof value !== \"object\") {\n throw new Error(`Invalid config value for ${bosUrl}`);\n }\n\n return value as T;\n}\n\nexport interface PluginManifest {\n schemaVersion: number;\n kind: string;\n plugin: { name: string; version: string };\n runtime: { remoteEntry: string };\n contract: {\n kind: string;\n types: { path: string; exportName: string; typeName: string; sha256: string };\n };\n additionalExports?: Array<{ path: string; exports: string[]; sha256: string }>;\n}\n\nexport interface PluginMetadata {\n title: string | null;\n description: string | null;\n repoUrl: string | null;\n version: string;\n publishedAt: string;\n cdnUrl: string;\n integrity: string | null;\n}\n\nexport interface PluginRegistryEntry {\n manifest: PluginManifest;\n metadata: PluginMetadata;\n}\n\nexport function parsePluginBosUrl(\n source: string,\n): { accountId: string; pluginName: string } | null {\n if (!source.startsWith(\"bos://\")) return null;\n const match = source.match(/^bos:\\/\\/([^/]+)\\/plugins\\/([^/]+)$/);\n if (!match?.[1] || !match[2]) return null;\n return { accountId: match[1], pluginName: match[2] };\n}\n\nasync function fetchKvValue(accountId: string, key: string): Promise<unknown | null> {\n const payload = await fetchJson<FastKvListResponse>(\n `${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId))}/${encodeURIComponent(accountId)}`,\n {\n method: \"POST\",\n body: JSON.stringify({ key, limit: 1 }),\n },\n );\n const value = payload?.entries?.find(Boolean)?.value;\n if (value == null) return null;\n if (typeof value === \"string\") {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n }\n return value;\n}\n\nexport async function fetchPluginFromRegistry(\n accountId: string,\n pluginName: string,\n): Promise<PluginRegistryEntry | null> {\n const manifestKey = `plugins/${accountId}/${pluginName}/manifest.json`;\n const metadataKey = `plugins/${accountId}/${pluginName}/metadata`;\n\n const [rawManifest, rawMetadata] = await Promise.all([\n fetchKvValue(accountId, manifestKey),\n fetchKvValue(accountId, metadataKey),\n ]);\n\n if (!rawManifest || typeof rawManifest !== \"object\") return null;\n\n return {\n manifest: rawManifest as PluginManifest,\n metadata: (rawMetadata ?? {\n title: null,\n description: null,\n repoUrl: null,\n version: \"\",\n publishedAt: \"\",\n cdnUrl: \"\",\n integrity: null,\n }) as PluginMetadata,\n };\n}\n\nexport async function fetchRemotePluginManifest(cdnUrl: string): Promise<PluginManifest | null> {\n try {\n const baseUrl = cdnUrl.replace(/\\/$/, \"\");\n const response = await fetch(`${baseUrl}/plugin.manifest.json`);\n if (!response.ok) return null;\n return (await response.json()) as PluginManifest;\n } catch {\n return null;\n }\n}\n\nasync function fetchJson<T>(url: string, init?: RequestInit): Promise<T | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FASTKV_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n ...init,\n headers: {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n ...(init?.headers ?? {}),\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n return null;\n }\n\n return (await response.json()) as T;\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":"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"}
|