open-design/tools/dev/src/config.ts

196 lines
7.2 KiB
TypeScript
Raw Normal View History

import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
APP_KEYS,
OPEN_DESIGN_SIDECAR_CONTRACT,
SIDECAR_ENV,
SIDECAR_SOURCES,
} from "@open-design/sidecar-proto";
import {
resolveAppIpcPath,
resolveAppRuntimePath,
resolveLogFilePath,
resolveNamespace,
resolveNamespaceRoot,
resolveSidecarBase,
resolveSourceRuntimeRoot,
} from "@open-design/sidecar";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ENTRY_DIR_NAME = path.basename(__dirname);
export const WORKSPACE_ROOT = path.resolve(__dirname, ENTRY_DIR_NAME === "dist" ? "../../.." : "../../..");
export const ALL_APPS = [APP_KEYS.DAEMON, APP_KEYS.WEB, APP_KEYS.DESKTOP] as const;
export const DEFAULT_START_APPS = [APP_KEYS.DAEMON, APP_KEYS.WEB, APP_KEYS.DESKTOP] as const;
export const DEFAULT_RUN_APPS = [APP_KEYS.DAEMON, APP_KEYS.WEB] as const;
export const DEFAULT_STOP_APPS = [APP_KEYS.DESKTOP, APP_KEYS.WEB, APP_KEYS.DAEMON] as const;
export type ToolDevAppName = (typeof ALL_APPS)[number];
export type ToolDevOptions = {
daemonPort?: number | string | null;
json?: boolean;
namespace?: string;
prod?: boolean;
toolsDevRoot?: string;
webPort?: number | string | null;
};
export type ToolDevAppConfig = {
app: ToolDevAppName;
ipcPath: string;
latestLogPath: string;
logDir: string;
};
export type ToolDevConfig = {
apps: {
daemon: ToolDevAppConfig & {
sidecarEntryPath: string;
};
desktop: ToolDevAppConfig & {
electronBinaryPath: string;
mainEntryPath: string;
packageJsonPath: string;
};
web: ToolDevAppConfig & {
nextDistDir: string;
nextTsconfigPath: string;
sidecarEntryPath: string;
};
};
namespace: string;
namespaceRoot: string;
toolsDevRoot: string;
tsxCliPath: string;
workspaceRoot: string;
};
function resolveTsxCliPath(): string {
const require = createRequire(import.meta.url);
return require.resolve("tsx/cli");
}
function resolveElectronBinaryPath(workspaceRoot: string): string {
const packageJsonPath = path.join(workspaceRoot, "apps/desktop/package.json");
const require = createRequire(packageJsonPath);
const electron = require("electron") as unknown;
if (typeof electron === "string" && electron.length > 0) return electron;
return require.resolve("electron/cli.js");
}
function resolveAppConfig(options: {
app: ToolDevAppName;
namespace: string;
namespaceRoot: string;
toolsDevRoot: string;
}): ToolDevAppConfig {
return {
app: options.app,
ipcPath: resolveAppIpcPath({
app: options.app,
contract: OPEN_DESIGN_SIDECAR_CONTRACT,
namespace: options.namespace,
}),
latestLogPath: resolveLogFilePath({ runtimeRoot: options.namespaceRoot, app: options.app, contract: OPEN_DESIGN_SIDECAR_CONTRACT }),
logDir: path.dirname(resolveLogFilePath({ runtimeRoot: options.namespaceRoot, app: options.app, contract: OPEN_DESIGN_SIDECAR_CONTRACT })),
};
}
export function isToolDevAppName(value: string): value is ToolDevAppName {
return ALL_APPS.includes(value as ToolDevAppName);
}
function unsupportedAppError(value: string): Error {
return new Error(`unsupported tools-dev app: ${value} (expected one of: ${ALL_APPS.join(", ")})`);
}
export function resolveTargetApps(appName: string | undefined, defaults: readonly ToolDevAppName[]): ToolDevAppName[] {
if (appName == null) return [...defaults];
if (!isToolDevAppName(appName)) throw unsupportedAppError(appName);
return [appName];
}
export function resolveStartApps(appName: string | undefined): ToolDevAppName[] {
if (appName == null) return [...DEFAULT_START_APPS];
if (!isToolDevAppName(appName)) throw unsupportedAppError(appName);
if (appName === APP_KEYS.WEB) return [APP_KEYS.DAEMON, APP_KEYS.WEB];
if (appName === APP_KEYS.DESKTOP) return [APP_KEYS.DAEMON, APP_KEYS.WEB, APP_KEYS.DESKTOP];
return [APP_KEYS.DAEMON];
}
export function resolveRunApps(appName: string | undefined): ToolDevAppName[] {
if (appName == null) return [...DEFAULT_RUN_APPS];
return resolveStartApps(appName);
}
export function resolveStopApps(appName: string | undefined): ToolDevAppName[] {
if (appName == null) return [...DEFAULT_STOP_APPS];
if (!isToolDevAppName(appName)) throw unsupportedAppError(appName);
if (appName === APP_KEYS.WEB) return [APP_KEYS.WEB, APP_KEYS.DAEMON];
if (appName === APP_KEYS.DESKTOP) return [APP_KEYS.DESKTOP];
return [APP_KEYS.DAEMON];
}
export function parsePortOption(value: number | string | null | undefined, optionName: string): number | null {
if (value == null || value === "") return null;
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
throw new Error(`${optionName} must be an integer between 1 and 65535`);
}
return parsed;
}
export function resolveToolDevConfig(options: ToolDevOptions = {}): ToolDevConfig {
const namespace = resolveNamespace({ namespace: options.namespace, env: process.env, contract: OPEN_DESIGN_SIDECAR_CONTRACT });
const toolsDevRoot = resolveSidecarBase({
base: options.toolsDevRoot ?? process.env[SIDECAR_ENV.BASE] ?? resolveSourceRuntimeRoot({
contract: OPEN_DESIGN_SIDECAR_CONTRACT,
projectRoot: WORKSPACE_ROOT,
source: SIDECAR_SOURCES.TOOLS_DEV,
}),
contract: OPEN_DESIGN_SIDECAR_CONTRACT,
env: process.env,
projectRoot: WORKSPACE_ROOT,
source: SIDECAR_SOURCES.TOOLS_DEV,
});
const namespaceRoot = resolveNamespaceRoot({ base: toolsDevRoot, namespace, contract: OPEN_DESIGN_SIDECAR_CONTRACT });
const daemon = resolveAppConfig({ app: APP_KEYS.DAEMON, namespace, namespaceRoot, toolsDevRoot });
const desktop = resolveAppConfig({ app: APP_KEYS.DESKTOP, namespace, namespaceRoot, toolsDevRoot });
const web = resolveAppConfig({ app: APP_KEYS.WEB, namespace, namespaceRoot, toolsDevRoot });
const desktopPackageJsonPath = path.join(WORKSPACE_ROOT, "apps/desktop/package.json");
let cachedElectronBinaryPath: string | undefined;
return {
apps: {
daemon: {
...daemon,
sidecarEntryPath: path.join(WORKSPACE_ROOT, "apps/daemon/src/sidecar/index.ts"),
},
desktop: {
...desktop,
get electronBinaryPath() {
if (cachedElectronBinaryPath == null) cachedElectronBinaryPath = resolveElectronBinaryPath(WORKSPACE_ROOT);
return cachedElectronBinaryPath;
},
mainEntryPath: path.join(WORKSPACE_ROOT, "apps/desktop/dist/main/index.js"),
packageJsonPath: desktopPackageJsonPath,
},
web: {
...web,
nextDistDir: resolveAppRuntimePath({ app: APP_KEYS.WEB, namespaceRoot, fileName: "next", contract: OPEN_DESIGN_SIDECAR_CONTRACT }),
nextTsconfigPath: resolveAppRuntimePath({ app: APP_KEYS.WEB, namespaceRoot, fileName: "tsconfig.json", contract: OPEN_DESIGN_SIDECAR_CONTRACT }),
sidecarEntryPath: path.join(WORKSPACE_ROOT, "apps/web/sidecar/index.ts"),
},
},
namespace,
namespaceRoot,
toolsDevRoot,
tsxCliPath: resolveTsxCliPath(),
workspaceRoot: WORKSPACE_ROOT,
};
}