220 lines
5.6 KiB
TypeScript
220 lines
5.6 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import { spawnSync, type SpawnSyncReturns } from "node:child_process";
|
|
import { createRequire } from "node:module";
|
|
import path from "node:path";
|
|
import { app } from "electron";
|
|
import {
|
|
DESKTOP_CLI_ENV,
|
|
createNodeEntrypointInvocation as createSharedNodeEntrypointInvocation,
|
|
parseCliPassthroughArgsFromArgv as parseCliPassthroughArgs,
|
|
type NodeEntrypointArgvMode,
|
|
type NodeEntrypointInvocation,
|
|
type NodeEntrypointSpec,
|
|
} from "./node-entrypoint-launcher.js";
|
|
|
|
const CLI_PACKAGE_NAME = "@getpaseo/cli";
|
|
const SERVER_PACKAGE_NAME = "@getpaseo/server";
|
|
const CLI_BIN_ENTRY = `${CLI_PACKAGE_NAME}/bin/paseo`;
|
|
|
|
type PackageInfo = {
|
|
root: string;
|
|
};
|
|
|
|
const esmRequire = createRequire(__filename);
|
|
|
|
function findPackageRootFromResolvedPath(input: {
|
|
resolvedPath: string;
|
|
packageName: string;
|
|
}): PackageInfo {
|
|
let currentDir = path.dirname(input.resolvedPath);
|
|
|
|
while (true) {
|
|
const packageJsonPath = path.join(currentDir, "package.json");
|
|
if (existsSync(packageJsonPath)) {
|
|
try {
|
|
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as {
|
|
name?: string;
|
|
};
|
|
if (pkg.name === input.packageName) {
|
|
return {
|
|
root: currentDir,
|
|
};
|
|
}
|
|
} catch {
|
|
// Ignore malformed package metadata while walking up.
|
|
}
|
|
}
|
|
|
|
const parent = path.dirname(currentDir);
|
|
if (parent === currentDir) {
|
|
break;
|
|
}
|
|
currentDir = parent;
|
|
}
|
|
|
|
throw new Error(`Unable to resolve ${input.packageName} package root`);
|
|
}
|
|
|
|
function resolveServerPackageInfo(): PackageInfo {
|
|
const serverExportPath = esmRequire.resolve(SERVER_PACKAGE_NAME);
|
|
return findPackageRootFromResolvedPath({
|
|
resolvedPath: serverExportPath,
|
|
packageName: SERVER_PACKAGE_NAME,
|
|
});
|
|
}
|
|
|
|
function resolveCliPackageInfo(): PackageInfo {
|
|
const cliBinPath = esmRequire.resolve(CLI_BIN_ENTRY);
|
|
return findPackageRootFromResolvedPath({
|
|
resolvedPath: cliBinPath,
|
|
packageName: CLI_PACKAGE_NAME,
|
|
});
|
|
}
|
|
|
|
function resolvePackagedAsarPath(): string {
|
|
return path.join(process.resourcesPath, "app.asar");
|
|
}
|
|
|
|
function resolvePackagedNodeEntrypointRunnerPath(): string {
|
|
return path.join(resolvePackagedAsarPath(), "dist", "daemon", "node-entrypoint-runner.js");
|
|
}
|
|
|
|
function assertPathExists(input: { label: string; filePath: string }): string {
|
|
if (!existsSync(input.filePath)) {
|
|
throw new Error(`${input.label} is missing at ${input.filePath}`);
|
|
}
|
|
|
|
return input.filePath;
|
|
}
|
|
|
|
export function parseCliPassthroughArgsFromArgv(argv: string[]): string[] | null {
|
|
return parseCliPassthroughArgs({
|
|
argv,
|
|
isDefaultApp: process.defaultApp,
|
|
forceCli: process.env[DESKTOP_CLI_ENV] === "1",
|
|
});
|
|
}
|
|
|
|
export function resolveDaemonRunnerEntrypoint(): NodeEntrypointSpec {
|
|
if (app.isPackaged) {
|
|
return {
|
|
entryPath: assertPathExists({
|
|
label: "Bundled daemon runner",
|
|
filePath: path.join(
|
|
resolvePackagedAsarPath(),
|
|
"node_modules",
|
|
"@getpaseo",
|
|
"server",
|
|
"dist",
|
|
"scripts",
|
|
"daemon-runner.js",
|
|
),
|
|
}),
|
|
execArgv: [],
|
|
};
|
|
}
|
|
|
|
const serverPackage = resolveServerPackageInfo();
|
|
const distRunner = path.join(serverPackage.root, "dist", "scripts", "daemon-runner.js");
|
|
if (existsSync(distRunner)) {
|
|
return {
|
|
entryPath: distRunner,
|
|
execArgv: [],
|
|
};
|
|
}
|
|
|
|
return {
|
|
entryPath: assertPathExists({
|
|
label: "Daemon runner source",
|
|
filePath: path.join(serverPackage.root, "scripts", "daemon-runner.ts"),
|
|
}),
|
|
execArgv: ["--import", "tsx"],
|
|
};
|
|
}
|
|
|
|
export function resolveCliEntrypoint(): NodeEntrypointSpec {
|
|
if (app.isPackaged) {
|
|
return {
|
|
entryPath: assertPathExists({
|
|
label: "Bundled CLI entrypoint",
|
|
filePath: path.join(
|
|
resolvePackagedAsarPath(),
|
|
"node_modules",
|
|
"@getpaseo",
|
|
"cli",
|
|
"dist",
|
|
"index.js",
|
|
),
|
|
}),
|
|
execArgv: [],
|
|
};
|
|
}
|
|
|
|
const cliPackage = resolveCliPackageInfo();
|
|
const distEntry = path.join(cliPackage.root, "dist", "index.js");
|
|
if (existsSync(distEntry)) {
|
|
return {
|
|
entryPath: distEntry,
|
|
execArgv: [],
|
|
};
|
|
}
|
|
|
|
return {
|
|
entryPath: assertPathExists({
|
|
label: "CLI source entrypoint",
|
|
filePath: path.join(cliPackage.root, "src", "index.ts"),
|
|
}),
|
|
execArgv: ["--import", "tsx"],
|
|
};
|
|
}
|
|
|
|
export function createNodeEntrypointInvocation(input: {
|
|
entrypoint: NodeEntrypointSpec;
|
|
argvMode: NodeEntrypointArgvMode;
|
|
args: string[];
|
|
baseEnv: NodeJS.ProcessEnv;
|
|
}): NodeEntrypointInvocation {
|
|
return createSharedNodeEntrypointInvocation({
|
|
execPath: process.execPath,
|
|
isPackaged: app.isPackaged,
|
|
packagedRunnerPath: app.isPackaged
|
|
? assertPathExists({
|
|
label: "Bundled node entrypoint runner",
|
|
filePath: resolvePackagedNodeEntrypointRunnerPath(),
|
|
})
|
|
: null,
|
|
entrypoint: input.entrypoint,
|
|
argvMode: input.argvMode,
|
|
args: input.args,
|
|
baseEnv: input.baseEnv,
|
|
});
|
|
}
|
|
|
|
function spawnCliProcess(args: string[]): SpawnSyncReturns<Buffer> {
|
|
const cli = resolveCliEntrypoint();
|
|
const invocation = createNodeEntrypointInvocation({
|
|
entrypoint: cli,
|
|
argvMode: "bare",
|
|
args,
|
|
baseEnv: process.env,
|
|
});
|
|
|
|
return spawnSync(invocation.command, invocation.args, {
|
|
env: invocation.env,
|
|
stdio: "inherit",
|
|
});
|
|
}
|
|
|
|
export function runCliPassthroughCommand(args: string[]): number {
|
|
const result = spawnCliProcess(args);
|
|
if (result.error) {
|
|
throw result.error;
|
|
}
|
|
|
|
if (typeof result.status === "number") {
|
|
return result.status;
|
|
}
|
|
|
|
return result.signal ? 1 : 0;
|
|
}
|