@driftengine/package 3.61.0
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/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +408 -0
- package/bin/drift-package.mjs +82 -0
- package/dist/android.d.ts +18 -0
- package/dist/android.js +207 -0
- package/dist/bootstrap.d.ts +17 -0
- package/dist/bootstrap.js +326 -0
- package/dist/build.d.ts +11 -0
- package/dist/build.js +248 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +343 -0
- package/dist/flags.d.ts +57 -0
- package/dist/flags.js +122 -0
- package/dist/identity.d.ts +51 -0
- package/dist/identity.js +75 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +24 -0
- package/dist/init.d.ts +24 -0
- package/dist/init.js +45 -0
- package/dist/ios.d.ts +27 -0
- package/dist/ios.js +152 -0
- package/dist/macCrossSign.d.ts +34 -0
- package/dist/macCrossSign.js +298 -0
- package/dist/main/consoleForward.d.ts +43 -0
- package/dist/main/consoleForward.js +63 -0
- package/dist/main/csp.d.ts +4 -0
- package/dist/main/csp.js +55 -0
- package/dist/main/entry.d.ts +1 -0
- package/dist/main/entry.js +56 -0
- package/dist/main/ipc.d.ts +28 -0
- package/dist/main/ipc.js +284 -0
- package/dist/main/links.d.ts +1 -0
- package/dist/main/links.js +29 -0
- package/dist/main/menu.d.ts +17 -0
- package/dist/main/menu.js +49 -0
- package/dist/main/serve.d.ts +21 -0
- package/dist/main/serve.js +69 -0
- package/dist/main/shortcuts.d.ts +28 -0
- package/dist/main/shortcuts.js +26 -0
- package/dist/main/splash.d.ts +14 -0
- package/dist/main/splash.js +41 -0
- package/dist/main/splashTiming.d.ts +36 -0
- package/dist/main/splashTiming.js +24 -0
- package/dist/main/window.d.ts +19 -0
- package/dist/main/window.js +297 -0
- package/dist/manifest.d.ts +61 -0
- package/dist/manifest.js +91 -0
- package/dist/mobile/assetCheck.d.ts +1 -0
- package/dist/mobile/assetCheck.js +28 -0
- package/dist/mobile/bridgeShim.d.ts +83 -0
- package/dist/mobile/bridgeShim.js +208 -0
- package/dist/mobile/iosShim.d.ts +42 -0
- package/dist/mobile/iosShim.js +104 -0
- package/dist/mobile/report.d.ts +27 -0
- package/dist/mobile/report.js +122 -0
- package/dist/mobile/versionCode.d.ts +12 -0
- package/dist/mobile/versionCode.js +17 -0
- package/dist/outDir.d.ts +30 -0
- package/dist/outDir.js +32 -0
- package/dist/preload/bridge.d.ts +77 -0
- package/dist/preload/bridge.js +2 -0
- package/dist/preload/entry.d.ts +1 -0
- package/dist/preload/entry.js +77 -0
- package/dist/renderer/fileStore.d.ts +23 -0
- package/dist/renderer/fileStore.js +33 -0
- package/dist/renderer/host.d.ts +45 -0
- package/dist/renderer/host.js +30 -0
- package/dist/renderer/nativeHost.d.ts +83 -0
- package/dist/renderer/nativeHost.js +125 -0
- package/dist/resources.d.ts +71 -0
- package/dist/resources.js +83 -0
- package/dist/runtime.d.ts +38 -0
- package/dist/runtime.js +80 -0
- package/dist/runtimeVersion.d.ts +10 -0
- package/dist/runtimeVersion.js +18 -0
- package/dist/signing.d.ts +49 -0
- package/dist/signing.js +135 -0
- package/dist/stage.d.ts +22 -0
- package/dist/stage.js +138 -0
- package/dist/steam/cloudStore.d.ts +30 -0
- package/dist/steam/cloudStore.js +54 -0
- package/dist/steam/depot.d.ts +29 -0
- package/dist/steam/depot.js +39 -0
- package/dist/steam/main.d.ts +22 -0
- package/dist/steam/main.js +41 -0
- package/dist/templates/buildWindows.d.ts +16 -0
- package/dist/templates/buildWindows.js +161 -0
- package/dist/templates/packageWrapper.d.ts +9 -0
- package/dist/templates/packageWrapper.js +80 -0
- package/dist/toolchain.d.ts +101 -0
- package/dist/toolchain.js +186 -0
- package/package.json +73 -0
- package/src/android.ts +256 -0
- package/src/bootstrap.ts +389 -0
- package/src/build.ts +285 -0
- package/src/cli.ts +367 -0
- package/src/flags.ts +171 -0
- package/src/identity.ts +87 -0
- package/src/index.ts +31 -0
- package/src/init.ts +61 -0
- package/src/ios.ts +185 -0
- package/src/macCrossSign.ts +338 -0
- package/src/main/consoleForward.ts +93 -0
- package/src/main/csp.ts +57 -0
- package/src/main/entry.ts +63 -0
- package/src/main/ipc.ts +311 -0
- package/src/main/links.ts +29 -0
- package/src/main/menu.ts +53 -0
- package/src/main/serve.ts +70 -0
- package/src/main/shortcuts.ts +55 -0
- package/src/main/splash.ts +42 -0
- package/src/main/splashTiming.ts +51 -0
- package/src/main/window.ts +316 -0
- package/src/manifest.ts +157 -0
- package/src/mobile/assetCheck.ts +25 -0
- package/src/mobile/bridgeShim.ts +292 -0
- package/src/mobile/iosShim.ts +148 -0
- package/src/mobile/report.ts +121 -0
- package/src/mobile/versionCode.ts +17 -0
- package/src/outDir.ts +37 -0
- package/src/preload/bridge.ts +73 -0
- package/src/preload/entry.ts +91 -0
- package/src/renderer/fileStore.ts +38 -0
- package/src/renderer/host.ts +96 -0
- package/src/renderer/nativeHost.ts +146 -0
- package/src/resources.ts +150 -0
- package/src/runtime.ts +87 -0
- package/src/runtimeVersion.ts +16 -0
- package/src/signing.ts +206 -0
- package/src/stage.ts +180 -0
- package/src/steam/cloudStore.ts +73 -0
- package/src/steam/depot.ts +69 -0
- package/src/steam/main.ts +69 -0
- package/src/templates/buildWindows.ts +163 -0
- package/src/templates/packageWrapper.ts +82 -0
- package/src/toolchain.ts +210 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
4
|
+
import { writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { dirname, join, resolve } from 'node:path';
|
|
7
|
+
|
|
8
|
+
import { bootstrap } from './bootstrap.ts';
|
|
9
|
+
import { build, installedElectronVersion } from './build.ts';
|
|
10
|
+
import { planFlags } from './flags.ts';
|
|
11
|
+
import { refuseLyingFilesystem } from './identity.ts';
|
|
12
|
+
import { init } from './init.ts';
|
|
13
|
+
import { outFor } from './outDir.ts';
|
|
14
|
+
import { parseManifest } from './manifest.ts';
|
|
15
|
+
import type { PackageManifest, Target } from './manifest.ts';
|
|
16
|
+
import { planSigning } from './signing.ts';
|
|
17
|
+
import { electronMajor } from './runtimeVersion.ts';
|
|
18
|
+
import { depotScripts } from './steam/depot.ts';
|
|
19
|
+
import { androidToolchain, findRcodesign, needsRcodesign, requirementsFor } from './toolchain.ts';
|
|
20
|
+
import { stageApp } from './stage.ts';
|
|
21
|
+
import { resourceDir } from './resources.ts';
|
|
22
|
+
import type { ResourceOptions } from './resources.ts';
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* **`ELECTRON_RUN_AS_NODE` is removed from this process before anything else happens.**
|
|
26
|
+
*
|
|
27
|
+
* Any terminal that is itself an Electron application — an editor's built-in one is the ordinary
|
|
28
|
+
* case, and a developer's own shell had it set — exports the variable to everything it spawns.
|
|
29
|
+
* Inherited, it turns Electron into plain Node: a packaged build dies on `Cannot find module
|
|
30
|
+
* 'electron'` under a Node banner, which looks nothing like its cause, and in that mode the
|
|
31
|
+
* built-in module genuinely is not there.
|
|
32
|
+
*
|
|
33
|
+
* **It used to be removed for `run` alone**, in `launchEnv` below, which left `build` and every
|
|
34
|
+
* other subcommand inheriting it — and `build` is the one that spends twenty minutes before
|
|
35
|
+
* anything can go wrong. Deleted rather than set empty: Electron treats the variable as set
|
|
36
|
+
* whatever its value.
|
|
37
|
+
*/
|
|
38
|
+
delete process.env.ELECTRON_RUN_AS_NODE;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `drift-package` — the command a consumer runs to get an installable application.
|
|
42
|
+
*
|
|
43
|
+
* Four subcommands, and `verify` is the one that is not obvious. An artifact that was never
|
|
44
|
+
* rebuilt is byte-identical in every respect a build log can see, so the only honest check is to
|
|
45
|
+
* look inside the shipped thing for a string from the change. This repository already learned that
|
|
46
|
+
* about deploys: an asset hash proves nothing.
|
|
47
|
+
*
|
|
48
|
+
* Every failure exits non-zero with the reason on stderr. A packaging command that prints a
|
|
49
|
+
* complaint and exits 0 is a command that passes in a script.
|
|
50
|
+
*/
|
|
51
|
+
function loadManifest(cwd: string): PackageManifest {
|
|
52
|
+
const path = join(cwd, 'drift.package.json');
|
|
53
|
+
try {
|
|
54
|
+
return parseManifest(JSON.parse(readFileSync(path, 'utf8')) as unknown);
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
throw new Error(`could not read ${path}: ${String(cause)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function walk(dir: string, out: string[] = []): string[] {
|
|
61
|
+
for (const entry of readdirSync(dir)) {
|
|
62
|
+
const full = join(dir, entry);
|
|
63
|
+
if (statSync(full).isDirectory()) walk(full, out);
|
|
64
|
+
else out.push(full);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function doctor(cwd: string, options: ResourceOptions = {}): Promise<PackageManifest> {
|
|
70
|
+
/*
|
|
71
|
+
* **Before anything is read, downloaded or staged**, because the failure it replaces arrives ten
|
|
72
|
+
* minutes in and talks about copying a file into a subdirectory of itself. See `identity.ts`:
|
|
73
|
+
* a share that invents a file index makes Node refuse a copy that is perfectly legitimate.
|
|
74
|
+
*/
|
|
75
|
+
refuseLyingFilesystem(cwd, options.outRoot);
|
|
76
|
+
const manifest = loadManifest(cwd);
|
|
77
|
+
/*
|
|
78
|
+
* **The packager's own three directories, checked here rather than met mid-build.** `assets/`
|
|
79
|
+
* becomes every desktop artifact's `drift://shell/` origin, and a copy of the engine that
|
|
80
|
+
* flattened its packages has moved all three out of reach — which used to arrive as `ENOENT` on a
|
|
81
|
+
* path that is not in the project, after esbuild had already run. `doctor` is where this file
|
|
82
|
+
* already refuses what a build cannot do, before anything is downloaded or staged.
|
|
83
|
+
*
|
|
84
|
+
* `assets` alone: it is the one every target needs, and naming `android/` or `ios/` on a machine
|
|
85
|
+
* building for Linux would refuse a build that was never going to touch them. The two mobile
|
|
86
|
+
* builders check their own.
|
|
87
|
+
*/
|
|
88
|
+
await resourceDir('assets', options.resourceRoot);
|
|
89
|
+
/*
|
|
90
|
+
* **Against the Electron this machine would actually package**, not the one the package declares.
|
|
91
|
+
* A switch written for one Chromium and applied to another is ignored without a word, so doctor
|
|
92
|
+
* is where a runtime too old for it is named — before a build spends its minutes and ships a
|
|
93
|
+
* game that silently has no WebGPU.
|
|
94
|
+
*/
|
|
95
|
+
const electron = electronMajor(await installedElectronVersion().catch(() => null));
|
|
96
|
+
for (const target of manifest.targets) {
|
|
97
|
+
const plan = planFlags(manifest, target, electron);
|
|
98
|
+
if (plan.refusal !== null) throw new Error(`${target}: ${plan.refusal}`);
|
|
99
|
+
for (const note of plan.notes) console.log(` ${target}: ${note}`);
|
|
100
|
+
}
|
|
101
|
+
console.log(`ok: ${manifest.name} (${manifest.id}) for ${manifest.targets.join(', ')}`);
|
|
102
|
+
for (const target of manifest.targets) {
|
|
103
|
+
const signing = planSigning(target, process.env, process.platform);
|
|
104
|
+
console.log(` ${target}: signing ${signing.mode} — ${signing.reason}`);
|
|
105
|
+
}
|
|
106
|
+
console.log(
|
|
107
|
+
` splash: ${manifest.splash.show ? `the engine badge, held ${manifest.splash.minMs} ms` : 'off'}`,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
* **What each target still needs, and the command that fetches it.** A `doctor` that only
|
|
112
|
+
* validated a manifest would pass on a machine with no Java and no SDK, and the failure would
|
|
113
|
+
* arrive twenty minutes later from Gradle.
|
|
114
|
+
*/
|
|
115
|
+
for (const target of manifest.targets) {
|
|
116
|
+
const ready = await toolchainReady(target);
|
|
117
|
+
if (ready) continue;
|
|
118
|
+
console.log(
|
|
119
|
+
` ${target}: needs ${requirementsFor(target).join(', ')} — ` +
|
|
120
|
+
`run \`drift-package bootstrap --target=${target}\``,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return manifest;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function verify(cwd: string, needle: string): void {
|
|
127
|
+
const manifest = loadManifest(cwd);
|
|
128
|
+
const root = join(cwd, dirname(manifest.entry));
|
|
129
|
+
const found = walk(root).some((file) => readFileSync(file, 'utf8').includes(needle));
|
|
130
|
+
if (!found) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`verify: "${needle}" is in no file under ${root}. That is what a build that did not rerun ` +
|
|
133
|
+
'looks like — the artifact exists and is stale.',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
console.log(`ok: "${needle}" is in the built bundle`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The packaged application, run from source without producing an installer.
|
|
141
|
+
*
|
|
142
|
+
* **The whole point is that it is the same code path.** It stages exactly what a build stages and
|
|
143
|
+
* starts Electron on it, so the window, the flags, the `drift://` origin and the badge are the
|
|
144
|
+
* ones that will ship. A `run` that took a shortcut would be a rehearsal of something else.
|
|
145
|
+
*/
|
|
146
|
+
async function run(cwd: string, options: ResourceOptions): Promise<number> {
|
|
147
|
+
const manifest = await doctor(cwd, options);
|
|
148
|
+
const stageDir = mkdtempSync(join(tmpdir(), 'drift-run-'));
|
|
149
|
+
/* A development run: developer tools open, warnings forwarded. `build` stages the other kind. */
|
|
150
|
+
await stageApp(manifest, cwd, stageDir, { development: true, ...options });
|
|
151
|
+
const electron = await import('electron');
|
|
152
|
+
const binary = electron.default as unknown as string;
|
|
153
|
+
console.log(`[drift-package] running from ${stageDir}`);
|
|
154
|
+
return new Promise((resolve) => {
|
|
155
|
+
const child = spawn(binary, [stageDir], { stdio: 'inherit', env: launchEnv() });
|
|
156
|
+
child.on('close', (code) => {
|
|
157
|
+
/* The staging directory holds a whole copy of the game's build — tens of megabytes for a
|
|
158
|
+
real one — and a `run` per iteration would leave one of each in the temp directory. */
|
|
159
|
+
rmSync(stageDir, { recursive: true, force: true });
|
|
160
|
+
resolve(code ?? 0);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The environment Electron is started in, with one variable removed.
|
|
167
|
+
*
|
|
168
|
+
* **`ELECTRON_RUN_AS_NODE` turns Electron into plain Node**, and any Electron-based terminal sets
|
|
169
|
+
* it for the processes it spawns — an editor's integrated terminal is the common case, and this
|
|
170
|
+
* workspace's own shell had it set. Inherited, it produces a failure that looks nothing like its
|
|
171
|
+
* cause: the binary starts, prints a Node banner and dies on `Cannot find module 'electron'`,
|
|
172
|
+
* because in that mode the built-in module genuinely is not there.
|
|
173
|
+
*
|
|
174
|
+
* Deleted rather than overridden with an empty string: Electron treats the variable as set
|
|
175
|
+
* whatever its value.
|
|
176
|
+
*
|
|
177
|
+
* **This is now the second place it is removed** and it stays: the top of this file clears it for
|
|
178
|
+
* the whole process, and this clears it for the child. The reason is written twice because it is
|
|
179
|
+
* the reason somebody will come here looking for, and a copy of `process.env` taken elsewhere
|
|
180
|
+
* would not inherit the first deletion.
|
|
181
|
+
*/
|
|
182
|
+
function launchEnv(): NodeJS.ProcessEnv {
|
|
183
|
+
const env = { ...process.env };
|
|
184
|
+
delete env.ELECTRON_RUN_AS_NODE;
|
|
185
|
+
return env;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Whether this machine can build that target today.
|
|
190
|
+
*
|
|
191
|
+
* Desktop targets need the Electron runtime, which is present unless an install skipped its
|
|
192
|
+
* postinstall; Android needs a whole toolchain; iOS needs Xcode, which nothing here can check for
|
|
193
|
+
* beyond the platform it would have to be on.
|
|
194
|
+
*/
|
|
195
|
+
async function toolchainReady(target: Target): Promise<boolean> {
|
|
196
|
+
if (target === 'android') return (await androidToolchain()) !== null;
|
|
197
|
+
if (target === 'ios') return process.platform === 'darwin';
|
|
198
|
+
/* A Mac target built anywhere else is signed by rcodesign, and an unsigned .app will not
|
|
199
|
+
launch — so a machine without it is not ready for that target, however well it packs. */
|
|
200
|
+
if (needsRcodesign(target)) return (await findRcodesign()) !== null;
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The two files `steamcmd` uploads a build with, written beside the artifacts they describe.
|
|
206
|
+
*
|
|
207
|
+
* A separate command rather than part of `build`, because uploading is a decision: the scripts
|
|
208
|
+
* name an app id that costs money to get wrong, and a build is often made several times before one
|
|
209
|
+
* of them is the one that ships.
|
|
210
|
+
*/
|
|
211
|
+
async function writeSteamScripts(
|
|
212
|
+
cwd: string,
|
|
213
|
+
args: readonly string[],
|
|
214
|
+
options: ResourceOptions,
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
const manifest = loadManifest(cwd);
|
|
217
|
+
if (manifest.steam.appId === null) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
'steam: `steam.appId` is null in drift.package.json. Set it to the app id Valve gave you; ' +
|
|
220
|
+
"a depot script with the wrong one uploads into somebody else's game.",
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const target = (
|
|
224
|
+
args.find((entry) => entry.startsWith('--target=')) ?? '--target=linux-x64'
|
|
225
|
+
).slice('--target='.length) as Target;
|
|
226
|
+
const depotArg = args.find((entry) => entry.startsWith('--depot='));
|
|
227
|
+
/* A first depot is conventionally the app id plus one, which is how Valve numbers them. */
|
|
228
|
+
const depotId =
|
|
229
|
+
depotArg === undefined ? manifest.steam.appId + 1 : Number.parseInt(depotArg.slice(8), 10);
|
|
230
|
+
const branchArg = args.find((entry) => entry.startsWith('--branch='));
|
|
231
|
+
|
|
232
|
+
const outDir = outFor(cwd, target, options);
|
|
233
|
+
const content =
|
|
234
|
+
target === 'linux-x64' ? 'linux-unpacked' : target === 'win-x64' ? 'win-unpacked' : 'mac';
|
|
235
|
+
const scripts = depotScripts({
|
|
236
|
+
appId: manifest.steam.appId,
|
|
237
|
+
depotId,
|
|
238
|
+
description: `${manifest.name} ${target}`,
|
|
239
|
+
contentRoot: content,
|
|
240
|
+
branch: branchArg === undefined ? null : branchArg.slice('--branch='.length),
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await writeFile(join(outDir, scripts.appFileName), scripts.app, 'utf8');
|
|
244
|
+
await writeFile(join(outDir, scripts.depotFileName), scripts.depot, 'utf8');
|
|
245
|
+
console.log(`ok: ${join(outDir, scripts.appFileName)}`);
|
|
246
|
+
console.log(`ok: ${join(outDir, scripts.depotFileName)}`);
|
|
247
|
+
console.log(
|
|
248
|
+
` upload with: steamcmd +login <user> +run_app_build "${join(outDir, scripts.appFileName)}" +quit`,
|
|
249
|
+
);
|
|
250
|
+
console.log(" nothing is set live: the build waits in Steam's admin for a person");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** What this machine builds for itself, used when nothing has said otherwise. */
|
|
254
|
+
function hostTarget(): Target {
|
|
255
|
+
if (process.platform === 'win32') return 'win-x64';
|
|
256
|
+
if (process.platform === 'darwin') return process.arch === 'x64' ? 'mac-x64' : 'mac-arm64';
|
|
257
|
+
return 'linux-x64';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function targetsFrom(manifest: PackageManifest, args: readonly string[]): readonly Target[] {
|
|
261
|
+
const asked = args.find((entry) => entry.startsWith('--target='));
|
|
262
|
+
if (asked === undefined) return manifest.targets;
|
|
263
|
+
const name = asked.slice('--target='.length);
|
|
264
|
+
const found = manifest.targets.find((target) => target === name);
|
|
265
|
+
if (found === undefined) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`--target=${name} is not in this manifest's targets (${manifest.targets.join(', ')})`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return [found];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The project being packaged, which is the working directory unless `--project=` says otherwise.
|
|
277
|
+
*
|
|
278
|
+
* **Because the packager and the game are often not the same repository.** A game that keeps its
|
|
279
|
+
* `drift.package.json` and nothing else — no Electron, no electron-builder, no Android SDK in its
|
|
280
|
+
* own `node_modules` — is packaged by pointing this at it from wherever the tool lives, and that
|
|
281
|
+
* is the arrangement most consumers actually want: half a gigabyte of build tooling does not
|
|
282
|
+
* belong in a game's dependency tree.
|
|
283
|
+
*/
|
|
284
|
+
const projectFlag = rest.find((entry) => entry.startsWith('--project='));
|
|
285
|
+
const cwd =
|
|
286
|
+
projectFlag === undefined
|
|
287
|
+
? process.cwd()
|
|
288
|
+
: resolve(process.cwd(), projectFlag.slice('--project='.length));
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Where the packager's own `assets/`, `android/` and `ios/` are, if not beside its sources.
|
|
292
|
+
*
|
|
293
|
+
* **For a consumer that vendors engine source** rather than installing it: such a vendor copies
|
|
294
|
+
* `packages/<name>/src/**` to `<name>/**` and the flattening moves
|
|
295
|
+
* the three out of reach of anything this package can compute. Reported from outside, where the
|
|
296
|
+
* workaround was a layout exception in the vendor script for this one package. See `resources.ts`
|
|
297
|
+
* for why this is a flag rather than an environment variable.
|
|
298
|
+
*/
|
|
299
|
+
const resourcesFlag = rest.find((entry) => entry.startsWith('--resources='));
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Where builds write, if not `out/` inside the project.
|
|
303
|
+
*
|
|
304
|
+
* **For a project on a filesystem that misreports file identity**, where the first staging copy
|
|
305
|
+
* of a build refuses itself: `fs.cp` compares `dev` and `ino` up the destination's ancestors, and
|
|
306
|
+
* a shared or network folder can answer the same pair for a file and a directory. `outDir.ts`
|
|
307
|
+
* carries the measurement. Absolute here, or relative to wherever the command was run, which is
|
|
308
|
+
* not necessarily `--project=`.
|
|
309
|
+
*/
|
|
310
|
+
const outFlag = rest.find((entry) => entry.startsWith('--out='));
|
|
311
|
+
const resources: ResourceOptions = {
|
|
312
|
+
...(resourcesFlag === undefined
|
|
313
|
+
? {}
|
|
314
|
+
: { resourceRoot: resourcesFlag.slice('--resources='.length) }),
|
|
315
|
+
...(outFlag === undefined ? {} : { outRoot: outFlag.slice('--out='.length) }),
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
if (command === 'doctor') {
|
|
320
|
+
await doctor(cwd, resources);
|
|
321
|
+
} else if (command === 'verify') {
|
|
322
|
+
const arg = rest.find((entry) => entry.startsWith('--contains='));
|
|
323
|
+
if (arg === undefined) throw new Error('verify needs --contains=<string>');
|
|
324
|
+
verify(cwd, arg.slice('--contains='.length));
|
|
325
|
+
} else if (command === 'run') {
|
|
326
|
+
process.exitCode = await run(cwd, resources);
|
|
327
|
+
} else if (command === 'bootstrap') {
|
|
328
|
+
/* Deliberately does not need a manifest: a machine is prepared before a project exists on it,
|
|
329
|
+
and `--target=` names what to prepare for. */
|
|
330
|
+
const asked = rest.find((entry) => entry.startsWith('--target='));
|
|
331
|
+
const targets =
|
|
332
|
+
asked === undefined
|
|
333
|
+
? ((): readonly Target[] => {
|
|
334
|
+
try {
|
|
335
|
+
return loadManifest(cwd).targets;
|
|
336
|
+
} catch {
|
|
337
|
+
/* No manifest here: prepare for this desktop, which is the useful default on a
|
|
338
|
+
machine somebody is setting up. */
|
|
339
|
+
return [hostTarget()];
|
|
340
|
+
}
|
|
341
|
+
})()
|
|
342
|
+
: [asked.slice('--target='.length) as Target];
|
|
343
|
+
await bootstrap(targets);
|
|
344
|
+
} else if (command === 'init') {
|
|
345
|
+
/*
|
|
346
|
+
* Scaffolds a *project*, where `bootstrap` prepares a *machine*. Two commands because a
|
|
347
|
+
* machine is prepared once and a project once per project, and conflating them would make the
|
|
348
|
+
* one that writes files into your repository the one you run to install a JDK.
|
|
349
|
+
*/
|
|
350
|
+
await init(cwd, loadManifest(cwd), rest.includes('--force'));
|
|
351
|
+
} else if (command === 'steam') {
|
|
352
|
+
await writeSteamScripts(cwd, rest, resources);
|
|
353
|
+
} else if (command === 'build') {
|
|
354
|
+
/* Checked before anything is downloaded or staged: a build that would be refused must be
|
|
355
|
+
refused first, not after twenty minutes of Electron. */
|
|
356
|
+
const manifest = await doctor(cwd, resources);
|
|
357
|
+
await build(manifest, cwd, targetsFrom(manifest, rest), resources);
|
|
358
|
+
} else {
|
|
359
|
+
throw new Error(
|
|
360
|
+
'usage: drift-package <init|bootstrap|doctor|build|run|verify|steam> [--project=<dir>] ' +
|
|
361
|
+
`[--target=<target>] [--resources=<dir>] [--out=<dir>] [--force] — got "${String(command)}"`,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
} catch (cause) {
|
|
365
|
+
console.error(String(cause instanceof Error ? cause.message : cause));
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
package/src/flags.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { PackageManifest, Target } from './manifest.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Chromium switches a shipped build launches with.
|
|
5
|
+
*
|
|
6
|
+
* **This is most of why the packager exists.** Every switch below was paid for once already, in
|
|
7
|
+
* this repository or by a consumer, and a game that wraps Electron by hand rediscovers each of
|
|
8
|
+
* them — two of them only by measuring a frame nobody thought to measure.
|
|
9
|
+
*
|
|
10
|
+
* **What this costs**: the flags are ours rather than the platform's defaults, so a Chromium
|
|
11
|
+
* change can invalidate one and nothing will say so. **What would make it wrong**: Electron
|
|
12
|
+
* enabling Vulkan on Linux by default, at which point the first block below becomes a no-op worth
|
|
13
|
+
* deleting rather than a fix worth keeping.
|
|
14
|
+
*
|
|
15
|
+
* **Checked 2026-09-09 and still needed**, on Electron 43.4.1 / Chromium 150.0.7871.224 with an
|
|
16
|
+
* RX 9070 XT: launched with no switches at all, `navigator.gpu` is present and `requestAdapter`
|
|
17
|
+
* answers **null**. With the interop feature it hands back an `amd`/`rdna-4` adapter and a device.
|
|
18
|
+
* So the block below is doing the work rather than riding along, six Chromium majors after it was
|
|
19
|
+
* written.
|
|
20
|
+
*
|
|
21
|
+
* **And a switch is now only asked for on a runtime it has been seen to work on**, because the
|
|
22
|
+
* other half of "nothing will say so" is a name Chromium does not recognise: unknown features in
|
|
23
|
+
* `--enable-features` are dropped in silence, and the build that results is indistinguishable from
|
|
24
|
+
* a machine with no adapter to offer. See `WEBGPU_INTEROP_ELECTRON`.
|
|
25
|
+
*/
|
|
26
|
+
export interface FlagPlan {
|
|
27
|
+
readonly switches: readonly (readonly [string, string])[];
|
|
28
|
+
/** Non-null means refuse to build, and this is what to print. */
|
|
29
|
+
readonly refusal: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* What a caller must print, having decided nothing.
|
|
32
|
+
*
|
|
33
|
+
* **A switch that names a feature the runtime has never heard of is worse than no switch**, and
|
|
34
|
+
* that is what these exist for: Chromium ignores an unknown name in `--enable-features` without a
|
|
35
|
+
* word, so a build that cannot deliver WebGPU looks exactly like a machine whose driver had no
|
|
36
|
+
* adapter to offer. Where this plan declines to ask for something, it says so out loud.
|
|
37
|
+
*/
|
|
38
|
+
readonly notes: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The Electron the Linux WebGPU switch below is written against, and measured on.
|
|
43
|
+
*
|
|
44
|
+
* **Not the version the feature was introduced in, which nobody here has established.** It is the
|
|
45
|
+
* version this repository has run the switch against and watched WebGPU come up: Electron 43.4.1,
|
|
46
|
+
* Chromium 150.0.7871.224, an RX 9070 XT, where `requestAdapter` answers null without the switch
|
|
47
|
+
* and hands back an `amd`/`rdna-4` adapter with it. Reported from outside against Electron 33.4.11,
|
|
48
|
+
* Chromium 130: the feature name is not in that binary at all, and the switch is inert there.
|
|
49
|
+
*
|
|
50
|
+
* So a runtime older than this is *unverified*, not known-broken — and the plan declines to ship an
|
|
51
|
+
* unverified switch rather than shipping one that may do nothing silently.
|
|
52
|
+
*/
|
|
53
|
+
export const WEBGPU_INTEROP_ELECTRON = 43;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param electronMajor The major version of the Electron these switches will be applied to, or
|
|
57
|
+
* `null` where a caller genuinely cannot know. Both callers in this package can: the build reads
|
|
58
|
+
* it out of the installed package, and the main process reads it out of `process.versions`.
|
|
59
|
+
*/
|
|
60
|
+
export function planFlags(
|
|
61
|
+
manifest: PackageManifest,
|
|
62
|
+
target: Target,
|
|
63
|
+
electronMajor: number | null = null,
|
|
64
|
+
): FlagPlan {
|
|
65
|
+
/*
|
|
66
|
+
* **A mobile host has no command line to pass switches on.** The renderer is the platform's own
|
|
67
|
+
* WebView, updated by the store rather than by us, and every decision below is about a Chromium
|
|
68
|
+
* this package ships. Returning nothing is the honest answer rather than switches that would be
|
|
69
|
+
* silently dropped — and it is why the Linux refusal further down cannot fire on a phone.
|
|
70
|
+
*/
|
|
71
|
+
if (target === 'android' || target === 'ios') return { switches: [], refusal: null, notes: [] };
|
|
72
|
+
|
|
73
|
+
const switches: (readonly [string, string])[] = [];
|
|
74
|
+
const notes: string[] = [];
|
|
75
|
+
const wantsWebGpu = manifest.backend.webgpu !== 'off';
|
|
76
|
+
const linux = target === 'linux-x64';
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* **A software rasteriser is never chosen silently.** Plain headless Chromium selects
|
|
80
|
+
* SwiftShader on a development machine, measured, and says nothing about it — every
|
|
81
|
+
* frame it produces is a lie about what a player sees and it saturates the CPU producing it.
|
|
82
|
+
* `requireHardwareGpu()` refuses it in the capture harness; this refuses it in the product.
|
|
83
|
+
*/
|
|
84
|
+
if (!manifest.backend.allowSoftwareRenderer) {
|
|
85
|
+
switches.push(['disable-software-rasterizer', '']);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (linux && wantsWebGpu) {
|
|
89
|
+
/*
|
|
90
|
+
* **Electron does not enable WebGPU on Linux although Chrome does** (`electron#40929`), so
|
|
91
|
+
* without this there is no WebGPU on that target at all — `requestAdapter` simply answers null
|
|
92
|
+
* and the engine falls back, correctly and invisibly.
|
|
93
|
+
*
|
|
94
|
+
* **The interop feature, and deliberately not `--use-angle=vulkan --enable-features=Vulkan`.**
|
|
95
|
+
* Both yield WebGPU. The second also breaks video encoding under Wayland: measured on an
|
|
96
|
+
* RX 9070 XT, canvas frames encode as all-zero YUV — a green clip — with nothing reporting it,
|
|
97
|
+
* since `VideoEncoder.error` never fires and the muxer finalises a file of the expected size.
|
|
98
|
+
* `ForceEnableWebGpuInterop` composites WebGPU through GL instead of moving ANGLE onto Vulkan,
|
|
99
|
+
* and both survive: measured in a packaged build on this machine, WebGPU drew and a vp8 round
|
|
100
|
+
* trip came back magenta rather than green.
|
|
101
|
+
*
|
|
102
|
+
* **This is why clip export and WebGPU are no longer refused together.** They were, for as
|
|
103
|
+
* long as the only switch known to work was the one that broke encoding.
|
|
104
|
+
*
|
|
105
|
+
* **What would make it wrong** is a Chromium that retires the feature, at which point WebGPU
|
|
106
|
+
* quietly stops being offered on Linux and the acceptance probe reports the fallback — which
|
|
107
|
+
* is the failure mode to watch for rather than a green clip.
|
|
108
|
+
*
|
|
109
|
+
* **Reported from outside as hanging a Wayland session, and it does not do that here.** The
|
|
110
|
+
* report is a packaged game that never paints on AMD under Wayland: an adapter is offered and
|
|
111
|
+
* the renderer's creation never returns. Chromium does print
|
|
112
|
+
* `'--ozone-platform=wayland' is not compatible with Vulkan` on stderr while this switch is
|
|
113
|
+
* applied, on this machine too. But run against the engine's own starter — an RX 9070 XT on a
|
|
114
|
+
* Wayland session under COSMIC, Electron 43.4.1, Chromium 150 — the acquisition completes in
|
|
115
|
+
* **153 ms, three runs of three**, and the example draws with WebGPU. The bare path completes
|
|
116
|
+
* as well: adapter, device, canvas context, swapchain, submit, and a one-pixel readback that
|
|
117
|
+
* comes back magenta.
|
|
118
|
+
*
|
|
119
|
+
* **Chromium's own advice is worse here.** Pairing this with `--ozone-platform=x11`, which is
|
|
120
|
+
* what that message suggests, takes the GPU process down on this machine: `exit_code=139`, and
|
|
121
|
+
* a screenshot request answers `UnknownVizError`. So it is not offered.
|
|
122
|
+
*
|
|
123
|
+
* The switch is therefore kept, because withdrawing it on every Wayland session would take a
|
|
124
|
+
* measured-working backend away from every machine like this one. What the report is right
|
|
125
|
+
* about is that a stall had no floor under it, and that is closed where it belongs: see
|
|
126
|
+
* `BACKEND_TIMEOUT_MS` in the engine's backend selection, which answers WebGL2 rather than
|
|
127
|
+
* waiting for a device that is not coming.
|
|
128
|
+
*/
|
|
129
|
+
/*
|
|
130
|
+
* **And it is only asked for on a runtime it has been seen to work on.** The switch was
|
|
131
|
+
* verified once and then travelled, unchecked, into whatever Electron a consumer's install
|
|
132
|
+
* happened to resolve — which is how a build reached a Chromium whose binary does not contain
|
|
133
|
+
* the feature name at all. Nothing reported it, because nothing could: an ignored feature name
|
|
134
|
+
* and an absent adapter produce the same product.
|
|
135
|
+
*/
|
|
136
|
+
/*
|
|
137
|
+
* **And there is no second-best route on an older runtime, deliberately.**
|
|
138
|
+
*
|
|
139
|
+
* There is one that works: measured on Electron 33.4.11 against this machine, WebGPU comes up
|
|
140
|
+
* under `--enable-unsafe-webgpu` *and* `--enable-features=Vulkan` together — neither alone
|
|
141
|
+
* produces an adapter, nor does `--use-angle=vulkan` beside them. It is not offered, for two
|
|
142
|
+
* reasons that hold together. This package declares the Electron it is written against, so a
|
|
143
|
+
* build resolving an older one has an install to fix rather than a workaround to adopt; and the
|
|
144
|
+
* route costs video encoding under Wayland, where canvas frames encode as all-zero YUV — a
|
|
145
|
+
* green clip, with `VideoEncoder.error` never firing and the muxer finalising a file of the
|
|
146
|
+
* expected size. Shipping an unsafe flag and that trade, to a configuration this package says
|
|
147
|
+
* it does not support, is a worse answer than saying so.
|
|
148
|
+
*/
|
|
149
|
+
if (electronMajor === null || electronMajor >= WEBGPU_INTEROP_ELECTRON) {
|
|
150
|
+
switches.push(['enable-features', 'ForceEnableWebGpuInterop']);
|
|
151
|
+
} else if (manifest.backend.webgpu === 'require') {
|
|
152
|
+
return {
|
|
153
|
+
switches,
|
|
154
|
+
notes,
|
|
155
|
+
refusal:
|
|
156
|
+
`backend.webgpu is "require" and Electron ${electronMajor} cannot deliver it on Linux. ` +
|
|
157
|
+
`This package declares Electron ${WEBGPU_INTEROP_ELECTRON}; an install resolving an older ` +
|
|
158
|
+
'one is the thing to fix. Otherwise set backend.webgpu to "prefer" or "off" to ship ' +
|
|
159
|
+
'WebGL2 on this target deliberately.',
|
|
160
|
+
};
|
|
161
|
+
} else {
|
|
162
|
+
notes.push(
|
|
163
|
+
`no WebGPU on Linux: Electron ${electronMajor} predates the interop switch this package is ` +
|
|
164
|
+
`written against (${WEBGPU_INTEROP_ELECTRON}), so this build falls back to WebGL2 there. ` +
|
|
165
|
+
'Install the Electron this package declares to get it back.',
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { switches, refusal: null, notes };
|
|
171
|
+
}
|
package/src/identity.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/** The two fields Node compares to decide whether two paths are the same thing. */
|
|
5
|
+
export interface Identity {
|
|
6
|
+
readonly dev: bigint;
|
|
7
|
+
readonly ino: bigint;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Node's own `areIdentical`, which is what decides whether a copy is refused.
|
|
12
|
+
*
|
|
13
|
+
* The two zeros are excluded there and here: a filesystem that hands out no index answers 0, and
|
|
14
|
+
* Node declines to believe it.
|
|
15
|
+
*
|
|
16
|
+
* **`bigint` and not `number`.** A Windows file index is 64 bits and loses precision as a double
|
|
17
|
+
* exactly in the high half where those values live, so two different indices can arrive equal and
|
|
18
|
+
* two equal ones different — the check would be wrong in both directions on precisely the platform
|
|
19
|
+
* it exists for.
|
|
20
|
+
*/
|
|
21
|
+
export function sameIdentity(a: Identity, b: Identity): boolean {
|
|
22
|
+
return a.ino !== 0n && a.dev !== 0n && a.ino === b.ino && a.dev === b.dev;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Refuse a filesystem that cannot tell a file from a directory, before a build spends its minutes.
|
|
27
|
+
*
|
|
28
|
+
* **The failure this replaces is a sentence about the one thing that is not happening.** `stage.ts`
|
|
29
|
+
* copies `drift.package.json` into the staging directory, and Node's `fs.cp` refuses to copy a
|
|
30
|
+
* thing into itself — deciding that by walking the destination's ancestors and comparing each
|
|
31
|
+
* one's `dev` and `ino` against the source's. On a filesystem that reports those honestly it never
|
|
32
|
+
* fires. On a network share it can, and what comes out is
|
|
33
|
+
*
|
|
34
|
+
* cannot copy …\drift.package.json to a subdirectory of self …\out\win-x64\stage\…
|
|
35
|
+
*
|
|
36
|
+
* ten minutes into a build, about the thing furthest from the cause you could imagine. Reported
|
|
37
|
+
* from a Windows VM building over a mapped drive, where a file and a directory both answered
|
|
38
|
+
* `dev=66313 ino=-112686486700016`: a redirector that gets no file index from the server invents a
|
|
39
|
+
* constant one, and on Windows `dev` is already the volume serial number.
|
|
40
|
+
*
|
|
41
|
+
* **The witnesses are not the directories Node will really compare, and that is fine.**
|
|
42
|
+
* `checkParentPaths` walks up from `out/<target>/stage` and stops at the source's parent, so it
|
|
43
|
+
* compares `stage`, `<target>` and `out` — which on a first build do not exist yet and cannot be
|
|
44
|
+
* stat'ed here. What is being established is not *which* directory collides but that this volume
|
|
45
|
+
* gives a file and a directory the same identity, which is a property of the volume. The project
|
|
46
|
+
* root and `out/` are witnesses as good as any.
|
|
47
|
+
*
|
|
48
|
+
* **The remedy it names is `--out=<dir>`, and that is what changed.** Until that flag existed the
|
|
49
|
+
* only answer was to clone onto a local disk, which for a project with tens of gigabytes of assets
|
|
50
|
+
* is a real cost. Now the output can simply be sent somewhere that tells the truth, and the copy is
|
|
51
|
+
* the second suggestion rather than the first.
|
|
52
|
+
*
|
|
53
|
+
* **`look` is a seam and exists for one reason**: the condition cannot be reproduced on any
|
|
54
|
+
* filesystem this suite can create, so without it the refusal — the whole point of the function —
|
|
55
|
+
* would ship having never been seen to fire. The default is `statSync` and no caller passes
|
|
56
|
+
* anything else.
|
|
57
|
+
*/
|
|
58
|
+
export function refuseLyingFilesystem(
|
|
59
|
+
cwd: string,
|
|
60
|
+
outRoot: string | undefined,
|
|
61
|
+
look: (path: string) => Identity = (path) => statSync(path, { bigint: true }),
|
|
62
|
+
): void {
|
|
63
|
+
/* Already pointed off this volume: whatever it answers about itself no longer decides a copy. */
|
|
64
|
+
if (outRoot !== undefined && !resolve(process.cwd(), outRoot).startsWith(resolve(cwd))) return;
|
|
65
|
+
|
|
66
|
+
const manifest = join(cwd, 'drift.package.json');
|
|
67
|
+
if (!existsSync(manifest)) return;
|
|
68
|
+
const its = look(manifest);
|
|
69
|
+
|
|
70
|
+
for (const witness of [cwd, join(cwd, 'out')]) {
|
|
71
|
+
if (!existsSync(witness)) continue;
|
|
72
|
+
const theirs = look(witness);
|
|
73
|
+
if (!sameIdentity(its, theirs)) continue;
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${cwd} is on a filesystem that gives a file and a directory the same identity.\n\n` +
|
|
76
|
+
` ${manifest}\n dev=${its.dev} ino=${its.ino}\n` +
|
|
77
|
+
` ${witness}\n dev=${theirs.dev} ino=${theirs.ino}\n\n` +
|
|
78
|
+
'Node uses those two fields to avoid copying something into itself, so the first copy of ' +
|
|
79
|
+
'the staging step fails with "cannot copy … to a subdirectory of self …" — a message ' +
|
|
80
|
+
'about the thing furthest from the cause, ten minutes into a build. It is what a network ' +
|
|
81
|
+
'share does: the redirector invents a constant file index when the server gives it none.\n\n' +
|
|
82
|
+
'Send the output somewhere that tells the truth:\n\n' +
|
|
83
|
+
' drift-package build --out=/a/local/disk\n\n' +
|
|
84
|
+
'Or build from a local copy of the project, if the assets are small enough to move.',
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*! DriftEngine | Copyright 2026 Drift Technologies | Apache-2.0 | https://github.com/drftrun/driftengine */
|
|
2
|
+
/**
|
|
3
|
+
* What a game imports from the packager.
|
|
4
|
+
*
|
|
5
|
+
* **The renderer half only.** The main process, the CLI, the builder and the staging step are
|
|
6
|
+
* Node and are reached through the `drift-package` command rather than through this barrel — a
|
|
7
|
+
* game bundling `electron` or `esbuild` into its own payload is the failure this boundary exists
|
|
8
|
+
* to prevent.
|
|
9
|
+
*
|
|
10
|
+
* `createHost` is the one call most consumers need; the classes beside it are for a game that
|
|
11
|
+
* assembles its capabilities itself.
|
|
12
|
+
*/
|
|
13
|
+
export { createHost } from './renderer/host.ts';
|
|
14
|
+
export type { Host } from './renderer/host.ts';
|
|
15
|
+
export { FileStore } from './renderer/fileStore.ts';
|
|
16
|
+
export {
|
|
17
|
+
NativeDisplay,
|
|
18
|
+
NativeFileDialogs,
|
|
19
|
+
NativeLifecycle,
|
|
20
|
+
hostBridge,
|
|
21
|
+
} from './renderer/nativeHost.ts';
|
|
22
|
+
/*
|
|
23
|
+
* A store's own features, when a store answered.
|
|
24
|
+
*
|
|
25
|
+
* `createHost().services` is how a game reaches these — null in a browser, null where the manifest
|
|
26
|
+
* named no app, and null when the copy was launched outside the store. The SDK itself is not here:
|
|
27
|
+
* it is a native Node module held by the main process, because the renderer has no Node at all.
|
|
28
|
+
*/
|
|
29
|
+
export { NativePlatformServices } from './renderer/nativeHost.ts';
|
|
30
|
+
export { BRIDGE_KEY } from './preload/bridge.ts';
|
|
31
|
+
export type { DriftHostBridge } from './preload/bridge.ts';
|