elit 3.5.6 → 3.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/desktop/build.rs +83 -0
- package/desktop/icon.rs +106 -0
- package/desktop/lib.rs +2 -0
- package/desktop/main.rs +235 -0
- package/desktop/native_main.rs +128 -0
- package/desktop/native_renderer/action_widgets.rs +184 -0
- package/desktop/native_renderer/app_models.rs +171 -0
- package/desktop/native_renderer/app_runtime.rs +140 -0
- package/desktop/native_renderer/container_rendering.rs +610 -0
- package/desktop/native_renderer/content_widgets.rs +634 -0
- package/desktop/native_renderer/css_models.rs +371 -0
- package/desktop/native_renderer/embedded_surfaces.rs +414 -0
- package/desktop/native_renderer/form_controls.rs +516 -0
- package/desktop/native_renderer/interaction_dispatch.rs +89 -0
- package/desktop/native_renderer/runtime_support.rs +135 -0
- package/desktop/native_renderer/utilities.rs +495 -0
- package/desktop/native_renderer/vector_drawing.rs +491 -0
- package/desktop/native_renderer.rs +4122 -0
- package/desktop/runtime/external.rs +422 -0
- package/desktop/runtime/mod.rs +67 -0
- package/desktop/runtime/quickjs.rs +106 -0
- package/desktop/window.rs +383 -0
- package/dist/build.d.ts +1 -1
- package/dist/cli.cjs +16 -2
- package/dist/cli.mjs +16 -2
- package/dist/config.d.ts +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/desktop-auto-render.cjs +2370 -0
- package/dist/desktop-auto-render.d.ts +13 -0
- package/dist/desktop-auto-render.js +2341 -0
- package/dist/desktop-auto-render.mjs +2344 -0
- package/dist/render-context.cjs +118 -0
- package/dist/render-context.d.ts +39 -0
- package/dist/render-context.js +77 -0
- package/dist/render-context.mjs +87 -0
- package/dist/{server-CNgDUgSZ.d.ts → server-FCdUqabc.d.ts} +1 -1
- package/dist/server.d.ts +1 -1
- package/package.json +26 -3
- package/dist/build.d.mts +0 -20
- package/dist/chokidar.d.mts +0 -134
- package/dist/cli.d.mts +0 -81
- package/dist/config.d.mts +0 -254
- package/dist/coverage.d.mts +0 -85
- package/dist/database.d.mts +0 -52
- package/dist/desktop.d.mts +0 -68
- package/dist/dom.d.mts +0 -87
- package/dist/el.d.mts +0 -208
- package/dist/fs.d.mts +0 -255
- package/dist/hmr.d.mts +0 -38
- package/dist/http.d.mts +0 -169
- package/dist/https.d.mts +0 -108
- package/dist/index.d.mts +0 -13
- package/dist/mime-types.d.mts +0 -48
- package/dist/native.d.mts +0 -136
- package/dist/path.d.mts +0 -163
- package/dist/router.d.mts +0 -49
- package/dist/runtime.d.mts +0 -97
- package/dist/server-D0Dp4R5z.d.mts +0 -449
- package/dist/server.d.mts +0 -7
- package/dist/state.d.mts +0 -117
- package/dist/style.d.mts +0 -232
- package/dist/test-reporter.d.mts +0 -77
- package/dist/test-runtime.d.mts +0 -122
- package/dist/test.d.mts +0 -39
- package/dist/types.d.mts +0 -586
- package/dist/universal.d.mts +0 -21
- package/dist/ws.d.mts +0 -200
- package/dist/wss.d.mts +0 -108
- package/src/build.ts +0 -362
- package/src/chokidar.ts +0 -427
- package/src/cli.ts +0 -1162
- package/src/config.ts +0 -509
- package/src/coverage.ts +0 -1479
- package/src/database.ts +0 -1410
- package/src/desktop-auto-render.ts +0 -317
- package/src/desktop-cli.ts +0 -1533
- package/src/desktop.ts +0 -99
- package/src/dev-build.ts +0 -340
- package/src/dom.ts +0 -901
- package/src/el.ts +0 -183
- package/src/fs.ts +0 -609
- package/src/hmr.ts +0 -149
- package/src/http.ts +0 -856
- package/src/https.ts +0 -411
- package/src/index.ts +0 -16
- package/src/mime-types.ts +0 -222
- package/src/mobile-cli.ts +0 -2313
- package/src/native-background.ts +0 -444
- package/src/native-border.ts +0 -343
- package/src/native-canvas.ts +0 -260
- package/src/native-cli.ts +0 -414
- package/src/native-color.ts +0 -904
- package/src/native-estimation.ts +0 -194
- package/src/native-grid.ts +0 -590
- package/src/native-interaction.ts +0 -1289
- package/src/native-layout.ts +0 -568
- package/src/native-link.ts +0 -76
- package/src/native-render-support.ts +0 -361
- package/src/native-spacing.ts +0 -231
- package/src/native-state.ts +0 -318
- package/src/native-strings.ts +0 -46
- package/src/native-transform.ts +0 -120
- package/src/native-types.ts +0 -439
- package/src/native-typography.ts +0 -254
- package/src/native-units.ts +0 -441
- package/src/native-vector.ts +0 -910
- package/src/native.ts +0 -5606
- package/src/path.ts +0 -493
- package/src/pm-cli.ts +0 -2498
- package/src/preview-build.ts +0 -294
- package/src/render-context.ts +0 -138
- package/src/router.ts +0 -260
- package/src/runtime.ts +0 -97
- package/src/server.ts +0 -2294
- package/src/state.ts +0 -556
- package/src/style.ts +0 -1790
- package/src/test-globals.d.ts +0 -184
- package/src/test-reporter.ts +0 -609
- package/src/test-runtime.ts +0 -1359
- package/src/test.ts +0 -368
- package/src/types.ts +0 -381
- package/src/universal.ts +0 -81
- package/src/wapk-cli.ts +0 -3213
- package/src/workspace-package.ts +0 -102
- package/src/ws.ts +0 -648
- package/src/wss.ts +0 -241
package/src/desktop-cli.ts
DELETED
|
@@ -1,1533 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { createRequire } from 'node:module';
|
|
5
|
-
import { basename, dirname, extname, join, relative, resolve } from 'node:path';
|
|
6
|
-
import { pathToFileURL } from 'node:url';
|
|
7
|
-
|
|
8
|
-
import { build as esbuild } from 'esbuild';
|
|
9
|
-
import { loadConfig, type DesktopConfig, type DesktopMode } from './config';
|
|
10
|
-
import { renderMaterializedNativeTree, type NativeTree } from './native';
|
|
11
|
-
import { loadNativeEntryResult } from './native-cli';
|
|
12
|
-
import type { DesktopRenderOptions } from './render-context';
|
|
13
|
-
import { resolveWorkspacePackageImport } from './workspace-package';
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
WAPK_RUNTIMES,
|
|
17
|
-
createWapkLiveSync,
|
|
18
|
-
getWapkRuntimeArgs,
|
|
19
|
-
prepareWapkApp,
|
|
20
|
-
resolveWapkRuntimeExecutable,
|
|
21
|
-
type PreparedWapkApp,
|
|
22
|
-
type WapkRuntimeName,
|
|
23
|
-
} from './wapk-cli';
|
|
24
|
-
|
|
25
|
-
type DesktopRuntimeName = 'quickjs' | 'bun' | 'node' | 'deno';
|
|
26
|
-
type DesktopCompilerName = 'auto' | 'none' | 'esbuild' | 'tsx' | 'tsup';
|
|
27
|
-
type DesktopFormat = 'iife' | 'cjs' | 'esm';
|
|
28
|
-
type DesktopPlatform = keyof typeof PLATFORMS;
|
|
29
|
-
|
|
30
|
-
type TsupModule = typeof import('tsup');
|
|
31
|
-
|
|
32
|
-
interface DesktopRunOptions {
|
|
33
|
-
mode: DesktopMode;
|
|
34
|
-
runtime: DesktopRuntimeName;
|
|
35
|
-
compiler: DesktopCompilerName;
|
|
36
|
-
exportName?: string;
|
|
37
|
-
release: boolean;
|
|
38
|
-
entry?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface DesktopBuildOptions extends DesktopRunOptions {
|
|
42
|
-
outDir: string;
|
|
43
|
-
platform?: DesktopPlatform;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface EnsureBinaryOptions {
|
|
47
|
-
runtime: DesktopRuntimeName;
|
|
48
|
-
release: boolean;
|
|
49
|
-
triple?: string;
|
|
50
|
-
entryPath?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface EnsureNativeBinaryOptions {
|
|
54
|
-
release: boolean;
|
|
55
|
-
triple?: string;
|
|
56
|
-
entryPath?: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface PreparedEntry {
|
|
60
|
-
appName: string;
|
|
61
|
-
entryPath: string;
|
|
62
|
-
cleanupPath?: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
interface PreparedDesktopNativePayload {
|
|
66
|
-
appName: string;
|
|
67
|
-
payloadPath: string;
|
|
68
|
-
cleanupPath?: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface DesktopNativeWindowOptions {
|
|
72
|
-
title: string;
|
|
73
|
-
width: number;
|
|
74
|
-
height: number;
|
|
75
|
-
center: boolean;
|
|
76
|
-
icon?: string;
|
|
77
|
-
autoClose?: boolean;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
interface DesktopNativeInteractionOutput {
|
|
81
|
-
file?: string;
|
|
82
|
-
stdout?: boolean;
|
|
83
|
-
emitReady?: boolean;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface DesktopNativePayload {
|
|
87
|
-
window: DesktopNativeWindowOptions;
|
|
88
|
-
resourceBaseDir?: string;
|
|
89
|
-
interactionOutput?: DesktopNativeInteractionOutput;
|
|
90
|
-
tree: NativeTree;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
interface DesktopBootstrapEntry {
|
|
94
|
-
bootstrapPath: string;
|
|
95
|
-
cleanupPaths: string[];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
interface DesktopWapkRunOptions {
|
|
99
|
-
runtime?: WapkRuntimeName;
|
|
100
|
-
release: boolean;
|
|
101
|
-
file: string;
|
|
102
|
-
syncInterval?: number;
|
|
103
|
-
useWatcher?: boolean;
|
|
104
|
-
password?: string;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const PACKAGE_ROOT = resolve(__dirname, '..');
|
|
108
|
-
const DESKTOP_RUNTIMES: DesktopRuntimeName[] = ['quickjs', 'bun', 'node', 'deno'];
|
|
109
|
-
const DESKTOP_COMPILERS: DesktopCompilerName[] = ['auto', 'none', 'esbuild', 'tsx', 'tsup'];
|
|
110
|
-
const BUILD_FEATURES: Record<DesktopRuntimeName, string[]> = {
|
|
111
|
-
quickjs: ['runtime-quickjs'],
|
|
112
|
-
bun: ['runtime-external'],
|
|
113
|
-
node: ['runtime-external'],
|
|
114
|
-
deno: ['runtime-external'],
|
|
115
|
-
};
|
|
116
|
-
const EMBED_MAGIC_V2 = Buffer.from([0x57, 0x41, 0x50, 0x4b, 0x52, 0x54, 0x00, 0x02]);
|
|
117
|
-
const EMBED_NATIVE_MAGIC_V1 = Buffer.from([0x45, 0x4c, 0x49, 0x54, 0x4e, 0x55, 0x49, 0x31]);
|
|
118
|
-
const EMBED_RUNTIME_CODE: Record<DesktopRuntimeName, number> = {
|
|
119
|
-
quickjs: 1,
|
|
120
|
-
bun: 2,
|
|
121
|
-
node: 3,
|
|
122
|
-
deno: 4,
|
|
123
|
-
};
|
|
124
|
-
const PLATFORMS = {
|
|
125
|
-
windows: 'x86_64-pc-windows-msvc',
|
|
126
|
-
win: 'x86_64-pc-windows-msvc',
|
|
127
|
-
'windows-arm': 'aarch64-pc-windows-msvc',
|
|
128
|
-
'win-arm': 'aarch64-pc-windows-msvc',
|
|
129
|
-
linux: 'x86_64-unknown-linux-gnu',
|
|
130
|
-
'linux-musl': 'x86_64-unknown-linux-musl',
|
|
131
|
-
'linux-arm': 'aarch64-unknown-linux-gnu',
|
|
132
|
-
macos: 'x86_64-apple-darwin',
|
|
133
|
-
mac: 'x86_64-apple-darwin',
|
|
134
|
-
darwin: 'x86_64-apple-darwin',
|
|
135
|
-
'macos-arm': 'aarch64-apple-darwin',
|
|
136
|
-
'mac-arm': 'aarch64-apple-darwin',
|
|
137
|
-
} as const;
|
|
138
|
-
const TS_LIKE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.jsx']);
|
|
139
|
-
|
|
140
|
-
function toDesktopBootstrapImportPath(fromPath: string, toPath: string): string {
|
|
141
|
-
const importPath = relative(dirname(fromPath), toPath).replace(/\\/g, '/');
|
|
142
|
-
return importPath.startsWith('./') || importPath.startsWith('../') ? importPath : `./${importPath}`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function formatDesktopDisplayName(value: string): string {
|
|
146
|
-
if (!value) {
|
|
147
|
-
return 'Elit';
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return value
|
|
151
|
-
.split(/[._-]+/)
|
|
152
|
-
.filter(Boolean)
|
|
153
|
-
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
154
|
-
.join(' ');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function resolveDesktopEntryDisplayName(entryPath: string, fallbackName: string): string {
|
|
158
|
-
let currentDir = dirname(resolve(entryPath));
|
|
159
|
-
|
|
160
|
-
while (true) {
|
|
161
|
-
const packageJsonPath = join(currentDir, 'package.json');
|
|
162
|
-
if (existsSync(packageJsonPath)) {
|
|
163
|
-
try {
|
|
164
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
|
|
165
|
-
appName?: string;
|
|
166
|
-
name?: string;
|
|
167
|
-
productName?: string;
|
|
168
|
-
};
|
|
169
|
-
const configuredName = packageJson.productName ?? packageJson.appName ?? packageJson.name;
|
|
170
|
-
if (configuredName) {
|
|
171
|
-
return formatDesktopDisplayName(configuredName);
|
|
172
|
-
}
|
|
173
|
-
} catch {
|
|
174
|
-
// Ignore invalid package metadata while walking upward.
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const parentDir = dirname(currentDir);
|
|
179
|
-
if (parentDir === currentDir) {
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
currentDir = parentDir;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return formatDesktopDisplayName(fallbackName);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function createDesktopBootstrapEntry(entryPath: string, appName: string): DesktopBootstrapEntry {
|
|
189
|
-
const bootstrapId = randomUUID();
|
|
190
|
-
const bootstrapPath = join(dirname(entryPath), `.elit-desktop-bootstrap-${appName}-${bootstrapId}.ts`);
|
|
191
|
-
const preludePath = join(dirname(entryPath), `.elit-desktop-prelude-${appName}-${bootstrapId}.ts`);
|
|
192
|
-
const desktopAutoRenderPath = resolve(PACKAGE_ROOT, 'src', 'desktop-auto-render.ts');
|
|
193
|
-
const renderContextPath = resolve(PACKAGE_ROOT, 'src', 'render-context.ts');
|
|
194
|
-
const defaultTitle = `${resolveDesktopEntryDisplayName(entryPath, appName)} Desktop`;
|
|
195
|
-
|
|
196
|
-
writeFileSync(
|
|
197
|
-
preludePath,
|
|
198
|
-
[
|
|
199
|
-
`import { installDesktopRenderTracking } from ${JSON.stringify(toDesktopBootstrapImportPath(preludePath, desktopAutoRenderPath))};`,
|
|
200
|
-
`import { clearCapturedRenderedVNode, clearDesktopRenderOptions, setRenderRuntimeTarget } from ${JSON.stringify(toDesktopBootstrapImportPath(preludePath, renderContextPath))};`,
|
|
201
|
-
'',
|
|
202
|
-
`setRenderRuntimeTarget(${JSON.stringify('desktop')});`,
|
|
203
|
-
'clearCapturedRenderedVNode();',
|
|
204
|
-
'clearDesktopRenderOptions();',
|
|
205
|
-
'installDesktopRenderTracking();',
|
|
206
|
-
'',
|
|
207
|
-
].join('\n'),
|
|
208
|
-
'utf8',
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
writeFileSync(
|
|
212
|
-
bootstrapPath,
|
|
213
|
-
[
|
|
214
|
-
`import { completeDesktopAutoRender } from ${JSON.stringify(toDesktopBootstrapImportPath(bootstrapPath, desktopAutoRenderPath))};`,
|
|
215
|
-
`import ${JSON.stringify(toDesktopBootstrapImportPath(bootstrapPath, preludePath))};`,
|
|
216
|
-
`import ${JSON.stringify(toDesktopBootstrapImportPath(bootstrapPath, entryPath))};`,
|
|
217
|
-
'',
|
|
218
|
-
`const desktopAutoRenderOptions = ${JSON.stringify({
|
|
219
|
-
center: true,
|
|
220
|
-
height: 720,
|
|
221
|
-
title: defaultTitle,
|
|
222
|
-
width: 1080,
|
|
223
|
-
})};`,
|
|
224
|
-
'',
|
|
225
|
-
'try {',
|
|
226
|
-
' completeDesktopAutoRender(desktopAutoRenderOptions);',
|
|
227
|
-
'} catch (error) {',
|
|
228
|
-
' console.error(error);',
|
|
229
|
-
' if (typeof process !== "undefined" && typeof process.exit === "function") {',
|
|
230
|
-
' process.exit(1);',
|
|
231
|
-
' }',
|
|
232
|
-
' throw error;',
|
|
233
|
-
'}',
|
|
234
|
-
'',
|
|
235
|
-
].join('\n'),
|
|
236
|
-
'utf8',
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
bootstrapPath,
|
|
241
|
-
cleanupPaths: [bootstrapPath, preludePath],
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function createWorkspacePackagePlugin(entryDir: string) {
|
|
246
|
-
return {
|
|
247
|
-
name: 'workspace-package-self-reference',
|
|
248
|
-
setup(build: any) {
|
|
249
|
-
build.onResolve({ filter: /^elit(?:\/.*)?$/ }, (args: { path: string; resolveDir?: string }) => {
|
|
250
|
-
const resolved = resolveWorkspacePackageImport(args.path, args.resolveDir || entryDir);
|
|
251
|
-
return resolved ? { path: resolved } : undefined;
|
|
252
|
-
});
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export async function runDesktopCommand(args: string[]): Promise<void> {
|
|
258
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
259
|
-
printDesktopHelp();
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const config = await loadConfig();
|
|
264
|
-
const desktopConfig = config?.desktop;
|
|
265
|
-
|
|
266
|
-
if (args.length === 0 && !resolveConfiguredDesktopEntry(getDefaultDesktopMode(desktopConfig), desktopConfig)) {
|
|
267
|
-
printDesktopHelp();
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (args[0] === 'wapk') {
|
|
272
|
-
await runDesktopWapkCommand(args.slice(1), desktopConfig);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (args[0] === 'run') {
|
|
277
|
-
await runDesktopRuntime(parseDesktopRunArgs(args.slice(1), desktopConfig));
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (args[0] === 'build') {
|
|
282
|
-
await buildDesktopBundle(parseDesktopBuildArgs(args.slice(1), desktopConfig));
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
await runDesktopRuntime(parseDesktopRunArgs(args, desktopConfig));
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function parseDesktopRunArgs(args: string[], config?: DesktopConfig): DesktopRunOptions {
|
|
290
|
-
const options: DesktopRunOptions = {
|
|
291
|
-
mode: getDefaultDesktopMode(config),
|
|
292
|
-
entry: undefined,
|
|
293
|
-
exportName: config?.native?.exportName,
|
|
294
|
-
runtime: config?.runtime ?? 'quickjs',
|
|
295
|
-
compiler: config?.compiler ?? 'auto',
|
|
296
|
-
release: config?.release ?? false,
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
for (let i = 0; i < args.length; i++) {
|
|
300
|
-
const arg = args[i];
|
|
301
|
-
|
|
302
|
-
switch (arg) {
|
|
303
|
-
case '--mode':
|
|
304
|
-
case '-m': {
|
|
305
|
-
const mode = args[++i];
|
|
306
|
-
if (!mode) {
|
|
307
|
-
throw new Error('Missing value for --mode');
|
|
308
|
-
}
|
|
309
|
-
options.mode = parseDesktopMode(mode, '--mode');
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
case '--runtime':
|
|
313
|
-
case '-r': {
|
|
314
|
-
const runtime = args[++i] as DesktopRuntimeName | undefined;
|
|
315
|
-
if (!runtime || !DESKTOP_RUNTIMES.includes(runtime)) {
|
|
316
|
-
throw new Error(`Unknown desktop runtime: ${runtime}`);
|
|
317
|
-
}
|
|
318
|
-
options.runtime = runtime;
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
case '--compiler':
|
|
322
|
-
case '-c': {
|
|
323
|
-
const compiler = args[++i] as DesktopCompilerName | undefined;
|
|
324
|
-
if (!compiler || !DESKTOP_COMPILERS.includes(compiler)) {
|
|
325
|
-
throw new Error(`Unknown desktop compiler: ${compiler}`);
|
|
326
|
-
}
|
|
327
|
-
options.compiler = compiler;
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
case '--export': {
|
|
331
|
-
const exportName = args[++i];
|
|
332
|
-
if (!exportName) {
|
|
333
|
-
throw new Error('Missing value for --export');
|
|
334
|
-
}
|
|
335
|
-
options.exportName = exportName;
|
|
336
|
-
break;
|
|
337
|
-
}
|
|
338
|
-
case '--release':
|
|
339
|
-
options.release = true;
|
|
340
|
-
break;
|
|
341
|
-
default:
|
|
342
|
-
if (!arg.startsWith('-')) {
|
|
343
|
-
options.entry = arg;
|
|
344
|
-
}
|
|
345
|
-
break;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
options.entry = options.entry ?? resolveConfiguredDesktopEntry(options.mode, config);
|
|
350
|
-
|
|
351
|
-
if (!options.entry) {
|
|
352
|
-
throw new Error(
|
|
353
|
-
`Desktop ${options.mode} mode requires an entry file, either from the command line or ${desktopEntrySourceLabel(options.mode)} in elit.config.ts.`,
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return options;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function parseDesktopBuildArgs(args: string[], config?: DesktopConfig): DesktopBuildOptions {
|
|
361
|
-
const options: DesktopBuildOptions = {
|
|
362
|
-
mode: getDefaultDesktopMode(config),
|
|
363
|
-
entry: undefined,
|
|
364
|
-
exportName: config?.native?.exportName,
|
|
365
|
-
runtime: config?.runtime ?? 'quickjs',
|
|
366
|
-
compiler: config?.compiler ?? 'auto',
|
|
367
|
-
release: config?.release ?? false,
|
|
368
|
-
outDir: config?.outDir ?? 'dist',
|
|
369
|
-
platform: config?.platform as DesktopPlatform | undefined,
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
for (let i = 0; i < args.length; i++) {
|
|
373
|
-
const arg = args[i];
|
|
374
|
-
|
|
375
|
-
switch (arg) {
|
|
376
|
-
case '--mode':
|
|
377
|
-
case '-m': {
|
|
378
|
-
const mode = args[++i];
|
|
379
|
-
if (!mode) {
|
|
380
|
-
throw new Error('Missing value for --mode');
|
|
381
|
-
}
|
|
382
|
-
options.mode = parseDesktopMode(mode, '--mode');
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
case '--runtime':
|
|
386
|
-
case '-r': {
|
|
387
|
-
const runtime = args[++i] as DesktopRuntimeName | undefined;
|
|
388
|
-
if (!runtime || !DESKTOP_RUNTIMES.includes(runtime)) {
|
|
389
|
-
throw new Error(`Unknown desktop runtime: ${runtime}`);
|
|
390
|
-
}
|
|
391
|
-
options.runtime = runtime;
|
|
392
|
-
break;
|
|
393
|
-
}
|
|
394
|
-
case '--compiler':
|
|
395
|
-
case '-c': {
|
|
396
|
-
const compiler = args[++i] as DesktopCompilerName | undefined;
|
|
397
|
-
if (!compiler || !DESKTOP_COMPILERS.includes(compiler)) {
|
|
398
|
-
throw new Error(`Unknown desktop compiler: ${compiler}`);
|
|
399
|
-
}
|
|
400
|
-
options.compiler = compiler;
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
|
-
case '--export': {
|
|
404
|
-
const exportName = args[++i];
|
|
405
|
-
if (!exportName) {
|
|
406
|
-
throw new Error('Missing value for --export');
|
|
407
|
-
}
|
|
408
|
-
options.exportName = exportName;
|
|
409
|
-
break;
|
|
410
|
-
}
|
|
411
|
-
case '--platform':
|
|
412
|
-
case '-p': {
|
|
413
|
-
const platform = args[++i] as DesktopPlatform | undefined;
|
|
414
|
-
if (!platform || !(platform in PLATFORMS)) {
|
|
415
|
-
throw new Error(`Unknown desktop platform: ${platform}`);
|
|
416
|
-
}
|
|
417
|
-
options.platform = platform;
|
|
418
|
-
break;
|
|
419
|
-
}
|
|
420
|
-
case '--out-dir':
|
|
421
|
-
case '-o': {
|
|
422
|
-
const outDir = args[++i];
|
|
423
|
-
if (!outDir) {
|
|
424
|
-
throw new Error('Desktop build requires an output directory value.');
|
|
425
|
-
}
|
|
426
|
-
options.outDir = outDir;
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
429
|
-
case '--release':
|
|
430
|
-
options.release = true;
|
|
431
|
-
break;
|
|
432
|
-
default:
|
|
433
|
-
if (!arg.startsWith('-')) {
|
|
434
|
-
options.entry = arg;
|
|
435
|
-
}
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
options.entry = options.entry ?? resolveConfiguredDesktopEntry(options.mode, config);
|
|
441
|
-
|
|
442
|
-
return options;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
export function parseDesktopMode(value: string, source: string): DesktopMode {
|
|
446
|
-
if (value === 'native' || value === 'hybrid') {
|
|
447
|
-
return value;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
throw new Error(`Invalid ${source}: ${value}. Expected "native" or "hybrid".`);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
export function getDefaultDesktopMode(config?: DesktopConfig): DesktopMode {
|
|
454
|
-
if (config?.mode) {
|
|
455
|
-
return parseDesktopMode(config.mode, 'desktop.mode');
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return config?.native?.entry ? 'native' : 'hybrid';
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
export function resolveConfiguredDesktopEntry(mode: DesktopMode, config?: DesktopConfig): string | undefined {
|
|
462
|
-
if (mode === 'native') {
|
|
463
|
-
return config?.native?.entry ?? config?.entry;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
return config?.entry;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function desktopEntrySourceLabel(mode: DesktopMode): string {
|
|
470
|
-
return mode === 'native' ? 'desktop.native.entry' : 'desktop.entry';
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function printDesktopHelp(): void {
|
|
474
|
-
console.log([
|
|
475
|
-
'',
|
|
476
|
-
'Desktop mode for Elit',
|
|
477
|
-
'',
|
|
478
|
-
'Usage:',
|
|
479
|
-
' elit desktop [options] [entry]',
|
|
480
|
-
' elit desktop run [options] [entry]',
|
|
481
|
-
' elit desktop wapk [options] <file.wapk>',
|
|
482
|
-
' elit desktop wapk run [options] <file.wapk>',
|
|
483
|
-
' elit desktop build [options] [entry]',
|
|
484
|
-
' elit desktop build [options]',
|
|
485
|
-
'',
|
|
486
|
-
'Run options:',
|
|
487
|
-
' -m, --mode <name> Desktop mode: hybrid, native',
|
|
488
|
-
' -r, --runtime <name> Desktop runtime: quickjs, bun, node, deno',
|
|
489
|
-
' -c, --compiler <name> Entry transpiler: auto, none, esbuild, tsx, tsup (default: auto)',
|
|
490
|
-
' --release Use the release desktop runtime binary',
|
|
491
|
-
'',
|
|
492
|
-
'Build options:',
|
|
493
|
-
' -m, --mode <name> Desktop mode: hybrid, native',
|
|
494
|
-
' -r, --runtime <name> Runtime to embed in the app binary',
|
|
495
|
-
' -c, --compiler <name> Entry transpiler: auto, none, esbuild, tsx, tsup (default: auto)',
|
|
496
|
-
` -p, --platform <name> Target platform (${Object.keys(PLATFORMS).join(', ')})`,
|
|
497
|
-
' -o, --out-dir <dir> Output directory (default: dist)',
|
|
498
|
-
' --release Build the desktop runtime in release mode',
|
|
499
|
-
'',
|
|
500
|
-
'Desktop WAPK options:',
|
|
501
|
-
' -r, --runtime <name> Packaged app runtime: node, bun, deno',
|
|
502
|
-
' --sync-interval <ms> Polling interval for live sync (ms, default 300)',
|
|
503
|
-
' --watcher, --use-watcher Use event-driven file watcher instead of polling',
|
|
504
|
-
' --password <value> Password used to unlock a protected archive',
|
|
505
|
-
' --release Use the release desktop runtime binary',
|
|
506
|
-
'',
|
|
507
|
-
'Examples:',
|
|
508
|
-
' elit desktop src/main.ts',
|
|
509
|
-
' elit desktop run --mode native',
|
|
510
|
-
' elit desktop --runtime node app.ts',
|
|
511
|
-
' elit desktop wapk app.wapk',
|
|
512
|
-
' elit desktop wapk run app.wapk --runtime bun',
|
|
513
|
-
' elit desktop wapk app.wapk --watcher',
|
|
514
|
-
' elit desktop build src/main.ts',
|
|
515
|
-
' elit desktop build --mode native --release',
|
|
516
|
-
' elit desktop build --runtime bun --release src/main.ts',
|
|
517
|
-
'',
|
|
518
|
-
'Notes:',
|
|
519
|
-
' - Cargo is required to build the native WebView runtime.',
|
|
520
|
-
' - TypeScript and module-style QuickJS entries are transpiled automatically.',
|
|
521
|
-
' - The tsx compiler is Node-only and keeps loading the original source tree.',
|
|
522
|
-
' - The tsx and tsup compilers require those packages to be installed.',
|
|
523
|
-
' - desktop.mode defaults to "native" when desktop.native.entry exists, otherwise "hybrid".',
|
|
524
|
-
' - desktop.entry is used for hybrid mode. desktop.native.entry is used for native mode.',
|
|
525
|
-
' - Native mode falls back to desktop.entry when only the legacy desktop.entry is configured.',
|
|
526
|
-
' - Hybrid mode uses the WebView desktop runtime. Native mode renders the native IR in the dedicated native desktop runtime.',
|
|
527
|
-
' - When [entry] is omitted, Elit falls back to the configured entry for the resolved desktop mode.',
|
|
528
|
-
' - The build subcommand can be used without an entry to prebuild the native runtime.',
|
|
529
|
-
' - Desktop WAPK mode expects the packaged entry to start an HTTP app.',
|
|
530
|
-
' - Use --watcher for faster file change detection (less CPU usage).',
|
|
531
|
-
].join('\n'));
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
async function runDesktopWapkCommand(args: string[], config?: DesktopConfig): Promise<void> {
|
|
535
|
-
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
536
|
-
printDesktopHelp();
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
const options = parseDesktopWapkRunArgs(args, config?.wapk);
|
|
541
|
-
const preparedApp = await prepareWapkApp(options.file, {
|
|
542
|
-
runtime: options.runtime,
|
|
543
|
-
syncInterval: options.syncInterval,
|
|
544
|
-
useWatcher: options.useWatcher,
|
|
545
|
-
password: options.password,
|
|
546
|
-
});
|
|
547
|
-
const preparedEntry = await createDesktopWapkEntry(preparedApp);
|
|
548
|
-
const liveSync = createWapkLiveSync(preparedApp);
|
|
549
|
-
|
|
550
|
-
try {
|
|
551
|
-
const binary = ensureDesktopBinary({
|
|
552
|
-
runtime: preparedApp.runtime,
|
|
553
|
-
release: options.release,
|
|
554
|
-
entryPath: preparedApp.entryPath,
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
const exitCode = await spawnDesktopProcess(binary, ['--runtime', preparedApp.runtime, preparedEntry.entryPath]);
|
|
558
|
-
if (exitCode !== 0) {
|
|
559
|
-
process.exit(exitCode);
|
|
560
|
-
}
|
|
561
|
-
} finally {
|
|
562
|
-
await liveSync.stop();
|
|
563
|
-
rmSync(preparedApp.workDir, { recursive: true, force: true });
|
|
564
|
-
cleanupPreparedEntry(preparedEntry);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
function parseDesktopWapkRunArgs(args: string[], config?: DesktopConfig['wapk']): DesktopWapkRunOptions {
|
|
569
|
-
const normalizedArgs = args[0] === 'run' ? args.slice(1) : args;
|
|
570
|
-
const options: DesktopWapkRunOptions = {
|
|
571
|
-
runtime: config?.runtime,
|
|
572
|
-
release: config?.release ?? false,
|
|
573
|
-
file: '',
|
|
574
|
-
syncInterval: config?.syncInterval,
|
|
575
|
-
useWatcher: config?.useWatcher,
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
for (let i = 0; i < normalizedArgs.length; i++) {
|
|
579
|
-
const arg = normalizedArgs[i];
|
|
580
|
-
|
|
581
|
-
switch (arg) {
|
|
582
|
-
case '--runtime':
|
|
583
|
-
case '-r': {
|
|
584
|
-
const runtime = normalizedArgs[++i] as WapkRuntimeName | undefined;
|
|
585
|
-
if (!runtime || !WAPK_RUNTIMES.includes(runtime)) {
|
|
586
|
-
throw new Error(`Unknown desktop WAPK runtime: ${runtime}`);
|
|
587
|
-
}
|
|
588
|
-
options.runtime = runtime;
|
|
589
|
-
break;
|
|
590
|
-
}
|
|
591
|
-
case '--release':
|
|
592
|
-
options.release = true;
|
|
593
|
-
break;
|
|
594
|
-
case '--sync-interval': {
|
|
595
|
-
const value = parseInt(normalizedArgs[++i] ?? '', 10);
|
|
596
|
-
if (Number.isNaN(value) || value < 50) {
|
|
597
|
-
throw new Error('--sync-interval must be a number >= 50 (milliseconds)');
|
|
598
|
-
}
|
|
599
|
-
options.syncInterval = value;
|
|
600
|
-
break;
|
|
601
|
-
}
|
|
602
|
-
case '--use-watcher':
|
|
603
|
-
case '--watcher': {
|
|
604
|
-
options.useWatcher = true;
|
|
605
|
-
break;
|
|
606
|
-
}
|
|
607
|
-
case '--password':
|
|
608
|
-
options.password = normalizedArgs[++i];
|
|
609
|
-
if (!options.password) {
|
|
610
|
-
throw new Error('--password requires a value.');
|
|
611
|
-
}
|
|
612
|
-
break;
|
|
613
|
-
default:
|
|
614
|
-
if (arg.startsWith('-')) {
|
|
615
|
-
throw new Error(`Unknown desktop WAPK option: ${arg}`);
|
|
616
|
-
}
|
|
617
|
-
if (options.file) {
|
|
618
|
-
throw new Error('Desktop WAPK mode accepts exactly one package file.');
|
|
619
|
-
}
|
|
620
|
-
options.file = arg;
|
|
621
|
-
break;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
if (!options.file) {
|
|
626
|
-
throw new Error('Usage: elit desktop wapk <file.wapk>');
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
return options;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
async function createDesktopWapkEntry(preparedApp: PreparedWapkApp): Promise<PreparedEntry> {
|
|
633
|
-
const port = await resolveDesktopWapkPort(preparedApp.header.port);
|
|
634
|
-
const appName = sanitizeDesktopWapkName(preparedApp.header.name);
|
|
635
|
-
const entryPath = join(preparedApp.workDir, `.elit-desktop-wapk-${appName}-${randomUUID()}.mjs`);
|
|
636
|
-
const desktopOptions = buildDesktopWapkWindowOptions(preparedApp);
|
|
637
|
-
const runtimeExecutable = resolveWapkRuntimeExecutable(preparedApp.runtime);
|
|
638
|
-
const runtimeArgs = getWapkRuntimeArgs(preparedApp.runtime, preparedApp.entryPath);
|
|
639
|
-
const env = {
|
|
640
|
-
...process.env,
|
|
641
|
-
...preparedApp.header.env,
|
|
642
|
-
PORT: String(port),
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
writeFileSync(
|
|
646
|
-
entryPath,
|
|
647
|
-
[
|
|
648
|
-
`import { spawn } from 'node:child_process';`,
|
|
649
|
-
`import http from 'node:http';`,
|
|
650
|
-
'',
|
|
651
|
-
`const runtimeExecutable = ${JSON.stringify(runtimeExecutable)};`,
|
|
652
|
-
`const runtimeArgs = ${JSON.stringify(runtimeArgs)};`,
|
|
653
|
-
`const workDir = ${JSON.stringify(preparedApp.workDir)};`,
|
|
654
|
-
`const runtimeEnv = ${JSON.stringify(env)};`,
|
|
655
|
-
`const windowOptions = ${JSON.stringify(desktopOptions)};`,
|
|
656
|
-
`const appUrl = ${JSON.stringify(`http://127.0.0.1:${port}`)};`,
|
|
657
|
-
'',
|
|
658
|
-
'function waitForServer(url, timeoutMs = 15000) {',
|
|
659
|
-
' return new Promise((resolvePromise, rejectPromise) => {',
|
|
660
|
-
' const startTime = Date.now();',
|
|
661
|
-
' const poll = () => {',
|
|
662
|
-
' const request = http.get(url, (response) => {',
|
|
663
|
-
' response.resume();',
|
|
664
|
-
' resolvePromise();',
|
|
665
|
-
' });',
|
|
666
|
-
' request.on(\'error\', () => {',
|
|
667
|
-
' if (Date.now() - startTime > timeoutMs) {',
|
|
668
|
-
' rejectPromise(new Error(`Server did not start in ${timeoutMs}ms.`));',
|
|
669
|
-
' } else {',
|
|
670
|
-
' setTimeout(poll, 200);',
|
|
671
|
-
' }',
|
|
672
|
-
' });',
|
|
673
|
-
' request.setTimeout(1000, () => {',
|
|
674
|
-
' request.destroy();',
|
|
675
|
-
' });',
|
|
676
|
-
' };',
|
|
677
|
-
' poll();',
|
|
678
|
-
' });',
|
|
679
|
-
'}',
|
|
680
|
-
'',
|
|
681
|
-
'const child = spawn(runtimeExecutable, runtimeArgs, {',
|
|
682
|
-
' cwd: workDir,',
|
|
683
|
-
' env: runtimeEnv,',
|
|
684
|
-
' stdio: \"inherit\",',
|
|
685
|
-
' windowsHide: true,',
|
|
686
|
-
'});',
|
|
687
|
-
'',
|
|
688
|
-
'const stopChild = () => {',
|
|
689
|
-
' try {',
|
|
690
|
-
' if (!child.killed) child.kill();',
|
|
691
|
-
' } catch {}',
|
|
692
|
-
'};',
|
|
693
|
-
'',
|
|
694
|
-
'process.on(\'exit\', stopChild);',
|
|
695
|
-
'process.on(\'SIGINT\', () => { stopChild(); process.exit(130); });',
|
|
696
|
-
'process.on(\'SIGTERM\', () => { stopChild(); process.exit(143); });',
|
|
697
|
-
'',
|
|
698
|
-
'child.once(\'error\', (error) => {',
|
|
699
|
-
' console.error(error);',
|
|
700
|
-
' process.exit(1);',
|
|
701
|
-
'});',
|
|
702
|
-
'',
|
|
703
|
-
'child.once(\'exit\', (code) => {',
|
|
704
|
-
' if (code && code !== 0) {',
|
|
705
|
-
' process.exit(code);',
|
|
706
|
-
' return;',
|
|
707
|
-
' }',
|
|
708
|
-
' if (typeof globalThis.windowQuit === \"function\") {',
|
|
709
|
-
' globalThis.windowQuit();',
|
|
710
|
-
' }',
|
|
711
|
-
'});',
|
|
712
|
-
'',
|
|
713
|
-
'(async () => {',
|
|
714
|
-
' await waitForServer(appUrl);',
|
|
715
|
-
' if (typeof globalThis.createWindow !== \"function\") {',
|
|
716
|
-
' throw new Error(\'Desktop runtime did not expose createWindow().\');',
|
|
717
|
-
' }',
|
|
718
|
-
' globalThis.createWindow({ ...windowOptions, url: appUrl });',
|
|
719
|
-
'})().catch((error) => {',
|
|
720
|
-
' console.error(error);',
|
|
721
|
-
' stopChild();',
|
|
722
|
-
' process.exit(1);',
|
|
723
|
-
'});',
|
|
724
|
-
'',
|
|
725
|
-
].join('\n'),
|
|
726
|
-
'utf8',
|
|
727
|
-
);
|
|
728
|
-
|
|
729
|
-
return {
|
|
730
|
-
appName,
|
|
731
|
-
entryPath,
|
|
732
|
-
cleanupPath: entryPath,
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function sanitizeDesktopWapkName(name: string): string {
|
|
737
|
-
return name.replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function buildDesktopWapkWindowOptions(preparedApp: PreparedWapkApp): Record<string, unknown> {
|
|
741
|
-
const desktopOptions = preparedApp.header.desktop ? { ...preparedApp.header.desktop } : {};
|
|
742
|
-
const icon = typeof desktopOptions.icon === 'string' ? desktopOptions.icon : undefined;
|
|
743
|
-
|
|
744
|
-
if (icon && !/^(?:[a-z]+:)?[/\\]/i.test(icon)) {
|
|
745
|
-
desktopOptions.icon = join(preparedApp.workDir, icon);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
if (desktopOptions.title === undefined) {
|
|
749
|
-
desktopOptions.title = preparedApp.header.name;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
if (desktopOptions.width === undefined) {
|
|
753
|
-
desktopOptions.width = 1280;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
if (desktopOptions.height === undefined) {
|
|
757
|
-
desktopOptions.height = 800;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
if (desktopOptions.center === undefined) {
|
|
761
|
-
desktopOptions.center = true;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
delete desktopOptions.url;
|
|
765
|
-
delete desktopOptions.proxy_port;
|
|
766
|
-
delete desktopOptions.proxy_pipe;
|
|
767
|
-
delete desktopOptions.proxy_secret;
|
|
768
|
-
|
|
769
|
-
return desktopOptions;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
async function resolveDesktopWapkPort(preferredPort?: number): Promise<number> {
|
|
773
|
-
if (preferredPort) {
|
|
774
|
-
return preferredPort;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
const { createServer } = await import('node:net');
|
|
778
|
-
return await new Promise<number>((resolvePromise, rejectPromise) => {
|
|
779
|
-
const server = createServer();
|
|
780
|
-
server.once('error', rejectPromise);
|
|
781
|
-
server.listen(0, '127.0.0.1', () => {
|
|
782
|
-
const address = server.address();
|
|
783
|
-
server.close((closeError) => {
|
|
784
|
-
if (closeError) {
|
|
785
|
-
rejectPromise(closeError);
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
if (!address || typeof address === 'string') {
|
|
790
|
-
rejectPromise(new Error('Failed to allocate a desktop WAPK port.'));
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
resolvePromise(address.port);
|
|
795
|
-
});
|
|
796
|
-
});
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
async function runDesktopRuntime(options: DesktopRunOptions): Promise<void> {
|
|
801
|
-
if (options.mode === 'native') {
|
|
802
|
-
await runDesktopNativeRuntime(options);
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
const preparedEntry = await prepareEntry(options.entry!, options.runtime, options.compiler, 'run');
|
|
807
|
-
|
|
808
|
-
try {
|
|
809
|
-
const binary = ensureDesktopBinary({
|
|
810
|
-
runtime: options.runtime,
|
|
811
|
-
release: options.release,
|
|
812
|
-
entryPath: options.entry,
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
const exitCode = await spawnDesktopProcess(binary, ['--runtime', options.runtime, preparedEntry.entryPath]);
|
|
816
|
-
if (exitCode !== 0) {
|
|
817
|
-
process.exit(exitCode);
|
|
818
|
-
}
|
|
819
|
-
} finally {
|
|
820
|
-
cleanupPreparedEntry(preparedEntry);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
async function runDesktopNativeRuntime(options: DesktopRunOptions): Promise<void> {
|
|
825
|
-
const preparedPayload = await prepareDesktopNativePayload(options);
|
|
826
|
-
|
|
827
|
-
try {
|
|
828
|
-
const binary = ensureDesktopNativeBinary({
|
|
829
|
-
release: options.release,
|
|
830
|
-
entryPath: options.entry,
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
const exitCode = await spawnDesktopProcess(binary, [preparedPayload.payloadPath]);
|
|
834
|
-
if (exitCode !== 0) {
|
|
835
|
-
process.exit(exitCode);
|
|
836
|
-
}
|
|
837
|
-
} finally {
|
|
838
|
-
cleanupPreparedDesktopNativePayload(preparedPayload);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
async function buildDesktopBundle(options: DesktopBuildOptions): Promise<void> {
|
|
843
|
-
if (options.mode === 'native') {
|
|
844
|
-
await buildDesktopNativeBundle(options);
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
const triple = options.platform ? PLATFORMS[options.platform] : undefined;
|
|
849
|
-
buildDesktopRuntime({
|
|
850
|
-
runtime: options.runtime,
|
|
851
|
-
release: options.release,
|
|
852
|
-
triple,
|
|
853
|
-
entryPath: options.entry,
|
|
854
|
-
});
|
|
855
|
-
const binary = findDesktopBinary(options.runtime, options.release, triple);
|
|
856
|
-
|
|
857
|
-
if (!binary) {
|
|
858
|
-
throw new Error('Desktop runtime binary was not found after cargo build completed.');
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
if (!options.entry) {
|
|
862
|
-
console.log(`Desktop runtime ready: ${binary}`);
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
const preparedEntry = await prepareEntry(options.entry, options.runtime, options.compiler, 'build');
|
|
867
|
-
|
|
868
|
-
try {
|
|
869
|
-
const outDir = resolve(options.outDir);
|
|
870
|
-
const binIsWindows = isWindowsTarget(triple);
|
|
871
|
-
const outFile = join(outDir, `${preparedEntry.appName}${binIsWindows ? '.exe' : ''}`);
|
|
872
|
-
const runtimeBytes = readFileSync(binary);
|
|
873
|
-
const scriptBytes = readFileSync(preparedEntry.entryPath);
|
|
874
|
-
const sizeBuffer = Buffer.allocUnsafe(8);
|
|
875
|
-
sizeBuffer.writeBigUInt64LE(BigInt(scriptBytes.length));
|
|
876
|
-
const runtimeCode = Buffer.from([EMBED_RUNTIME_CODE[options.runtime]]);
|
|
877
|
-
|
|
878
|
-
mkdirSync(outDir, { recursive: true });
|
|
879
|
-
writeFileSync(outFile, Buffer.concat([runtimeBytes, scriptBytes, sizeBuffer, runtimeCode, EMBED_MAGIC_V2]));
|
|
880
|
-
|
|
881
|
-
if (!binIsWindows) {
|
|
882
|
-
chmodSync(outFile, 0o755);
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
console.log(`Desktop app built: ${outFile}`);
|
|
886
|
-
} finally {
|
|
887
|
-
cleanupPreparedEntry(preparedEntry);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
async function buildDesktopNativeBundle(options: DesktopBuildOptions): Promise<void> {
|
|
892
|
-
const triple = options.platform ? PLATFORMS[options.platform] : undefined;
|
|
893
|
-
buildDesktopNativeRuntime({
|
|
894
|
-
release: options.release,
|
|
895
|
-
triple,
|
|
896
|
-
entryPath: options.entry,
|
|
897
|
-
});
|
|
898
|
-
const binary = findDesktopNativeBinary(options.release, triple);
|
|
899
|
-
|
|
900
|
-
if (!binary) {
|
|
901
|
-
throw new Error('Desktop native runtime binary was not found after cargo build completed.');
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
if (!options.entry) {
|
|
905
|
-
console.log(`Desktop native runtime ready: ${binary}`);
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
const preparedPayload = await prepareDesktopNativePayload(options);
|
|
910
|
-
|
|
911
|
-
try {
|
|
912
|
-
const outDir = resolve(options.outDir);
|
|
913
|
-
const binIsWindows = isWindowsTarget(triple);
|
|
914
|
-
const outFile = join(outDir, `${preparedPayload.appName}${binIsWindows ? '.exe' : ''}`);
|
|
915
|
-
const runtimeBytes = readFileSync(binary);
|
|
916
|
-
const payloadBytes = readFileSync(preparedPayload.payloadPath);
|
|
917
|
-
const sizeBuffer = Buffer.allocUnsafe(8);
|
|
918
|
-
sizeBuffer.writeBigUInt64LE(BigInt(payloadBytes.length));
|
|
919
|
-
|
|
920
|
-
mkdirSync(outDir, { recursive: true });
|
|
921
|
-
writeFileSync(outFile, Buffer.concat([runtimeBytes, payloadBytes, sizeBuffer, EMBED_NATIVE_MAGIC_V1]));
|
|
922
|
-
|
|
923
|
-
if (!binIsWindows) {
|
|
924
|
-
chmodSync(outFile, 0o755);
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
console.log(`Desktop native app built: ${outFile}`);
|
|
928
|
-
} finally {
|
|
929
|
-
cleanupPreparedDesktopNativePayload(preparedPayload);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function ensureDesktopBinary(options: EnsureBinaryOptions): string {
|
|
934
|
-
let binary = findDesktopBinary(options.runtime, options.release, options.triple);
|
|
935
|
-
|
|
936
|
-
if (!binary || isDesktopRustBuildStale(binary)) {
|
|
937
|
-
buildDesktopRuntime(options);
|
|
938
|
-
binary = findDesktopBinary(options.runtime, options.release, options.triple);
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
if (!binary) {
|
|
942
|
-
throw new Error('Desktop runtime binary was not found after cargo build completed.');
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
return binary;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
function ensureDesktopNativeBinary(options: EnsureNativeBinaryOptions): string {
|
|
949
|
-
let binary = findDesktopNativeBinary(options.release, options.triple);
|
|
950
|
-
|
|
951
|
-
if (!binary || isDesktopRustBuildStale(binary)) {
|
|
952
|
-
buildDesktopNativeRuntime(options);
|
|
953
|
-
binary = findDesktopNativeBinary(options.release, options.triple);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
if (!binary) {
|
|
957
|
-
throw new Error('Desktop native runtime binary was not found after cargo build completed.');
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
return binary;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
function buildDesktopRuntime(options: EnsureBinaryOptions): void {
|
|
964
|
-
const args = [
|
|
965
|
-
'build',
|
|
966
|
-
'--manifest-path',
|
|
967
|
-
resolve(PACKAGE_ROOT, 'Cargo.toml'),
|
|
968
|
-
'--bin',
|
|
969
|
-
'elit-desktop',
|
|
970
|
-
'--no-default-features',
|
|
971
|
-
'--features',
|
|
972
|
-
BUILD_FEATURES[options.runtime].join(','),
|
|
973
|
-
];
|
|
974
|
-
|
|
975
|
-
if (options.release) {
|
|
976
|
-
args.push('--release');
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
if (options.triple) {
|
|
980
|
-
args.push('--target', options.triple);
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
const env: NodeJS.ProcessEnv = {
|
|
984
|
-
...process.env,
|
|
985
|
-
CARGO_TARGET_DIR: desktopCargoTargetDir(options.runtime),
|
|
986
|
-
};
|
|
987
|
-
|
|
988
|
-
const iconPath = resolveDesktopIcon(options.entryPath);
|
|
989
|
-
if (iconPath) {
|
|
990
|
-
env.ELIT_DESKTOP_EXE_ICON = iconPath;
|
|
991
|
-
env.WAPK_EXE_ICON = iconPath;
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
const result = spawnSync('cargo', args, {
|
|
995
|
-
cwd: PACKAGE_ROOT,
|
|
996
|
-
env,
|
|
997
|
-
stdio: 'inherit',
|
|
998
|
-
windowsHide: true,
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
if (result.error) {
|
|
1002
|
-
if ((result.error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
1003
|
-
throw new Error('Cargo is required for desktop mode but was not found in PATH.');
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
throw result.error;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
if (result.status !== 0) {
|
|
1010
|
-
process.exit(result.status ?? 1);
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
function buildDesktopNativeRuntime(options: EnsureNativeBinaryOptions): void {
|
|
1015
|
-
const args = [
|
|
1016
|
-
'build',
|
|
1017
|
-
'--manifest-path',
|
|
1018
|
-
resolve(PACKAGE_ROOT, 'Cargo.toml'),
|
|
1019
|
-
'--bin',
|
|
1020
|
-
'elit-desktop-native',
|
|
1021
|
-
];
|
|
1022
|
-
|
|
1023
|
-
if (options.release) {
|
|
1024
|
-
args.push('--release');
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
if (options.triple) {
|
|
1028
|
-
args.push('--target', options.triple);
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
const env: NodeJS.ProcessEnv = {
|
|
1032
|
-
...process.env,
|
|
1033
|
-
CARGO_TARGET_DIR: desktopNativeCargoTargetDir(),
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
const iconPath = resolveDesktopIcon(options.entryPath);
|
|
1037
|
-
if (iconPath) {
|
|
1038
|
-
env.ELIT_DESKTOP_EXE_ICON = iconPath;
|
|
1039
|
-
env.WAPK_EXE_ICON = iconPath;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
const result = spawnSync('cargo', args, {
|
|
1043
|
-
cwd: PACKAGE_ROOT,
|
|
1044
|
-
env,
|
|
1045
|
-
stdio: 'inherit',
|
|
1046
|
-
windowsHide: true,
|
|
1047
|
-
});
|
|
1048
|
-
|
|
1049
|
-
if (result.error) {
|
|
1050
|
-
if ((result.error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
1051
|
-
throw new Error('Cargo is required for desktop native mode but was not found in PATH.');
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
throw result.error;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
if (result.status !== 0) {
|
|
1058
|
-
process.exit(result.status ?? 1);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
function findDesktopBinary(runtime: DesktopRuntimeName, release: boolean, triple?: string): string | null {
|
|
1063
|
-
const targetDir = desktopCargoTargetDir(runtime);
|
|
1064
|
-
const profile = release ? 'release' : 'debug';
|
|
1065
|
-
const binaryName = isWindowsTarget(triple) ? 'elit-desktop.exe' : 'elit-desktop';
|
|
1066
|
-
const candidates = triple
|
|
1067
|
-
? [join(targetDir, triple, profile, binaryName)]
|
|
1068
|
-
: [join(targetDir, profile, binaryName)];
|
|
1069
|
-
|
|
1070
|
-
for (const candidate of candidates) {
|
|
1071
|
-
if (existsSync(candidate)) {
|
|
1072
|
-
return candidate;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
return null;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
function findDesktopNativeBinary(release: boolean, triple?: string): string | null {
|
|
1080
|
-
const targetDir = desktopNativeCargoTargetDir();
|
|
1081
|
-
const profile = release ? 'release' : 'debug';
|
|
1082
|
-
const binaryName = isWindowsTarget(triple) ? 'elit-desktop-native.exe' : 'elit-desktop-native';
|
|
1083
|
-
const candidates = triple
|
|
1084
|
-
? [join(targetDir, triple, profile, binaryName)]
|
|
1085
|
-
: [join(targetDir, profile, binaryName)];
|
|
1086
|
-
|
|
1087
|
-
for (const candidate of candidates) {
|
|
1088
|
-
if (existsSync(candidate)) {
|
|
1089
|
-
return candidate;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
return null;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
function desktopCargoTargetDir(runtime: DesktopRuntimeName): string {
|
|
1097
|
-
return resolve(PACKAGE_ROOT, 'target', 'desktop', runtime);
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
function desktopNativeCargoTargetDir(): string {
|
|
1101
|
-
return resolve(PACKAGE_ROOT, 'target', 'desktop', 'native');
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
function isDesktopRustBuildStale(binaryPath: string): boolean {
|
|
1105
|
-
if (!existsSync(binaryPath)) {
|
|
1106
|
-
return true;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
return statSync(binaryPath).mtimeMs < getLatestDesktopRustInputMtime();
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
let latestDesktopRustInputMtime: number | undefined;
|
|
1113
|
-
|
|
1114
|
-
function getLatestDesktopRustInputMtime(): number {
|
|
1115
|
-
if (latestDesktopRustInputMtime !== undefined) {
|
|
1116
|
-
return latestDesktopRustInputMtime;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
latestDesktopRustInputMtime = Math.max(
|
|
1120
|
-
getPathModifiedTime(resolve(PACKAGE_ROOT, 'Cargo.toml')),
|
|
1121
|
-
getPathModifiedTime(resolve(PACKAGE_ROOT, 'src', 'desktop')),
|
|
1122
|
-
);
|
|
1123
|
-
return latestDesktopRustInputMtime;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
function getPathModifiedTime(path: string): number {
|
|
1127
|
-
if (!existsSync(path)) {
|
|
1128
|
-
return 0;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
const stats = statSync(path);
|
|
1132
|
-
if (!stats.isDirectory()) {
|
|
1133
|
-
return stats.mtimeMs;
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
let latest = stats.mtimeMs;
|
|
1137
|
-
for (const entry of readdirSync(path, { withFileTypes: true })) {
|
|
1138
|
-
latest = Math.max(latest, getPathModifiedTime(join(path, entry.name)));
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
return latest;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
function resolveDesktopIcon(entryPath?: string): string | undefined {
|
|
1145
|
-
if (!entryPath) {
|
|
1146
|
-
return undefined;
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
const entryDir = dirname(resolve(entryPath));
|
|
1150
|
-
const projectDir = dirname(entryDir);
|
|
1151
|
-
const searchDirs = [
|
|
1152
|
-
entryDir,
|
|
1153
|
-
join(entryDir, 'public'),
|
|
1154
|
-
projectDir,
|
|
1155
|
-
join(projectDir, 'public'),
|
|
1156
|
-
];
|
|
1157
|
-
const candidates = [
|
|
1158
|
-
'icon.ico',
|
|
1159
|
-
'icon.png',
|
|
1160
|
-
'icon.svg',
|
|
1161
|
-
'favicon.ico',
|
|
1162
|
-
'favicon.png',
|
|
1163
|
-
'favicon.svg',
|
|
1164
|
-
];
|
|
1165
|
-
|
|
1166
|
-
for (const searchDir of searchDirs) {
|
|
1167
|
-
for (const candidate of candidates) {
|
|
1168
|
-
const iconPath = join(searchDir, candidate);
|
|
1169
|
-
if (existsSync(iconPath)) {
|
|
1170
|
-
return iconPath;
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
return undefined;
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
function resolveDesktopNativeWindowIcon(entryPath: string, desktopRenderOptions?: DesktopRenderOptions): string | undefined {
|
|
1179
|
-
const configuredIcon = desktopRenderOptions?.icon;
|
|
1180
|
-
if (configuredIcon) {
|
|
1181
|
-
const candidate = /^(?:[a-z]+:)?[/\\]/i.test(configuredIcon)
|
|
1182
|
-
? configuredIcon
|
|
1183
|
-
: resolve(dirname(resolve(entryPath)), configuredIcon);
|
|
1184
|
-
|
|
1185
|
-
if (existsSync(candidate)) {
|
|
1186
|
-
return candidate;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
return resolveDesktopIcon(entryPath);
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
function resolveDesktopNativeWindowOptions(
|
|
1194
|
-
entryPath: string,
|
|
1195
|
-
appName: string,
|
|
1196
|
-
desktopRenderOptions?: DesktopRenderOptions,
|
|
1197
|
-
): DesktopNativeWindowOptions {
|
|
1198
|
-
return {
|
|
1199
|
-
title: desktopRenderOptions?.title ?? `${resolveDesktopEntryDisplayName(entryPath, appName)} Desktop`,
|
|
1200
|
-
width: desktopRenderOptions?.width ?? 1080,
|
|
1201
|
-
height: desktopRenderOptions?.height ?? 720,
|
|
1202
|
-
center: desktopRenderOptions?.center ?? true,
|
|
1203
|
-
autoClose: desktopRenderOptions?.autoClose ?? false,
|
|
1204
|
-
...(resolveDesktopNativeWindowIcon(entryPath, desktopRenderOptions)
|
|
1205
|
-
? { icon: resolveDesktopNativeWindowIcon(entryPath, desktopRenderOptions) }
|
|
1206
|
-
: {}),
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
function resolveDesktopNativeInteractionOutput(
|
|
1211
|
-
entryPath: string,
|
|
1212
|
-
desktopRenderOptions?: DesktopRenderOptions,
|
|
1213
|
-
): DesktopNativeInteractionOutput | undefined {
|
|
1214
|
-
const output = desktopRenderOptions?.interactionOutput;
|
|
1215
|
-
if (!output || (!output.file && output.stdout !== true)) {
|
|
1216
|
-
return undefined;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
const resolvedFile = output.file
|
|
1220
|
-
? (/^(?:[a-z]+:)?[/\\]/i.test(output.file)
|
|
1221
|
-
? output.file
|
|
1222
|
-
: resolve(dirname(resolve(entryPath)), output.file))
|
|
1223
|
-
: undefined;
|
|
1224
|
-
|
|
1225
|
-
return {
|
|
1226
|
-
...(resolvedFile ? { file: resolvedFile } : {}),
|
|
1227
|
-
...(output.stdout ? { stdout: true } : {}),
|
|
1228
|
-
...(output.emitReady ? { emitReady: true } : {}),
|
|
1229
|
-
};
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
async function prepareDesktopNativePayload(options: Pick<DesktopRunOptions, 'entry' | 'exportName'>): Promise<PreparedDesktopNativePayload> {
|
|
1233
|
-
const entryPath = resolve(options.entry!);
|
|
1234
|
-
|
|
1235
|
-
if (!existsSync(entryPath)) {
|
|
1236
|
-
throw new Error(`Desktop native entry not found: ${entryPath}`);
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
const appName = basename(entryPath, extname(entryPath));
|
|
1240
|
-
const loadedEntry = await loadNativeEntryResult(entryPath, options.exportName, 'desktop');
|
|
1241
|
-
const payloadPath = join(dirname(entryPath), `.elit-desktop-native-${appName}-${randomUUID()}.json`);
|
|
1242
|
-
const interactionOutput = resolveDesktopNativeInteractionOutput(entryPath, loadedEntry.desktopRenderOptions);
|
|
1243
|
-
const payload: DesktopNativePayload = {
|
|
1244
|
-
window: resolveDesktopNativeWindowOptions(entryPath, appName, loadedEntry.desktopRenderOptions),
|
|
1245
|
-
resourceBaseDir: dirname(entryPath),
|
|
1246
|
-
...(interactionOutput ? { interactionOutput } : {}),
|
|
1247
|
-
tree: renderMaterializedNativeTree(loadedEntry.entry, { platform: 'generic' }),
|
|
1248
|
-
};
|
|
1249
|
-
|
|
1250
|
-
writeFileSync(payloadPath, JSON.stringify(payload, null, 2), 'utf8');
|
|
1251
|
-
|
|
1252
|
-
return {
|
|
1253
|
-
appName,
|
|
1254
|
-
payloadPath,
|
|
1255
|
-
cleanupPath: payloadPath,
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
async function prepareEntry(
|
|
1260
|
-
entry: string,
|
|
1261
|
-
runtime: DesktopRuntimeName,
|
|
1262
|
-
compiler: DesktopCompilerName,
|
|
1263
|
-
mode: 'run' | 'build',
|
|
1264
|
-
): Promise<PreparedEntry> {
|
|
1265
|
-
const entryPath = resolve(entry);
|
|
1266
|
-
|
|
1267
|
-
if (!existsSync(entryPath)) {
|
|
1268
|
-
throw new Error(`Desktop entry not found: ${entryPath}`);
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
const appName = basename(entryPath, extname(entryPath));
|
|
1272
|
-
const shouldCompile = shouldCompileEntry(entryPath, runtime, compiler, mode);
|
|
1273
|
-
if (!shouldCompile) {
|
|
1274
|
-
return { appName, entryPath };
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
const bootstrapEntry = createDesktopBootstrapEntry(entryPath, appName);
|
|
1278
|
-
|
|
1279
|
-
const output = compileTarget(runtime);
|
|
1280
|
-
const compiledPath = join(dirname(entryPath), `.elit-desktop-${appName}-${randomUUID()}${output.extension}`);
|
|
1281
|
-
|
|
1282
|
-
try {
|
|
1283
|
-
await compileDesktopEntry({
|
|
1284
|
-
appName,
|
|
1285
|
-
compiledPath,
|
|
1286
|
-
compiler,
|
|
1287
|
-
entryPath: bootstrapEntry.bootstrapPath,
|
|
1288
|
-
mode,
|
|
1289
|
-
output,
|
|
1290
|
-
runtime,
|
|
1291
|
-
});
|
|
1292
|
-
} catch (error) {
|
|
1293
|
-
cleanupPreparedEntry({ appName, entryPath: compiledPath, cleanupPath: compiledPath });
|
|
1294
|
-
throw error;
|
|
1295
|
-
} finally {
|
|
1296
|
-
for (const cleanupPath of bootstrapEntry.cleanupPaths) {
|
|
1297
|
-
if (existsSync(cleanupPath)) {
|
|
1298
|
-
rmSync(cleanupPath, { force: true });
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
return {
|
|
1304
|
-
appName,
|
|
1305
|
-
entryPath: compiledPath,
|
|
1306
|
-
cleanupPath: compiledPath,
|
|
1307
|
-
};
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
function shouldCompileEntry(
|
|
1311
|
-
entryPath: string,
|
|
1312
|
-
runtime: DesktopRuntimeName,
|
|
1313
|
-
compiler: DesktopCompilerName,
|
|
1314
|
-
mode: 'run' | 'build',
|
|
1315
|
-
): boolean {
|
|
1316
|
-
if (compiler === 'none') {
|
|
1317
|
-
return false;
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
if (compiler === 'esbuild' || compiler === 'tsx' || compiler === 'tsup') {
|
|
1321
|
-
return true;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
return mode === 'build' || runtime === 'quickjs' || TS_LIKE_EXTENSIONS.has(extname(entryPath).toLowerCase());
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
async function compileDesktopEntry(options: {
|
|
1328
|
-
appName: string;
|
|
1329
|
-
compiledPath: string;
|
|
1330
|
-
compiler: DesktopCompilerName;
|
|
1331
|
-
entryPath: string;
|
|
1332
|
-
mode: 'run' | 'build';
|
|
1333
|
-
output: { extension: string; format: DesktopFormat; platform: 'neutral' | 'node' };
|
|
1334
|
-
runtime: DesktopRuntimeName;
|
|
1335
|
-
}): Promise<void> {
|
|
1336
|
-
switch (options.compiler) {
|
|
1337
|
-
case 'tsup':
|
|
1338
|
-
await compileDesktopEntryWithTsup(options);
|
|
1339
|
-
return;
|
|
1340
|
-
case 'tsx':
|
|
1341
|
-
await compileDesktopEntryWithTsx(options);
|
|
1342
|
-
return;
|
|
1343
|
-
case 'auto':
|
|
1344
|
-
case 'esbuild':
|
|
1345
|
-
default:
|
|
1346
|
-
await compileDesktopEntryWithEsbuild(options);
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
async function compileDesktopEntryWithEsbuild(options: {
|
|
1352
|
-
compiledPath: string;
|
|
1353
|
-
entryPath: string;
|
|
1354
|
-
output: { extension: string; format: DesktopFormat; platform: 'neutral' | 'node' };
|
|
1355
|
-
runtime: DesktopRuntimeName;
|
|
1356
|
-
}): Promise<void> {
|
|
1357
|
-
const workspacePackagePlugin = createWorkspacePackagePlugin(dirname(options.entryPath));
|
|
1358
|
-
|
|
1359
|
-
await esbuild({
|
|
1360
|
-
absWorkingDir: dirname(options.entryPath),
|
|
1361
|
-
bundle: true,
|
|
1362
|
-
entryPoints: [options.entryPath],
|
|
1363
|
-
format: options.output.format,
|
|
1364
|
-
logLevel: 'silent',
|
|
1365
|
-
mainFields: options.output.platform === 'node' ? ['module', 'main'] : ['browser', 'module', 'main'],
|
|
1366
|
-
outfile: options.compiledPath,
|
|
1367
|
-
platform: options.output.platform,
|
|
1368
|
-
plugins: [workspacePackagePlugin],
|
|
1369
|
-
sourcemap: false,
|
|
1370
|
-
target: options.runtime === 'quickjs' ? ['es2020'] : ['es2022'],
|
|
1371
|
-
});
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
async function compileDesktopEntryWithTsup(options: {
|
|
1375
|
-
compiledPath: string;
|
|
1376
|
-
entryPath: string;
|
|
1377
|
-
output: { extension: string; format: DesktopFormat; platform: 'neutral' | 'node' };
|
|
1378
|
-
runtime: DesktopRuntimeName;
|
|
1379
|
-
}): Promise<void> {
|
|
1380
|
-
const tsup = await loadOptionalDesktopCompiler<TsupModule>('tsup', options.entryPath, 'tsup');
|
|
1381
|
-
const outputBaseName = basename(options.compiledPath, extname(options.compiledPath));
|
|
1382
|
-
const workspacePackagePlugin = createWorkspacePackagePlugin(dirname(options.entryPath));
|
|
1383
|
-
|
|
1384
|
-
await tsup.build({
|
|
1385
|
-
bundle: true,
|
|
1386
|
-
clean: false,
|
|
1387
|
-
config: false,
|
|
1388
|
-
dts: false,
|
|
1389
|
-
entry: { [outputBaseName]: options.entryPath },
|
|
1390
|
-
format: [options.output.format],
|
|
1391
|
-
noExternal: [/^elit(?:\/|$)/],
|
|
1392
|
-
outDir: dirname(options.compiledPath),
|
|
1393
|
-
outExtension: () => ({ js: options.output.extension }),
|
|
1394
|
-
platform: options.output.platform,
|
|
1395
|
-
silent: true,
|
|
1396
|
-
skipNodeModulesBundle: false,
|
|
1397
|
-
sourcemap: false,
|
|
1398
|
-
splitting: false,
|
|
1399
|
-
target: options.runtime === 'quickjs' ? 'es2020' : 'es2022',
|
|
1400
|
-
esbuildOptions(esbuildOptions) {
|
|
1401
|
-
esbuildOptions.logLevel = 'silent';
|
|
1402
|
-
esbuildOptions.mainFields = options.output.platform === 'node'
|
|
1403
|
-
? ['module', 'main']
|
|
1404
|
-
: ['browser', 'module', 'main'];
|
|
1405
|
-
esbuildOptions.plugins = [...(esbuildOptions.plugins ?? []), workspacePackagePlugin];
|
|
1406
|
-
},
|
|
1407
|
-
});
|
|
1408
|
-
|
|
1409
|
-
const actualOutputPath = findTsupOutputPath(options.compiledPath, options.output.extension);
|
|
1410
|
-
if (!actualOutputPath) {
|
|
1411
|
-
throw new Error(`Desktop compiler "tsup" did not produce the expected output: ${options.compiledPath}`);
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
if (actualOutputPath !== options.compiledPath) {
|
|
1415
|
-
renameSync(actualOutputPath, options.compiledPath);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
async function compileDesktopEntryWithTsx(options: {
|
|
1420
|
-
compiledPath: string;
|
|
1421
|
-
entryPath: string;
|
|
1422
|
-
mode: 'run' | 'build';
|
|
1423
|
-
runtime: DesktopRuntimeName;
|
|
1424
|
-
}): Promise<void> {
|
|
1425
|
-
if (options.runtime !== 'node') {
|
|
1426
|
-
throw new Error('Desktop compiler "tsx" is only supported with --runtime node.');
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
if (options.mode === 'build') {
|
|
1430
|
-
console.warn('[desktop] compiler "tsx" generates a Node loader stub that keeps reading the original source tree at runtime.');
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
const tsxApiPath = resolveOptionalDesktopCompilerPath('tsx/esm/api', options.entryPath, 'tsx');
|
|
1434
|
-
const entryUrl = pathToFileURL(options.entryPath).href;
|
|
1435
|
-
const bootstrap = [
|
|
1436
|
-
`'use strict';`,
|
|
1437
|
-
`const { register } = require(${JSON.stringify(tsxApiPath)});`,
|
|
1438
|
-
`register();`,
|
|
1439
|
-
`import(${JSON.stringify(entryUrl)}).catch((error) => {`,
|
|
1440
|
-
` console.error(error);`,
|
|
1441
|
-
` process.exit(1);`,
|
|
1442
|
-
`});`,
|
|
1443
|
-
'',
|
|
1444
|
-
].join('\n');
|
|
1445
|
-
|
|
1446
|
-
writeFileSync(options.compiledPath, bootstrap);
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
async function loadOptionalDesktopCompiler<T>(
|
|
1450
|
-
specifier: string,
|
|
1451
|
-
entryPath: string,
|
|
1452
|
-
compiler: Extract<DesktopCompilerName, 'tsx' | 'tsup'>,
|
|
1453
|
-
): Promise<T> {
|
|
1454
|
-
const resolvedPath = resolveOptionalDesktopCompilerPath(specifier, entryPath, compiler);
|
|
1455
|
-
return import(pathToFileURL(resolvedPath).href) as Promise<T>;
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
function resolveOptionalDesktopCompilerPath(
|
|
1459
|
-
specifier: string,
|
|
1460
|
-
entryPath: string,
|
|
1461
|
-
compiler: Extract<DesktopCompilerName, 'tsx' | 'tsup'>,
|
|
1462
|
-
): string {
|
|
1463
|
-
const searchRoots = Array.from(new Set([
|
|
1464
|
-
dirname(resolve(entryPath)),
|
|
1465
|
-
resolve(process.cwd()),
|
|
1466
|
-
PACKAGE_ROOT,
|
|
1467
|
-
]));
|
|
1468
|
-
|
|
1469
|
-
for (const searchRoot of searchRoots) {
|
|
1470
|
-
try {
|
|
1471
|
-
return createRequire(join(searchRoot, '__elit-desktop__.cjs')).resolve(specifier);
|
|
1472
|
-
} catch {
|
|
1473
|
-
continue;
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
throw new Error(
|
|
1478
|
-
`Desktop compiler "${compiler}" requires the ${compiler} package to be installed. Try: npm install -D ${compiler}`,
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
function findTsupOutputPath(expectedPath: string, expectedExtension: string): string | undefined {
|
|
1483
|
-
const basePath = expectedPath.slice(0, -expectedExtension.length);
|
|
1484
|
-
const candidates = [
|
|
1485
|
-
expectedPath,
|
|
1486
|
-
`${basePath}.js`,
|
|
1487
|
-
`${basePath}.cjs`,
|
|
1488
|
-
`${basePath}.mjs`,
|
|
1489
|
-
];
|
|
1490
|
-
|
|
1491
|
-
return candidates.find((candidate, index) => candidates.indexOf(candidate) === index && existsSync(candidate));
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
function compileTarget(runtime: DesktopRuntimeName): { extension: string; format: DesktopFormat; platform: 'neutral' | 'node' } {
|
|
1495
|
-
switch (runtime) {
|
|
1496
|
-
case 'quickjs':
|
|
1497
|
-
return { extension: '.js', format: 'iife', platform: 'neutral' };
|
|
1498
|
-
case 'deno':
|
|
1499
|
-
return { extension: '.mjs', format: 'esm', platform: 'neutral' };
|
|
1500
|
-
default:
|
|
1501
|
-
return { extension: '.cjs', format: 'cjs', platform: 'node' };
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
function cleanupPreparedEntry(entry: PreparedEntry): void {
|
|
1506
|
-
if (entry.cleanupPath && existsSync(entry.cleanupPath)) {
|
|
1507
|
-
rmSync(entry.cleanupPath, { force: true });
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
function cleanupPreparedDesktopNativePayload(payload: PreparedDesktopNativePayload): void {
|
|
1512
|
-
if (payload.cleanupPath && existsSync(payload.cleanupPath)) {
|
|
1513
|
-
rmSync(payload.cleanupPath, { force: true });
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
function isWindowsTarget(triple?: string): boolean {
|
|
1518
|
-
return triple ? triple.includes('windows') : process.platform === 'win32';
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
function spawnDesktopProcess(binary: string, args: string[]): Promise<number> {
|
|
1522
|
-
return new Promise((resolvePromise, rejectPromise) => {
|
|
1523
|
-
const child = spawn(binary, args, {
|
|
1524
|
-
stdio: 'inherit',
|
|
1525
|
-
windowsHide: true,
|
|
1526
|
-
});
|
|
1527
|
-
|
|
1528
|
-
child.once('error', rejectPromise);
|
|
1529
|
-
child.once('close', (code) => {
|
|
1530
|
-
resolvePromise(code ?? 1);
|
|
1531
|
-
});
|
|
1532
|
-
});
|
|
1533
|
-
}
|