first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run

This commit is contained in:
Zakaria
2026-05-04 14:58:14 -04:00
commit a46764fb1b
1210 changed files with 233231 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# apps/packaged
Follow the root `AGENTS.md` and `apps/AGENTS.md` first. This app owns only the packaged Electron runtime assembly entry.
## Owns
- Packaged Electron entry glue.
- Packaged config loading.
- Runtime startup of daemon/web sidecars before desktop main.
- `od://` packaged entry routing to the internal web runtime.
## Does not own
- Product/business logic.
- Web, daemon, or desktop implementation details.
- Sidecar protocol definitions or process stamp semantics.
## Rules
- Consume `@open-design/sidecar-proto`, `@open-design/sidecar`, and `@open-design/platform` primitives; do not hand-build stamp flags or process matching logic.
- Keep data/log/runtime/cache paths namespace-scoped and independent from daemon/web ports.
- Keep Next.js packaged runtime as SSR/web-sidecar-owned; do not put Next output under `OD_RESOURCE_ROOT`.
- `OD_RESOURCE_ROOT` is only for daemon non-Next read-only resources: `skills/`, `design-systems/`, and `frames/`.
+7
View File
@@ -0,0 +1,7 @@
# apps/packaged
Thin packaged Electron runtime entry for Open Design.
This package starts the packaged daemon and web sidecars, registers the `od://`
entry protocol, and then delegates to `@open-design/desktop/main` for the host
window. Product logic stays in `apps/daemon`, `apps/web`, and `apps/desktop`.
+11
View File
@@ -0,0 +1,11 @@
import { build } from "esbuild";
await build({
bundle: true,
entryPoints: ["./src/index.ts"],
format: "esm",
outfile: "./dist/index.mjs",
packages: "external",
platform: "node",
target: "node24",
});
+39
View File
@@ -0,0 +1,39 @@
{
"name": "@open-design/packaged",
"version": "0.3.0",
"private": true,
"type": "module",
"main": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
},
"./package.json": "./package.json"
},
"files": [
"dist"
],
"scripts": {
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@open-design/daemon": "workspace:0.3.0",
"@open-design/desktop": "workspace:0.3.0",
"@open-design/platform": "workspace:0.3.0",
"@open-design/sidecar": "workspace:0.3.0",
"@open-design/sidecar-proto": "workspace:0.3.0",
"@open-design/web": "workspace:0.3.0"
},
"devDependencies": {
"@types/node": "24.12.2",
"electron": "41.3.0",
"esbuild": "0.27.7",
"typescript": "6.0.3"
},
"engines": {
"node": "~24"
}
}
+83
View File
@@ -0,0 +1,83 @@
import { access, readFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { app } from "electron";
import { SIDECAR_DEFAULTS, normalizeNamespace } from "@open-design/sidecar-proto";
export const PACKAGED_CONFIG_PATH_ENV = "OD_PACKAGED_CONFIG_PATH";
export const PACKAGED_NAMESPACE_ENV = "OD_PACKAGED_NAMESPACE";
export type RawPackagedConfig = {
namespace?: string;
namespaceBaseRoot?: string;
nodeCommandRelative?: string;
resourceRoot?: string;
};
export type PackagedConfig = {
namespace: string;
namespaceBaseRoot: string;
nodeCommand: string | null;
resourceRoot: string;
};
async function pathExists(filePath: string): Promise<boolean> {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
async function readJsonIfExists(filePath: string): Promise<RawPackagedConfig | null> {
if (!(await pathExists(filePath))) return null;
return JSON.parse(await readFile(filePath, "utf8")) as RawPackagedConfig;
}
function resolveDefaultConfigPath(): string {
return join(process.resourcesPath, "open-design-config.json");
}
async function readRawPackagedConfig(): Promise<RawPackagedConfig> {
const explicit = process.env[PACKAGED_CONFIG_PATH_ENV];
if (explicit != null && explicit.length > 0) {
const config = await readJsonIfExists(resolve(explicit));
if (config == null) throw new Error(`packaged config not found at ${explicit}`);
return config;
}
return (
(await readJsonIfExists(resolveDefaultConfigPath())) ??
(await readJsonIfExists(join(app.getAppPath(), "open-design-config.json"))) ??
{}
);
}
function resolveOptionalPath(value: string | undefined): string | undefined {
return value == null || value.length === 0 ? undefined : resolve(value);
}
export async function readPackagedConfig(): Promise<PackagedConfig> {
const raw = await readRawPackagedConfig();
const namespace = normalizeNamespace(
process.env[PACKAGED_NAMESPACE_ENV] ?? raw.namespace ?? SIDECAR_DEFAULTS.namespace,
);
const namespaceBaseRoot =
resolveOptionalPath(raw.namespaceBaseRoot) ?? join(app.getPath("userData"), "namespaces");
const resourceRoot = resolveOptionalPath(raw.resourceRoot) ?? join(process.resourcesPath, "open-design");
const relativeNodeCommand =
raw.nodeCommandRelative == null || raw.nodeCommandRelative.length === 0
? join("open-design", "bin", "node")
: raw.nodeCommandRelative;
const nodeCommandCandidate = join(process.resourcesPath, relativeNodeCommand);
const nodeCommand = (await pathExists(nodeCommandCandidate)) ? nodeCommandCandidate : null;
return {
namespace,
namespaceBaseRoot,
nodeCommand,
resourceRoot,
};
}
+75
View File
@@ -0,0 +1,75 @@
import { dirname } from "node:path";
import { removeFile, writeJsonFile } from "@open-design/sidecar";
import type { SidecarStamp } from "@open-design/sidecar-proto";
import type { PackagedNamespacePaths } from "./paths.js";
export type PackagedDesktopRootIdentity = {
appPath: string;
executablePath: string;
logPath: string;
namespaceRoot: string;
pid: number;
ppid: number;
stamp: SidecarStamp;
startedAt: string;
updatedAt: string;
version: 1;
};
export type PackagedDesktopIdentityHandle = {
close(): Promise<void>;
identity: PackagedDesktopRootIdentity;
};
function resolveCurrentMacAppPath(executablePath: string): string {
return dirname(dirname(dirname(executablePath)));
}
function createPackagedDesktopRootIdentity(options: {
paths: PackagedNamespacePaths;
stamp: SidecarStamp;
}): PackagedDesktopRootIdentity {
const now = new Date().toISOString();
const executablePath = process.execPath;
return {
appPath: resolveCurrentMacAppPath(executablePath),
executablePath,
logPath: options.paths.desktopLogPath,
namespaceRoot: options.paths.namespaceRoot,
pid: process.pid,
ppid: process.ppid,
stamp: options.stamp,
startedAt: now,
updatedAt: now,
version: 1,
};
}
export async function writePackagedDesktopIdentity(options: {
paths: PackagedNamespacePaths;
stamp: SidecarStamp;
}): Promise<PackagedDesktopIdentityHandle> {
const identity = createPackagedDesktopRootIdentity(options);
const writeIdentity = async () => {
identity.updatedAt = new Date().toISOString();
await writeJsonFile(options.paths.desktopIdentityPath, identity);
};
await writeIdentity();
const heartbeat = setInterval(() => {
void writeIdentity().catch(() => undefined);
}, 5000);
heartbeat.unref();
return {
async close() {
clearInterval(heartbeat);
await removeFile(options.paths.desktopIdentityPath).catch(() => undefined);
},
identity,
};
}
+105
View File
@@ -0,0 +1,105 @@
import {
APP_KEYS,
OPEN_DESIGN_SIDECAR_CONTRACT,
SIDECAR_MODES,
SIDECAR_SOURCES,
type SidecarStamp,
} from "@open-design/sidecar-proto";
import {
bootstrapSidecarRuntime,
createSidecarLaunchEnv,
resolveAppIpcPath,
} from "@open-design/sidecar";
import { readProcessStamp } from "@open-design/platform";
import { app } from "electron";
import { readPackagedConfig } from "./config.js";
import { writePackagedDesktopIdentity } from "./identity.js";
import {
applyPackagedElectronPathOverrides,
ensurePackagedNamespacePaths,
} from "./launch.js";
import {
attachPackagedDesktopProcessLogging,
createPackagedDesktopLogger,
type PackagedDesktopLogger,
} from "./logging.js";
import { resolvePackagedNamespacePaths } from "./paths.js";
import { packagedEntryUrl, registerOdProtocol } from "./protocol.js";
import { startPackagedSidecars } from "./sidecars.js";
let packagedLogger: PackagedDesktopLogger | null = null;
function createPackagedDesktopStamp(namespace: string): SidecarStamp {
return {
app: APP_KEYS.DESKTOP,
ipc: resolveAppIpcPath({
app: APP_KEYS.DESKTOP,
contract: OPEN_DESIGN_SIDECAR_CONTRACT,
namespace,
}),
mode: SIDECAR_MODES.RUNTIME,
namespace,
source: SIDECAR_SOURCES.PACKAGED,
};
}
function applyLaunchEnv(base: string, stamp: SidecarStamp): void {
const env = createSidecarLaunchEnv({
base,
contract: OPEN_DESIGN_SIDECAR_CONTRACT,
stamp,
});
for (const [key, value] of Object.entries(env)) {
if (value != null) process.env[key] = value;
}
}
async function main(): Promise<void> {
const config = await readPackagedConfig();
const argvStamp = readProcessStamp(process.argv.slice(1), OPEN_DESIGN_SIDECAR_CONTRACT);
const namespace = argvStamp?.namespace ?? config.namespace;
const paths = resolvePackagedNamespacePaths(config, namespace);
const stamp = argvStamp ?? createPackagedDesktopStamp(namespace);
await ensurePackagedNamespacePaths(paths);
packagedLogger = createPackagedDesktopLogger(paths);
attachPackagedDesktopProcessLogging({ logger: packagedLogger, paths, stamp });
applyPackagedElectronPathOverrides(paths);
const identity = await writePackagedDesktopIdentity({ paths, stamp });
await app.whenReady();
applyLaunchEnv(paths.runtimeRoot, stamp);
const runtime = bootstrapSidecarRuntime(stamp, process.env, {
app: APP_KEYS.DESKTOP,
base: paths.runtimeRoot,
contract: OPEN_DESIGN_SIDECAR_CONTRACT,
});
const sidecars = await startPackagedSidecars(runtime, paths, {
nodeCommand: config.nodeCommand,
});
registerOdProtocol(sidecars.web.url ?? "http://127.0.0.1:0");
const { runDesktopMain } = await import("@open-design/desktop/main");
await runDesktopMain(runtime, {
async beforeShutdown() {
try {
await sidecars.close();
} finally {
await identity.close();
}
},
async discoverWebUrl() {
return packagedEntryUrl();
},
});
}
void main().catch((error: unknown) => {
packagedLogger?.error("packaged runtime failed", { error });
console.error("packaged runtime failed", error);
process.exit(1);
});
+28
View File
@@ -0,0 +1,28 @@
import { mkdir } from "node:fs/promises";
import { app } from "electron";
import type { PackagedNamespacePaths } from "./paths.js";
export async function ensurePackagedNamespacePaths(
paths: PackagedNamespacePaths,
): Promise<void> {
await Promise.all([
mkdir(paths.namespaceRoot, { recursive: true }),
mkdir(paths.cacheRoot, { recursive: true }),
mkdir(paths.dataRoot, { recursive: true }),
mkdir(paths.logsRoot, { recursive: true }),
mkdir(paths.desktopLogsRoot, { recursive: true }),
mkdir(paths.runtimeRoot, { recursive: true }),
mkdir(paths.electronUserDataRoot, { recursive: true }),
mkdir(paths.electronSessionDataRoot, { recursive: true }),
]);
}
export function applyPackagedElectronPathOverrides(
paths: PackagedNamespacePaths,
): void {
app.setPath("userData", paths.electronUserDataRoot);
app.setPath("sessionData", paths.electronSessionDataRoot);
app.setPath("logs", paths.desktopLogsRoot);
}
+135
View File
@@ -0,0 +1,135 @@
import { appendFileSync } from "node:fs";
import type { SidecarStamp } from "@open-design/sidecar-proto";
import type { PackagedNamespacePaths } from "./paths.js";
const DESKTOP_LOG_ECHO_ENV = "OD_DESKTOP_LOG_ECHO";
type LogLevel = "error" | "info" | "warn";
export type PackagedDesktopLogger = {
error(message: string, meta?: Record<string, unknown>): void;
info(message: string, meta?: Record<string, unknown>): void;
warn(message: string, meta?: Record<string, unknown>): void;
};
function normalizeError(error: unknown): unknown {
if (error instanceof Error) {
return {
message: error.message,
name: error.name,
stack: error.stack,
};
}
return error;
}
function normalizeMeta(meta: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
if (meta == null) return undefined;
return Object.fromEntries(
Object.entries(meta).map(([key, value]) => [key, key === "error" || key === "reason" ? normalizeError(value) : value]),
);
}
function serializeMessage(level: LogLevel, message: string, meta?: Record<string, unknown>): string {
const timestamp = new Date().toISOString();
try {
return `${JSON.stringify({
level,
message,
timestamp,
...(meta == null ? {} : { meta: normalizeMeta(meta) }),
})}\n`;
} catch (error) {
return `${JSON.stringify({
level,
message,
timestamp,
meta: {
serializationError: error instanceof Error ? error.message : String(error),
},
})}\n`;
}
}
export function createPackagedDesktopLogger(paths: PackagedNamespacePaths): PackagedDesktopLogger {
const echo = process.env[DESKTOP_LOG_ECHO_ENV] !== "0";
const write = (level: LogLevel, message: string, meta?: Record<string, unknown>) => {
appendFileSync(paths.desktopLogPath, serializeMessage(level, message, meta), "utf8");
};
const logger: PackagedDesktopLogger = {
error(message, meta) {
write("error", message, meta);
},
info(message, meta) {
write("info", message, meta);
},
warn(message, meta) {
write("warn", message, meta);
},
};
const originalConsole = {
error: console.error.bind(console),
info: console.info.bind(console),
log: console.log.bind(console),
warn: console.warn.bind(console),
};
console.log = (...args: unknown[]) => {
logger.info("console.log", { args });
if (echo) originalConsole.log(...args);
};
console.info = (...args: unknown[]) => {
logger.info("console.info", { args });
if (echo) originalConsole.info(...args);
};
console.warn = (...args: unknown[]) => {
logger.warn("console.warn", { args });
if (echo) originalConsole.warn(...args);
};
console.error = (...args: unknown[]) => {
logger.error("console.error", { args });
if (echo) originalConsole.error(...args);
};
return logger;
}
export function attachPackagedDesktopProcessLogging(options: {
logger: PackagedDesktopLogger;
paths: PackagedNamespacePaths;
stamp: SidecarStamp;
}): void {
const { logger, paths, stamp } = options;
logger.info("packaged desktop starting", {
daemonDataRoot: paths.dataRoot,
electronUserDataRoot: paths.electronUserDataRoot,
executablePath: process.execPath,
logPath: paths.desktopLogPath,
namespace: stamp.namespace,
pid: process.pid,
ppid: process.ppid,
resourceRoot: paths.resourceRoot,
runtimeRoot: paths.runtimeRoot,
source: stamp.source,
});
process.on("uncaughtExceptionMonitor", (error) => {
logger.error("packaged desktop uncaught exception", { error });
});
process.on("unhandledRejection", (reason) => {
logger.error("packaged desktop unhandled rejection", { reason });
});
process.on("beforeExit", (code) => {
logger.warn("packaged desktop beforeExit", { code });
});
process.on("exit", (code) => {
logger.warn("packaged desktop exit", { code });
});
}
+40
View File
@@ -0,0 +1,40 @@
import { join } from "node:path";
import { APP_KEYS } from "@open-design/sidecar-proto";
import type { PackagedConfig } from "./config.js";
export type PackagedNamespacePaths = {
cacheRoot: string;
desktopIdentityPath: string;
desktopLogPath: string;
dataRoot: string;
desktopLogsRoot: string;
electronSessionDataRoot: string;
electronUserDataRoot: string;
logsRoot: string;
namespaceRoot: string;
resourceRoot: string;
runtimeRoot: string;
};
export function resolvePackagedNamespacePaths(
config: PackagedConfig,
namespace = config.namespace,
): PackagedNamespacePaths {
const namespaceRoot = join(config.namespaceBaseRoot, namespace);
return {
cacheRoot: join(namespaceRoot, "cache"),
desktopIdentityPath: join(namespaceRoot, "runtime", "desktop-root.json"),
desktopLogPath: join(namespaceRoot, "logs", APP_KEYS.DESKTOP, "latest.log"),
dataRoot: join(namespaceRoot, "data"),
desktopLogsRoot: join(namespaceRoot, "logs", APP_KEYS.DESKTOP),
electronSessionDataRoot: join(namespaceRoot, "user-data", "session"),
electronUserDataRoot: join(namespaceRoot, "user-data"),
logsRoot: join(namespaceRoot, "logs"),
namespaceRoot,
resourceRoot: config.resourceRoot,
runtimeRoot: join(namespaceRoot, "runtime"),
};
}
+37
View File
@@ -0,0 +1,37 @@
import { protocol } from "electron";
const OD_SCHEME = "od";
const OD_ENTRY_URL = `${OD_SCHEME}://app/`;
protocol.registerSchemesAsPrivileged([
{
privileges: {
corsEnabled: true,
secure: true,
standard: true,
stream: true,
supportFetchAPI: true,
},
scheme: OD_SCHEME,
},
]);
function toWebRuntimeUrl(webRuntimeUrl: string, requestUrl: string): string {
const incoming = new URL(requestUrl);
const target = new URL(webRuntimeUrl);
target.pathname = incoming.pathname;
target.search = incoming.search;
target.hash = incoming.hash;
return target.toString();
}
export function packagedEntryUrl(): string {
return OD_ENTRY_URL;
}
export function registerOdProtocol(webRuntimeUrl: string): void {
protocol.handle(OD_SCHEME, async (request) => {
const target = toWebRuntimeUrl(webRuntimeUrl, request.url);
return await fetch(new Request(target, request));
});
}
+302
View File
@@ -0,0 +1,302 @@
import { spawn, type ChildProcess } from "node:child_process";
import { existsSync, readdirSync } from "node:fs";
import { mkdir, open, type FileHandle } from "node:fs/promises";
import { createRequire } from "node:module";
import { homedir } from "node:os";
import { delimiter, dirname, join } from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
import {
APP_KEYS,
OPEN_DESIGN_SIDECAR_CONTRACT,
SIDECAR_ENV,
SIDECAR_MESSAGES,
SIDECAR_MODES,
type AppKey,
type DaemonStatusSnapshot,
type SidecarStamp,
type WebStatusSnapshot,
} from "@open-design/sidecar-proto";
import {
createSidecarLaunchEnv,
requestJsonIpc,
resolveAppIpcPath,
type SidecarRuntimeContext,
} from "@open-design/sidecar";
import { createProcessStampArgs, stopProcesses, waitForProcessExit } from "@open-design/platform";
import type { PackagedNamespacePaths } from "./paths.js";
const require = createRequire(import.meta.url);
const PACKAGED_CHILD_ENV_ALLOWLIST = ["HOME", "LANG", "LC_ALL", "LOGNAME", "TMPDIR", "USER"] as const;
export type PackagedSidecarHandle = {
close(): Promise<void>;
daemon: DaemonStatusSnapshot;
web: WebStatusSnapshot;
};
type ManagedSidecarChild = {
app: AppKey;
child: ChildProcess;
ipcPath: string;
logHandle: FileHandle;
};
type PackagedDaemonManagedPathEnv = {
OD_DATA_DIR: string;
OD_RESOURCE_ROOT: string;
};
function resolveSidecarEntry(packageName: string, exportName: string): string {
return require.resolve(`${packageName}/${exportName}`);
}
function logPathFor(paths: PackagedNamespacePaths, app: AppKey): string {
return join(paths.logsRoot, app, "latest.log");
}
async function openLog(path: string): Promise<FileHandle> {
await mkdir(dirname(path), { recursive: true });
return await open(path, "w");
}
async function waitForStatus<T>(
ipcPath: string,
isReady: (status: T) => boolean,
timeoutMs = 35_000,
): Promise<T> {
const startedAt = Date.now();
let lastError: unknown;
while (Date.now() - startedAt < timeoutMs) {
try {
const status = await requestJsonIpc<T>(
ipcPath,
{ type: SIDECAR_MESSAGES.STATUS },
{ timeoutMs: 800 },
);
if (isReady(status)) return status;
} catch (error) {
lastError = error;
}
await sleep(150);
}
throw new Error(
`timed out waiting for sidecar status at ${ipcPath}${
lastError instanceof Error ? ` (${lastError.message})` : ""
}`,
);
}
function extractPort(url: string): string {
const parsed = new URL(url);
return parsed.port || (parsed.protocol === "https:" ? "443" : "80");
}
function existingDirsUnder(root: string, segments: string[] = []): string[] {
const dirs: string[] = [];
try {
const entries = readdirSync(root, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const full = join(root, entry.name, ...segments);
if (existsSync(full)) dirs.push(full);
}
} catch {
// best-effort: directory may not exist or be unreadable
}
return dirs;
}
function collectNvmFnmBins(home: string): string[] {
return [
...existingDirsUnder(join(home, ".nvm", "versions", "node"), ["bin"]),
...existingDirsUnder(join(home, ".local", "share", "fnm", "node-versions"), ["installation", "bin"]),
...existingDirsUnder(join(home, ".local", "share", "mise", "installs", "node"), ["bin"]),
];
}
function resolvePackagedPathEnv(basePath = process.env.PATH ?? ""): string {
const home = homedir();
const candidates = [
...basePath.split(delimiter),
join(home, ".local", "bin"),
join(home, ".opencode", "bin"),
join(home, ".cargo", "bin"),
join(home, ".bun", "bin"),
join(home, ".volta", "bin"),
...collectNvmFnmBins(home),
"/opt/homebrew/bin",
"/usr/local/bin",
"/usr/bin",
"/bin",
"/usr/sbin",
"/sbin",
];
return [...new Set(candidates.filter((entry) => entry.length > 0))].join(delimiter);
}
function resolvePackagedChildBaseEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
const baseEnv: NodeJS.ProcessEnv = {};
for (const key of PACKAGED_CHILD_ENV_ALLOWLIST) {
const value = env[key];
if (value != null && value.length > 0) baseEnv[key] = value;
}
return baseEnv;
}
function createPackagedDaemonManagedPathEnv(
paths: PackagedNamespacePaths,
): PackagedDaemonManagedPathEnv {
return {
OD_DATA_DIR: paths.dataRoot,
OD_RESOURCE_ROOT: paths.resourceRoot,
};
}
async function spawnSidecarChild(options: {
app: AppKey;
entryPath: string;
env: NodeJS.ProcessEnv;
nodeCommand: string | null;
paths: PackagedNamespacePaths;
runtime: SidecarRuntimeContext<SidecarStamp>;
}): Promise<ManagedSidecarChild> {
const ipcPath = resolveAppIpcPath({
app: options.app,
contract: OPEN_DESIGN_SIDECAR_CONTRACT,
namespace: options.runtime.namespace,
});
const stamp = {
app: options.app,
ipc: ipcPath,
mode: SIDECAR_MODES.RUNTIME,
namespace: options.runtime.namespace,
source: options.runtime.source,
} satisfies SidecarStamp;
const logHandle = await openLog(logPathFor(options.paths, options.app));
const childEnv = createSidecarLaunchEnv({
base: options.paths.runtimeRoot,
contract: OPEN_DESIGN_SIDECAR_CONTRACT,
extraEnv: {
...resolvePackagedChildBaseEnv(),
...options.env,
NODE_ENV: "production",
PATH: resolvePackagedPathEnv(),
...(options.nodeCommand == null ? { ELECTRON_RUN_AS_NODE: "1" } : {}),
},
stamp,
});
const command = options.nodeCommand ?? process.execPath;
const child = spawn(
command,
[options.entryPath, ...createProcessStampArgs(stamp, OPEN_DESIGN_SIDECAR_CONTRACT)],
{
cwd: process.cwd(),
env: childEnv,
stdio: ["ignore", logHandle.fd, logHandle.fd],
windowsHide: true,
},
);
await new Promise<void>((resolveSpawn, rejectSpawn) => {
child.once("error", rejectSpawn);
child.once("spawn", resolveSpawn);
});
return { app: options.app, child, ipcPath, logHandle };
}
async function closeManagedChild(child: ManagedSidecarChild): Promise<void> {
try {
await requestJsonIpc(child.ipcPath, { type: SIDECAR_MESSAGES.SHUTDOWN }, { timeoutMs: 1200 });
} catch {
// Fall through to process cleanup.
}
if (!(await waitForProcessExit(child.child.pid, 5000))) {
await stopProcesses([child.child.pid]);
}
await child.logHandle.close().catch(() => undefined);
}
export async function startPackagedSidecars(
runtime: SidecarRuntimeContext<SidecarStamp>,
paths: PackagedNamespacePaths,
options: { nodeCommand: string | null },
): Promise<PackagedSidecarHandle> {
await mkdir(paths.namespaceRoot, { recursive: true });
await mkdir(paths.cacheRoot, { recursive: true });
await mkdir(paths.dataRoot, { recursive: true });
await mkdir(paths.logsRoot, { recursive: true });
await mkdir(paths.desktopLogsRoot, { recursive: true });
await mkdir(paths.runtimeRoot, { recursive: true });
await mkdir(paths.electronUserDataRoot, { recursive: true });
await mkdir(paths.electronSessionDataRoot, { recursive: true });
const children: ManagedSidecarChild[] = [];
try {
const daemon = await spawnSidecarChild({
app: APP_KEYS.DAEMON,
entryPath: resolveSidecarEntry("@open-design/daemon", "sidecar"),
env: {
[SIDECAR_ENV.DAEMON_PORT]: "0",
// Packaged daemon managed paths are deliberately delivered through
// the sidecar launch environment. The daemon may keep its own default
// fallback, but packaged runtime must not rely on path inference from
// Electron userData, bundle names, or ports.
...createPackagedDaemonManagedPathEnv(paths),
},
nodeCommand: options.nodeCommand,
paths,
runtime,
});
children.push(daemon);
const daemonStatus = await waitForStatus<DaemonStatusSnapshot>(
daemon.ipcPath,
(status) => status.url != null,
);
if (daemonStatus.url == null) throw new Error("daemon did not report a URL");
const web = await spawnSidecarChild({
app: APP_KEYS.WEB,
entryPath: resolveSidecarEntry("@open-design/web", "sidecar"),
env: {
[SIDECAR_ENV.DAEMON_PORT]: extractPort(daemonStatus.url),
[SIDECAR_ENV.WEB_PORT]: "0",
OD_WEB_OUTPUT_MODE: "server",
PORT: "0",
},
nodeCommand: options.nodeCommand,
paths,
runtime,
});
children.push(web);
const webStatus = await waitForStatus<WebStatusSnapshot>(
web.ipcPath,
(status) => status.url != null,
);
if (webStatus.url == null) throw new Error("web did not report a URL");
return {
daemon: daemonStatus,
web: webStatus,
async close() {
for (const child of [...children].reverse()) {
await closeManagedChild(child).catch((error: unknown) => {
console.error(`failed to close packaged ${child.app} sidecar`, error);
});
}
},
};
} catch (error) {
for (const child of [...children].reverse()) {
await closeManagedChild(child).catch(() => undefined);
}
throw error;
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"lib": ["ES2024", "DOM"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"resolveJsonModule": true,
"rootDir": "src",
"skipLibCheck": true,
"strict": true,
"target": "ES2024",
"types": ["node"]
},
"include": ["src/**/*.ts"]
}