Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
204 lines
8.0 KiB
JavaScript
204 lines
8.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
/**
|
|
* Dev launcher with PARALLEL + INCREMENTAL builds.
|
|
*
|
|
* Uses `tsc --build` for library packages so the compiler skips
|
|
* unchanged sub-projects via .tsbuildinfo (incremental).
|
|
*
|
|
* Dependency graph (→ compiles after):
|
|
* core ─┬→ storage
|
|
* ├→ runtime
|
|
* └→ ui
|
|
*
|
|
* libs (tsc --build tsconfig.lib.json) ─── covers core+storage+runtime+ui
|
|
* ├─→ preload (esbuild)
|
|
* └─→ filesystem worker (esbuild)
|
|
* cursor overlay (esbuild) ─── independent
|
|
* main (esbuild) ─── fast app bundle for Electron
|
|
* Vite dev server + Electron ─── fork
|
|
*/
|
|
import { spawn } from 'node:child_process';
|
|
import { join, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { createServer } from 'vite';
|
|
import { build as esbuildBuild } from 'esbuild';
|
|
import { buildCursorOverlay } from '../../../scripts/build-cursor-overlay.mjs';
|
|
import {
|
|
createDevelopmentLaunchSession,
|
|
handleDevelopmentLaunchOutcome,
|
|
waitForDevelopmentLaunchVerdict,
|
|
} from './dev-app-runtime.mjs';
|
|
|
|
const DESKTOP_DIR = resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
const REPO_ROOT = resolve(DESKTOP_DIR, '..', '..');
|
|
const TSC_CLI = join(REPO_ROOT, 'node_modules', 'typescript', 'bin', 'tsc');
|
|
const MODEL_METADATA_SYNC = join(REPO_ROOT, 'scripts', 'sync-model-metadata.mjs');
|
|
const RUNTIME_WORKER_BUILD = join(REPO_ROOT, 'packages', 'runtime', 'scripts', 'build-filesystem-worker.mjs');
|
|
|
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
function log(label, msg) {
|
|
const ts = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
|
console.log(`[${ts}][${label}] ${msg}`);
|
|
}
|
|
|
|
function runNodeTool(dir, script, args) {
|
|
return new Promise((resolve_, reject_) => {
|
|
const child = spawn(process.execPath, [script, ...args], {
|
|
cwd: dir,
|
|
stdio: ['ignore', 'inherit', 'inherit'],
|
|
});
|
|
child.on('exit', (code) => {
|
|
if (code === 0) resolve_();
|
|
else reject_(new Error(`"${script} ${args.join(' ')}" exited with code ${code}`));
|
|
});
|
|
child.on('error', reject_);
|
|
});
|
|
}
|
|
|
|
// ── build phases ─────────────────────────────────────────────────────────────
|
|
|
|
const TIMER_START = Date.now();
|
|
|
|
// A clean or ignore-scripts install has no generated model modules yet, and
|
|
// `tsc --build` bypasses workspace prebuild hooks. Generate from the committed
|
|
// snapshot before starting the incremental library graph.
|
|
log('build', 'model metadata — generating from committed snapshot');
|
|
await runNodeTool(REPO_ROOT, MODEL_METADATA_SYNC, []);
|
|
|
|
// Phase 1: all library packages via `tsc --build` (single process, shared
|
|
// .tsbuildinfo, sub-project incremental detection). The preload bundle imports
|
|
// workspace package dist files, so it starts only after that build is ready.
|
|
log('build', 'libraries — starting (tsc --build)');
|
|
const librariesBuild = runNodeTool(REPO_ROOT, TSC_CLI, ['--build', 'tsconfig.lib.json']).then(
|
|
() => log('build', 'libraries (all) — done'),
|
|
(e) => {
|
|
log('build', `libraries — FAILED: ${e.message}`);
|
|
throw e;
|
|
},
|
|
);
|
|
await Promise.all([
|
|
librariesBuild,
|
|
librariesBuild.then(() => runNodeTool(REPO_ROOT, RUNTIME_WORKER_BUILD, [])).then(
|
|
() => log('build', 'filesystem worker bundle — done'),
|
|
(e) => { log('build', `filesystem worker bundle — FAILED: ${e.message}`); throw e; },
|
|
),
|
|
// esbuild via its JS API — NOT `node node_modules/esbuild/bin/esbuild`:
|
|
// esbuild's postinstall swaps that file for a platform-native binary,
|
|
// and executing a Mach-O file with node throws SyntaxError (broke
|
|
// `npm run dev` on any machine where postinstall ran).
|
|
librariesBuild.then(() => esbuildBuild({
|
|
absWorkingDir: DESKTOP_DIR,
|
|
entryPoints: ['src/preload/preload.ts'],
|
|
bundle: true,
|
|
platform: 'node',
|
|
format: 'cjs',
|
|
outfile: 'dist/preload/preload.cjs',
|
|
external: ['electron'],
|
|
logLevel: 'warning',
|
|
})).then(
|
|
() => log('build', 'preload — done'),
|
|
(e) => { log('build', `preload — FAILED: ${e.message}`); throw e; },
|
|
),
|
|
buildCursorOverlay({ logLevel: 'warning' }).then(
|
|
() => log('build', 'cursor overlay — done'),
|
|
(e) => { log('build', `cursor overlay — FAILED: ${e.message}`); throw e; },
|
|
),
|
|
]);
|
|
|
|
// Phase 2: main — esbuild bundle for dev startup. The full
|
|
// tsconfig.main.json still compiles tests for `npm test` and typechecks
|
|
// main-process code in verification commands.
|
|
log('build', 'main — starting');
|
|
await esbuildBuild({
|
|
absWorkingDir: DESKTOP_DIR,
|
|
entryPoints: ['src/main/main.ts'],
|
|
bundle: true,
|
|
platform: 'node',
|
|
format: 'esm',
|
|
packages: 'external',
|
|
outfile: 'dist/main/main.js',
|
|
external: ['electron'],
|
|
logLevel: 'warning',
|
|
});
|
|
log('build', 'main — done');
|
|
|
|
const BUILD_MS = Date.now() - TIMER_START;
|
|
log('build', `all builds finished in ${(BUILD_MS / 1000).toFixed(1)}s`);
|
|
|
|
// ── Vite dev server + Electron ───────────────────────────────────────────────
|
|
|
|
process.chdir(DESKTOP_DIR);
|
|
log('vite', 'starting dev server...');
|
|
const server = await createServer();
|
|
await server.listen();
|
|
server.printUrls();
|
|
|
|
const devUrl = server.resolvedUrls?.local?.[0]?.replace(/\/$/, '');
|
|
if (!devUrl) {
|
|
console.error('[dev] vite did not report a local URL; aborting.');
|
|
await server.close();
|
|
process.exit(1);
|
|
}
|
|
|
|
log('electron', `launching against ${devUrl} (renderer HMR live)`);
|
|
|
|
// Created before launch so signals during codesign/preparation are durable.
|
|
// Closing the terminal still stops only launcher resources, not the
|
|
// independently owned TCC app.
|
|
const launchSession = createDevelopmentLaunchSession({
|
|
close: () => server.close(),
|
|
});
|
|
let app = null;
|
|
try {
|
|
app = await launchSession.start({ argv: process.argv.slice(2), viteUrl: devUrl });
|
|
} catch (error) {
|
|
console.error(`[dev] failed to start Electron: ${String(error)}`);
|
|
await launchSession.stop(1);
|
|
}
|
|
|
|
if (app) {
|
|
if (app.isMacosBundle) log('electron', 'launched Maka Dev.app through LaunchServices');
|
|
|
|
app.child.on('error', (err) => {
|
|
console.error(`[dev] failed to start Electron: ${err.message}`);
|
|
launchSession.stop(1);
|
|
});
|
|
if (app.isMacosBundle) {
|
|
// `open` exits 0 at the handoff, so only a failure to hand off is news here.
|
|
// The app's later lifetime is deliberately independent from this dev server.
|
|
app.child.on('exit', (code) => {
|
|
if (code) launchSession.stop(code);
|
|
});
|
|
// All branching lives in handleDevelopmentLaunchOutcome; this line is the only
|
|
// un-automated surface here (launcher scripts are non-exported, darwin-only).
|
|
waitForDevelopmentLaunchVerdict({ stopped: launchSession.isStopping, resultFile: app.resultFile }).then((outcome) =>
|
|
handleDevelopmentLaunchOutcome(outcome, {
|
|
log: (m) => console.error('[dev]', m),
|
|
exit: (code) => launchSession.stop(code),
|
|
}),
|
|
);
|
|
} else {
|
|
app.child.on('exit', (code) => launchSession.stop(code ?? 0));
|
|
}
|
|
}
|