@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/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@driftengine/package",
|
|
3
|
+
"version": "3.61.0",
|
|
4
|
+
"description": "Builds an installable application from a game built on the engine",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"drift-source": "./src/index.ts",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json",
|
|
16
|
+
"./*": "./*"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"!src/**/*.test.ts",
|
|
22
|
+
"!src/**/*.test.mjs",
|
|
23
|
+
"!src/**/__snapshots__",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"NOTICE"
|
|
27
|
+
],
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"bin": {
|
|
30
|
+
"drift-package": "bin/drift-package.mjs"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@driftengine/core": "3.61.0",
|
|
34
|
+
"steamworks.js": ">=0.4.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"electron": "^43.4.1",
|
|
38
|
+
"electron-builder": "^26.0.0",
|
|
39
|
+
"esbuild": "0.28.2",
|
|
40
|
+
"tsx": "^4.23.12"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"steamworks.js": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"author": "Drift Technologies",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/drftrun/driftengine.git",
|
|
51
|
+
"directory": "packages/package"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/drftrun/driftengine#readme",
|
|
54
|
+
"bugs": "https://github.com/drftrun/driftengine/issues",
|
|
55
|
+
"keywords": [
|
|
56
|
+
"driftengine",
|
|
57
|
+
"3d",
|
|
58
|
+
"webgl",
|
|
59
|
+
"webgpu",
|
|
60
|
+
"typescript",
|
|
61
|
+
"electron",
|
|
62
|
+
"android",
|
|
63
|
+
"packaging",
|
|
64
|
+
"desktop",
|
|
65
|
+
"installer"
|
|
66
|
+
],
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=22.12.0"
|
|
69
|
+
},
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"access": "public"
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/android.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { build as esbuild } from 'esbuild';
|
|
4
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
import type { PackageManifest } from './manifest.ts';
|
|
8
|
+
import { resourceDir } from './resources.ts';
|
|
9
|
+
import type { ResourceOptions } from './resources.ts';
|
|
10
|
+
import { missingReferences } from './mobile/assetCheck.ts';
|
|
11
|
+
import { versionCodeFor } from './mobile/versionCode.ts';
|
|
12
|
+
import { androidToolchain, toolchainHome } from './toolchain.ts';
|
|
13
|
+
|
|
14
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* An installable APK, from the same web build the desktop shell runs.
|
|
18
|
+
*
|
|
19
|
+
* **The Gradle project is a template that is copied, never built in place.** A consumer's game is
|
|
20
|
+
* tens of megabytes of assets; copying it into `packages/package/android/` would put it in this
|
|
21
|
+
* repository's working tree, and a `git status` after one build would be unreadable. So the
|
|
22
|
+
* template is staged into `out/android/project` and the build happens there.
|
|
23
|
+
*
|
|
24
|
+
* **Signed with a throwaway key, on purpose and stated loudly.** Android refuses to install an
|
|
25
|
+
* unsigned APK at all, so there is no "unsigned" option to offer — the choice is between a key
|
|
26
|
+
* this generates and a key a person owns. A generated one installs on a device with developer mode
|
|
27
|
+
* on, which is what this is for; it cannot go to the Play Store, and an upgrade signed with a
|
|
28
|
+
* *different* generated key is refused by the device as an impostor. `DRIFT_ANDROID_KEYSTORE` and
|
|
29
|
+
* its two passwords take over the moment somebody has a real one.
|
|
30
|
+
*/
|
|
31
|
+
export async function buildAndroid(
|
|
32
|
+
manifest: PackageManifest,
|
|
33
|
+
projectDir: string,
|
|
34
|
+
outDir: string,
|
|
35
|
+
options: ResourceOptions = {},
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
const toolchain = await androidToolchain();
|
|
38
|
+
if (toolchain === null) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
'the Android toolchain is not here yet. Run `drift-package bootstrap --target=android`, ' +
|
|
41
|
+
'which fetches a JDK, the SDK and Gradle into a cache; or set JAVA_HOME and ANDROID_HOME ' +
|
|
42
|
+
'to ones you already have.',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const projectRoot = join(outDir, 'project');
|
|
47
|
+
await rm(projectRoot, { recursive: true, force: true });
|
|
48
|
+
await mkdir(projectRoot, { recursive: true });
|
|
49
|
+
await cp(await resourceDir('android', options.resourceRoot), projectRoot, { recursive: true });
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* **The game goes at the asset root**, because that is where its own absolute references point.
|
|
53
|
+
* See the comment in `MainActivity.java`: a build served from a subdirectory loads its HTML and
|
|
54
|
+
* nothing else.
|
|
55
|
+
*/
|
|
56
|
+
const assets = join(projectRoot, 'app', 'src', 'main', 'assets');
|
|
57
|
+
await mkdir(assets, { recursive: true });
|
|
58
|
+
const distDir = resolve(projectDir, dirname(manifest.entry));
|
|
59
|
+
await cp(distDir, assets, { recursive: true });
|
|
60
|
+
await checkAssetReferences(assets, basename(manifest.entry));
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
* The shim is bundled as an IIFE rather than a module: it is evaluated as a document-start
|
|
64
|
+
* script by the WebView, which is a bare script rather than a module context.
|
|
65
|
+
*/
|
|
66
|
+
await esbuild({
|
|
67
|
+
stdin: {
|
|
68
|
+
contents: `import { attachShim } from ${JSON.stringify(join(HERE, 'mobile', 'bridgeShim.ts'))};\nattachShim('__driftHost');\n`,
|
|
69
|
+
resolveDir: HERE,
|
|
70
|
+
loader: 'ts',
|
|
71
|
+
},
|
|
72
|
+
outfile: join(assets, 'drift-host', 'shim.js'),
|
|
73
|
+
bundle: true,
|
|
74
|
+
format: 'iife',
|
|
75
|
+
platform: 'browser',
|
|
76
|
+
target: 'es2022',
|
|
77
|
+
logLevel: 'warning',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (manifest.icon !== null) {
|
|
81
|
+
/* One PNG at one density: Android scales it for the rest, and a game that wants a full set
|
|
82
|
+
supplies its own `res/` through its own build rather than through this. */
|
|
83
|
+
await cp(
|
|
84
|
+
resolve(projectDir, manifest.icon),
|
|
85
|
+
join(projectRoot, 'app', 'src', 'main', 'res', 'mipmap-xxhdpi', 'ic_launcher.png'),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const keystore = await ensureKeystore(manifest.id, toolchain.javaHome);
|
|
90
|
+
await writeFile(join(projectRoot, 'local.properties'), `sdk.dir=${toolchain.sdkRoot}\n`, 'utf8');
|
|
91
|
+
|
|
92
|
+
const version = await consumerVersion(projectDir);
|
|
93
|
+
console.log(`[drift-package] android: building ${manifest.name} ${version}`);
|
|
94
|
+
console.log(`[drift-package] android: signing ${keystore.describe}`);
|
|
95
|
+
|
|
96
|
+
await gradle(toolchain, projectRoot, [
|
|
97
|
+
'assembleRelease',
|
|
98
|
+
`-PdriftAppId=${manifest.id}`,
|
|
99
|
+
`-PdriftAppName=${manifest.name}`,
|
|
100
|
+
`-PdriftVersionName=${version}`,
|
|
101
|
+
`-PdriftVersionCode=${versionCodeFor(version)}`,
|
|
102
|
+
`-PdriftKeystore=${keystore.path}`,
|
|
103
|
+
`-PdriftKeystorePassword=${keystore.password}`,
|
|
104
|
+
`-PdriftKeyAlias=${keystore.alias}`,
|
|
105
|
+
`-PdriftKeyPassword=${keystore.password}`,
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
const apk = join(projectRoot, 'app', 'build', 'outputs', 'apk', 'release', 'app-release.apk');
|
|
109
|
+
const delivered = join(outDir, `${manifest.name.replace(/[^\w.-]+/g, '-')}-${version}.apk`);
|
|
110
|
+
await cp(apk, delivered);
|
|
111
|
+
console.log(`[drift-package] android: ${delivered}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface Keystore {
|
|
115
|
+
readonly path: string;
|
|
116
|
+
readonly password: string;
|
|
117
|
+
readonly alias: string;
|
|
118
|
+
readonly describe: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A signing key: the one in the environment, or a throwaway generated once and kept.
|
|
123
|
+
*
|
|
124
|
+
* **Kept, and kept outside the project.** Android identifies an application by its signing key: a
|
|
125
|
+
* device refuses to upgrade an installed app whose new copy is signed with a different one, and
|
|
126
|
+
* the only way out on the device is to uninstall and lose the saves. So a generated key lives in
|
|
127
|
+
* the toolchain cache, keyed by application id — where `git clean` cannot reach it, where the
|
|
128
|
+
* build output being gitignored cannot lose it, and where two games on one machine do not share
|
|
129
|
+
* one identity.
|
|
130
|
+
*/
|
|
131
|
+
async function ensureKeystore(appId: string, javaHome: string): Promise<Keystore> {
|
|
132
|
+
const real = process.env.DRIFT_ANDROID_KEYSTORE;
|
|
133
|
+
if (typeof real === 'string' && real.length > 0) {
|
|
134
|
+
return {
|
|
135
|
+
path: real,
|
|
136
|
+
password: process.env.DRIFT_ANDROID_KEYSTORE_PASSWORD ?? '',
|
|
137
|
+
alias: process.env.DRIFT_ANDROID_KEY_ALIAS ?? 'drift',
|
|
138
|
+
describe: `with the keystore in DRIFT_ANDROID_KEYSTORE`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const dir = join(toolchainHome(), 'keystores');
|
|
143
|
+
const path = join(dir, `${appId}.jks`);
|
|
144
|
+
const keystore: Keystore = {
|
|
145
|
+
path,
|
|
146
|
+
password: 'driftengine',
|
|
147
|
+
alias: 'drift',
|
|
148
|
+
describe:
|
|
149
|
+
'with a generated throwaway key, kept in the toolchain cache — installable with developer ' +
|
|
150
|
+
'mode on, not publishable, and an upgrade signed with a different key is refused by the ' +
|
|
151
|
+
'device, which is why it is kept rather than made fresh',
|
|
152
|
+
};
|
|
153
|
+
try {
|
|
154
|
+
await readFile(path);
|
|
155
|
+
return keystore;
|
|
156
|
+
} catch {
|
|
157
|
+
/* Not there yet. */
|
|
158
|
+
}
|
|
159
|
+
await mkdir(dir, { recursive: true });
|
|
160
|
+
await runTool(
|
|
161
|
+
join(javaHome, 'bin', process.platform === 'win32' ? 'keytool.exe' : 'keytool'),
|
|
162
|
+
[
|
|
163
|
+
'-genkeypair',
|
|
164
|
+
'-keystore',
|
|
165
|
+
path,
|
|
166
|
+
'-alias',
|
|
167
|
+
keystore.alias,
|
|
168
|
+
'-storepass',
|
|
169
|
+
keystore.password,
|
|
170
|
+
'-keypass',
|
|
171
|
+
keystore.password,
|
|
172
|
+
'-keyalg',
|
|
173
|
+
'RSA',
|
|
174
|
+
'-keysize',
|
|
175
|
+
'2048',
|
|
176
|
+
/* Twenty-seven years, which is what Google asks of an upload key, and long enough that a
|
|
177
|
+
throwaway key is never the reason a build stops working. */
|
|
178
|
+
'-validity',
|
|
179
|
+
'10000',
|
|
180
|
+
'-dname',
|
|
181
|
+
'CN=DriftEngine, OU=Development, O=DriftEngine, C=GB',
|
|
182
|
+
],
|
|
183
|
+
{},
|
|
184
|
+
);
|
|
185
|
+
return keystore;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function consumerVersion(projectDir: string): Promise<string> {
|
|
189
|
+
try {
|
|
190
|
+
const raw: unknown = JSON.parse(await readFile(join(projectDir, 'package.json'), 'utf8'));
|
|
191
|
+
const version = (raw as { version?: unknown }).version;
|
|
192
|
+
return typeof version === 'string' && version.length > 0 ? version : '0.0.0';
|
|
193
|
+
} catch {
|
|
194
|
+
return '0.0.0';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function gradle(
|
|
199
|
+
toolchain: { gradle: string; javaHome: string; sdkRoot: string },
|
|
200
|
+
projectRoot: string,
|
|
201
|
+
args: readonly string[],
|
|
202
|
+
): Promise<void> {
|
|
203
|
+
await runTool(toolchain.gradle, ['--project-dir', projectRoot, '--no-daemon', ...args], {
|
|
204
|
+
JAVA_HOME: toolchain.javaHome,
|
|
205
|
+
ANDROID_HOME: toolchain.sdkRoot,
|
|
206
|
+
ANDROID_SDK_ROOT: toolchain.sdkRoot,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function runTool(
|
|
211
|
+
command: string,
|
|
212
|
+
args: readonly string[],
|
|
213
|
+
env: Readonly<Record<string, string>>,
|
|
214
|
+
): Promise<void> {
|
|
215
|
+
await new Promise<void>((done, fail) => {
|
|
216
|
+
const child = spawn(command, [...args], {
|
|
217
|
+
stdio: 'inherit',
|
|
218
|
+
env: { ...process.env, ...env },
|
|
219
|
+
});
|
|
220
|
+
child.on('error', fail);
|
|
221
|
+
child.on('close', (code) =>
|
|
222
|
+
code === 0 ? done() : fail(new Error(`${command} exited with ${String(code)}`)),
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Refuse a build whose entry page points at files that will not be there.
|
|
229
|
+
*
|
|
230
|
+
* **The check that would have saved a device round trip.** A game whose HTML references
|
|
231
|
+
* `/assets/index-abc.js` served from the wrong root loads its markup and none of its code, and
|
|
232
|
+
* nothing in the build says so — the APK is valid, installs, launches, and shows unstyled text.
|
|
233
|
+
* Here it is a build failure naming the first few references that do not resolve.
|
|
234
|
+
*/
|
|
235
|
+
async function checkAssetReferences(assetsRoot: string, entryName: string): Promise<void> {
|
|
236
|
+
const html = await readFile(join(assetsRoot, entryName), 'utf8');
|
|
237
|
+
const present = new Set(await listRelative(assetsRoot, assetsRoot));
|
|
238
|
+
const missing = missingReferences(html, present);
|
|
239
|
+
if (missing.length === 0) return;
|
|
240
|
+
throw new Error(
|
|
241
|
+
`the entry page references ${missing.length} file(s) that are not in the bundle: ` +
|
|
242
|
+
`${missing.slice(0, 5).join(', ')}. Those are absolute paths, so they resolve from the root ` +
|
|
243
|
+
'the game is served at — which is where the packager puts it. A game built with a `base` ' +
|
|
244
|
+
'other than `/` needs that base to match, or a relative one.',
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function listRelative(root: string, dir: string): Promise<string[]> {
|
|
249
|
+
const out: string[] = [];
|
|
250
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
251
|
+
const full = join(dir, entry.name);
|
|
252
|
+
if (entry.isDirectory()) out.push(...(await listRelative(root, full)));
|
|
253
|
+
else out.push(relative(root, full).split('\\').join('/'));
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|