| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- /** Build and launch the unpackaged Electron shell against the current workspace. */
- import { spawn } from 'node:child_process'
- import { existsSync, readFileSync } from 'node:fs'
- import { createRequire } from 'node:module'
- import { join, resolve } from 'node:path'
- import { parseArgs } from 'node:util'
- import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
- import type { DesktopRelease } from '../src/release.ts'
- import { prepareDevelopmentProject } from './development-project.ts'
- const APP_ROOT = resolve(import.meta.dirname, '..')
- const REPOSITORY_ROOT = resolve(APP_ROOT, '..', '..')
- const BUILD_ROOT = join(APP_ROOT, '.desktop-build')
- const DEVELOPMENT_ROOT = join(BUILD_ROOT, 'development')
- interface PackageManifest {
- readonly version?: string
- }
- function packageVersion(path: string, subject: string): string {
- const manifest = JSON.parse(readFileSync(path, 'utf8')) as PackageManifest
- if (typeof manifest.version !== 'string') throw new Error(`desktop development: ${subject} has no version`)
- return manifest.version
- }
- function debugPort(name: string, fallback: number): number {
- const value = process.env[name]
- if (value === undefined || value === '') return fallback
- const port = Number(value)
- if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
- throw new Error(`desktop development: ${name} must be an integer from 1 through 65535`)
- }
- return port
- }
- async function run(command: string, args: readonly string[], cwd: string, environment = process.env): Promise<void> {
- await new Promise<void>((resolvePromise, reject) => {
- const child = spawn(command, args, { cwd, env: environment, stdio: 'inherit' })
- child.once('error', reject)
- child.once('exit', (code, signal) => {
- if (code === 0) resolvePromise()
- else reject(new Error(`desktop development: ${args.join(' ')} exited with ${String(code ?? signal)}`))
- })
- })
- }
- async function runPackageScript(script: string, cwd: string): Promise<void> {
- const packageManager = process.env.npm_execpath
- if (packageManager === undefined || packageManager === '') {
- throw new Error('desktop development: invoke this launcher through pnpm run dev:desktop or start:desktop')
- }
- await run(process.execPath, [packageManager, 'run', script], cwd)
- }
- async function launchElectron(projectDir: string): Promise<void> {
- const require = createRequire(import.meta.url)
- const electron: unknown = require('electron')
- if (typeof electron !== 'string') throw new Error('desktop development: electron executable is unavailable')
- const mainPort = debugPort('DSH_DESKTOP_MAIN_INSPECT_PORT', 9229)
- const rendererPort = debugPort('DSH_DESKTOP_RENDERER_DEBUG_PORT', 9222)
- const hostPort = debugPort('DSH_DESKTOP_HOST_INSPECT_PORT', 9230)
- const home = resolve(process.env.DSH_HOME ?? join(DEVELOPMENT_ROOT, 'home'))
- const userData = join(DEVELOPMENT_ROOT, 'electron-user-data')
- const environment: NodeJS.ProcessEnv = {
- ...process.env,
- DSH_HOME: home,
- DSH_DESKTOP_DEV_PROJECT_DIR: projectDir,
- DSH_DESKTOP_HOST_INSPECT_PORT: String(hostPort),
- DSH_DESKTOP_NODE_BINARY: process.execPath,
- DSH_DESKTOP_OPEN_DEVTOOLS: process.env.DSH_DESKTOP_OPEN_DEVTOOLS ?? '1',
- ELECTRON_ENABLE_LOGGING: process.env.ELECTRON_ENABLE_LOGGING ?? '1',
- }
- console.log(`desktop development: DSH_HOME=${home}`)
- console.log(`desktop development: inspectors main=${String(mainPort)}, renderer=${String(rendererPort)}, host=${String(hostPort)}`)
- await run(electron, [
- `--inspect=127.0.0.1:${String(mainPort)}`,
- `--remote-debugging-port=${String(rendererPort)}`,
- `--user-data-dir=${userData}`,
- APP_ROOT,
- ], APP_ROOT, environment)
- }
- async function main(): Promise<void> {
- const { values } = parseArgs({ options: { 'skip-build': { type: 'boolean', default: false } } })
- if (!values['skip-build']) {
- await runPackageScript('build', REPOSITORY_ROOT)
- await runPackageScript('build', APP_ROOT)
- }
- for (const path of [
- join(APP_ROOT, 'lib', 'main.js'),
- join(REPOSITORY_ROOT, 'apps', 'desktop-host', 'lib', 'index.js'),
- ]) {
- if (!existsSync(path)) throw new Error(`desktop development: missing built artifact ${path}`)
- }
- const version = packageVersion(join(APP_ROOT, 'package.json'), 'desktop package')
- const pnpmVersion = packageVersion(join(APP_ROOT, 'node_modules', 'pnpm', 'package.json'), 'pnpm package')
- const release: DesktopRelease = {
- schemaVersion: 1,
- version,
- hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
- nodeVersion: process.versions.node,
- pnpmVersion,
- }
- const projectDir = prepareDevelopmentProject({
- projectDir: join(DEVELOPMENT_ROOT, 'project'),
- cliDir: join(REPOSITORY_ROOT, 'apps', 'cli'),
- hostDir: join(REPOSITORY_ROOT, 'apps', 'desktop-host'),
- dependencyDir: join(REPOSITORY_ROOT, 'node_modules', '.pnpm', 'node_modules'),
- release,
- })
- await launchElectron(projectDir)
- }
- main().catch((error: unknown) => {
- console.error(error instanceof Error ? error.message : error)
- process.exitCode = 1
- })
|