@downdraft/cli 0.4.4 → 0.4.7
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/package.json +2 -2
- package/scripts/package-mobile.mjs +130 -4
- package/src/dev.ts +8 -0
- package/src/release.ts +5 -0
- package/src/usage.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@downdraft/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/knackstedt/downdraft.git",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@aws-sdk/client-s3": "^3.700.0",
|
|
15
|
-
"@downdraft/engine": "^0.4.
|
|
15
|
+
"@downdraft/engine": "^0.4.7",
|
|
16
16
|
"eta": "^3.5.0",
|
|
17
17
|
"jimp": "^1.6.1",
|
|
18
18
|
"resedit": "3.1.0"
|
|
@@ -59,7 +59,7 @@ const modeDefines = {
|
|
|
59
59
|
"import.meta.env.MODE": JSON.stringify(modeArg),
|
|
60
60
|
...(retainMcp ? {} : { __DD_MCP_STRIP__: "true" }),
|
|
61
61
|
};
|
|
62
|
-
const
|
|
62
|
+
const minSdkFlag = flagValue("min-sdk") ?? "30";
|
|
63
63
|
// --node-flavor=lite selects build/dist/android-lite/<abi>/libnode.so
|
|
64
64
|
// (NODEJS_MOBILE_FLAVOR=lite from build-android.mjs: no intl/inspector/
|
|
65
65
|
// sqlite/amaro — ~20MB smaller binary, slightly lower PSS).
|
|
@@ -73,6 +73,23 @@ const excludeLibs = (flagValue("exclude-libs") ?? "").split(",").map((s) => s.tr
|
|
|
73
73
|
// Packaged .so's are stripped by default (debug info never maps — pure APK
|
|
74
74
|
// size). --no-strip keeps symbols for on-device native debugging.
|
|
75
75
|
const doStrip = !argv.includes("--no-strip");
|
|
76
|
+
// --webui=wpe stages the heavyweight web-UI backend: libdowndraft_webui_wpe
|
|
77
|
+
// (via the crate loop below) PLUS the vendored wpe-android AAR runtime —
|
|
78
|
+
// its jniLibs, dex'd WKRuntime bootstrap classes, WPE process service
|
|
79
|
+
// declarations, and AssetManager payload (gio modules, injected bundles).
|
|
80
|
+
const webuiBackend = flagValue("webui") ?? "none";
|
|
81
|
+
if (!["none", "wpe", "auto"].includes(webuiBackend)) {
|
|
82
|
+
console.error(`[package-mobile] --webui=${webuiBackend} unsupported on android (wpe|none|auto)`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const wpeAndroid = webuiBackend !== "none";
|
|
86
|
+
// Debug/bisect override: replace the Application class the manifest names
|
|
87
|
+
// (e.g. --wpe-app-class=com.downdraft.stub.App to keep WPE packaged but
|
|
88
|
+
// inert until native code loads it). Anything other than the default
|
|
89
|
+
// compiles a generated no-op Application into the dex.
|
|
90
|
+
const wpeAppClass = flagValue("wpe-app-class") ?? "org.wpewebkit.WPEApplication";
|
|
91
|
+
// The wpe-android AAR declares minSdk 31 — clamp up when it's staged.
|
|
92
|
+
const minSdk = wpeAndroid && Number(minSdkFlag) < 31 ? "31" : minSdkFlag;
|
|
76
93
|
// --env KEY=VALUE (repeatable) → dd-env.txt in the bundle; the shell
|
|
77
94
|
// setenv()s each before node::Start so process.env sees them.
|
|
78
95
|
const envPairs = argv.filter((a) => a.startsWith("--env=")).map((a) => a.slice(6));
|
|
@@ -709,6 +726,22 @@ for (const abi of abis.values()) {
|
|
|
709
726
|
throw new Error(`libnode.so for ${abi} not found — build via packages/node-mobile/scripts/build-android.mjs`);
|
|
710
727
|
}
|
|
711
728
|
|
|
729
|
+
// WPE runtime — the whole AAR jni/<abi>/ tree (libWPEWebKit-2.0, libwpe,
|
|
730
|
+
// libWPEBackend-android, glib/soup/gstreamer, libc++_shared, …) rides in
|
|
731
|
+
// jniLibs so every soname resolves from the app's native lib dir.
|
|
732
|
+
if (wpeAndroid) {
|
|
733
|
+
const wpeJni = join(monorepoRoot,
|
|
734
|
+
"packages/engine/libraries/webui/vendor/wpe-android/jni", abi);
|
|
735
|
+
if (!existsSync(wpeJni)) {
|
|
736
|
+
throw new Error(`wpe-android runtime missing at ${wpeJni} — run ` +
|
|
737
|
+
`packages/engine/libraries/webui/scripts/fetch-wpe-android.mjs`);
|
|
738
|
+
}
|
|
739
|
+
for (const f of readdirSync(wpeJni).filter((f) => f.endsWith(".so"))) {
|
|
740
|
+
copyFileSync(join(wpeJni, f), join(libDir, f));
|
|
741
|
+
}
|
|
742
|
+
console.log(`[package-mobile] staged wpe-android runtime → lib/${abi}/`);
|
|
743
|
+
}
|
|
744
|
+
|
|
712
745
|
// Ensure the engine crates are built for this target.
|
|
713
746
|
run("node", [join(monorepoRoot, "scripts/build-native.mjs"), `--target=${triple}`, "--profile=release"], {
|
|
714
747
|
cwd: monorepoRoot,
|
|
@@ -783,6 +816,86 @@ for (const abi of abis.values()) {
|
|
|
783
816
|
|
|
784
817
|
// ── APK assembly ──
|
|
785
818
|
|
|
819
|
+
// WPE Java runtime — dex the AAR's classes.jar (WKRuntime bootstrap,
|
|
820
|
+
// WPEServices, the WPEApplication class the manifest names) and stage its
|
|
821
|
+
// AssetManager payload (gio modules, fontconfig, injected-bundles —
|
|
822
|
+
// WKRuntime.initialize copies them to filesDir at first run).
|
|
823
|
+
let wpeServicesXml = "";
|
|
824
|
+
const wpeResZip = join(stageRoot, "wpe-res.zip");
|
|
825
|
+
if (wpeAndroid) {
|
|
826
|
+
const vendor = join(monorepoRoot, "packages/engine/libraries/webui/vendor/wpe-android");
|
|
827
|
+
if (!existsSync(join(vendor, "classes.jar"))) {
|
|
828
|
+
throw new Error(`wpe-android classes.jar missing at ${vendor} — run ` +
|
|
829
|
+
`packages/engine/libraries/webui/scripts/fetch-wpe-android.mjs`);
|
|
830
|
+
}
|
|
831
|
+
const d8 = join(bt, "d8");
|
|
832
|
+
if (!existsSync(d8)) throw new Error(`missing build-tool d8 in ${bt}`);
|
|
833
|
+
const dexInputs = [join(vendor, "classes.jar")];
|
|
834
|
+
// Java support classes compiled alongside the AAR dex:
|
|
835
|
+
// - WpeAppComponentFactory: libWPEAndroidCommon.so embeds a full static
|
|
836
|
+
// libc++ and exports all 915 of its symbols. WPEApplication's static
|
|
837
|
+
// init System.loadLibrary("WPEAndroidRuntime") pulls it into the
|
|
838
|
+
// classloader namespace's global group BEFORE libc++_shared.so ever
|
|
839
|
+
// loads, so libnode's undefined libc++ symbols (operator new/delete,
|
|
840
|
+
// exception machinery, std::*) bind to the wrong copy and node::Start
|
|
841
|
+
// dies on a C++ exception ~ms in. An AppComponentFactory is the one
|
|
842
|
+
// hook that runs before the Application class initializes — its
|
|
843
|
+
// instantiateApplication preloads libc++_shared so it wins the
|
|
844
|
+
// global-group lookup in every app process.
|
|
845
|
+
// - optional stub Application (--wpe-app-class): keeps WPE packaged
|
|
846
|
+
// but inert for bisecting (DD_LIB_PRELOAD in the shell then drives
|
|
847
|
+
// the load order manually).
|
|
848
|
+
const javaSrcRoot = join(stageRoot, "dd-java-src");
|
|
849
|
+
const javaSrcFiles = [];
|
|
850
|
+
const writeJava = (fqcn, body) => {
|
|
851
|
+
const pkg = fqcn.slice(0, fqcn.lastIndexOf("."));
|
|
852
|
+
const cls = fqcn.slice(fqcn.lastIndexOf(".") + 1);
|
|
853
|
+
const dir = join(javaSrcRoot, ...pkg.split("."));
|
|
854
|
+
mkdirSync(dir, { recursive: true });
|
|
855
|
+
writeFileSync(join(dir, `${cls}.java`), body);
|
|
856
|
+
javaSrcFiles.push(join(dir, `${cls}.java`));
|
|
857
|
+
};
|
|
858
|
+
writeJava("org.downdraft.webui.WpeAppComponentFactory",
|
|
859
|
+
`package org.downdraft.webui;
|
|
860
|
+
public class WpeAppComponentFactory extends android.app.AppComponentFactory {
|
|
861
|
+
@Override
|
|
862
|
+
public android.app.Application instantiateApplication(
|
|
863
|
+
ClassLoader cl, String className)
|
|
864
|
+
throws InstantiationException, IllegalAccessException,
|
|
865
|
+
ClassNotFoundException {
|
|
866
|
+
System.loadLibrary("c++_shared");
|
|
867
|
+
return super.instantiateApplication(cl, className);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
`);
|
|
871
|
+
if (wpeAppClass !== "org.wpewebkit.WPEApplication") {
|
|
872
|
+
writeJava(wpeAppClass,
|
|
873
|
+
`package ${wpeAppClass.slice(0, wpeAppClass.lastIndexOf("."))};\n` +
|
|
874
|
+
`public class ${wpeAppClass.slice(wpeAppClass.lastIndexOf(".") + 1)} ` +
|
|
875
|
+
`extends android.app.Application {}\n`);
|
|
876
|
+
console.log(`[package-mobile] wpe-android: stub application class ${wpeAppClass}`);
|
|
877
|
+
}
|
|
878
|
+
const javaOut = join(stageRoot, "dd-java-classes");
|
|
879
|
+
run("javac", ["-cp", androidJar, "-d", javaOut, ...javaSrcFiles]);
|
|
880
|
+
run("jar", ["cf", join(stageRoot, "dd-java.jar"), "-C", javaOut, "."]);
|
|
881
|
+
dexInputs.push(join(stageRoot, "dd-java.jar"));
|
|
882
|
+
run(d8, ["--release", "--min-api", minSdk, "--lib", androidJar,
|
|
883
|
+
"--output", stageRoot, ...dexInputs]);
|
|
884
|
+
cpSync(join(vendor, "assets"), join(stageRoot, "assets"), { recursive: true });
|
|
885
|
+
// WPEServices WebProcess/NetworkProcess slot declarations, verbatim from
|
|
886
|
+
// the AAR manifest — the service subprocesses are how WPEWebKit isolates
|
|
887
|
+
// its render/network work on Android.
|
|
888
|
+
const aarManifest = readFileSync(join(vendor, "AndroidManifest.xml"), "utf-8");
|
|
889
|
+
wpeServicesXml = aarManifest.match(/<service\b[\s\S]*?\/>/g)?.join("\n") ?? "";
|
|
890
|
+
// AAR res/ — R references in classes.jar resolve through resources.arsc.
|
|
891
|
+
const resDir = join(vendor, "res");
|
|
892
|
+
if (existsSync(resDir) && readdirSync(resDir).length) {
|
|
893
|
+
run(join(bt, "aapt2"), ["compile", "--dir", resDir, "-o", wpeResZip]);
|
|
894
|
+
}
|
|
895
|
+
console.log(`[package-mobile] wpe-android: dex'd classes.jar, staged ` +
|
|
896
|
+
`runtime assets + ${wpeServicesXml.split("<service").length - 1} service slots`);
|
|
897
|
+
}
|
|
898
|
+
|
|
786
899
|
const manifestTpl = readFileSync(join(monorepoRoot, "packages/android-shell/AndroidManifest.xml"), "utf-8");
|
|
787
900
|
const manifest = manifestTpl
|
|
788
901
|
.replaceAll("@APP_ID@", appId)
|
|
@@ -790,7 +903,17 @@ const manifest = manifestTpl
|
|
|
790
903
|
.replaceAll("@VERSION_CODE@", String(versionCode))
|
|
791
904
|
.replaceAll("@VERSION_NAME@", versionName.replaceAll("&", "&").replaceAll("<", "<"))
|
|
792
905
|
.replaceAll("@MIN_SDK@", minSdk)
|
|
793
|
-
.replaceAll("@DEBUGGABLE@", modeArg === "dev" ? "true" : "false")
|
|
906
|
+
.replaceAll("@DEBUGGABLE@", modeArg === "dev" ? "true" : "false")
|
|
907
|
+
.replaceAll("@HAS_CODE@", wpeAndroid ? "true" : "false")
|
|
908
|
+
// WPEApplication's static init detects process kind (main/web/network)
|
|
909
|
+
// and loads the right per-process libs — required for service spawning.
|
|
910
|
+
.replaceAll("@APPLICATION_NAME_ATTR@",
|
|
911
|
+
wpeAndroid ? `android:name="${wpeAppClass}"` : "")
|
|
912
|
+
// Loads libc++_shared before WPEApplication's static init can pull in
|
|
913
|
+
// libWPEAndroidCommon's embedded-libc++ exports (see dd-java comment).
|
|
914
|
+
.replaceAll("@APP_COMPONENT_FACTORY_ATTR@",
|
|
915
|
+
wpeAndroid ? 'android:appComponentFactory="org.downdraft.webui.WpeAppComponentFactory"' : "")
|
|
916
|
+
.replaceAll("@EXTRA_APPLICATION_XML@", wpeServicesXml);
|
|
794
917
|
writeFileSync(join(stageRoot, "AndroidManifest.xml"), manifest);
|
|
795
918
|
|
|
796
919
|
const unsigned = join(stageRoot, "unsigned.apk");
|
|
@@ -802,12 +925,15 @@ run(join(bt, "aapt2"), [
|
|
|
802
925
|
"-I", androidJar,
|
|
803
926
|
"--manifest", join(stageRoot, "AndroidManifest.xml"),
|
|
804
927
|
"-A", join(stageRoot, "assets"),
|
|
928
|
+
...(existsSync(wpeResZip) ? ["-R", wpeResZip] : []),
|
|
805
929
|
"--auto-add-overlay",
|
|
806
930
|
]);
|
|
807
931
|
|
|
808
932
|
// jniLibs → lib/<abi>/*.so inside the APK (extractNativeLibs=true allows
|
|
809
|
-
// compressed storage — plain zip is correct here).
|
|
810
|
-
|
|
933
|
+
// compressed storage — plain zip is correct here). classes.dex goes in as
|
|
934
|
+
// a raw root entry when the WPE backend ships its Java bootstrap.
|
|
935
|
+
const zipEntries = ["lib", ...(wpeAndroid ? ["classes.dex"] : [])];
|
|
936
|
+
run("zip", ["-qr", unsigned, ...zipEntries], { cwd: stageRoot });
|
|
811
937
|
run(join(bt, "zipalign"), ["-f", "-p", "4", unsigned, aligned]);
|
|
812
938
|
|
|
813
939
|
// Sign with the standard Android debug key unless a keystore is provided.
|
package/src/dev.ts
CHANGED
|
@@ -166,6 +166,14 @@ async function devNative(parsed: any): Promise<void> {
|
|
|
166
166
|
process.exit(1);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
// WebUI backend: --webui flag → DD_WEBUI_BACKEND → config webui.backend.
|
|
170
|
+
// Forwarded into the shell env; the host resolves final precedence
|
|
171
|
+
// (createWebUi opts still outrank it) inside libraries/webui.
|
|
172
|
+
const webuiBackend = (parsed.flags.webui as string | undefined)
|
|
173
|
+
|| process.env.DD_WEBUI_BACKEND
|
|
174
|
+
|| ((readGameConfig(gameDir).webui as { backend?: string } | undefined)?.backend);
|
|
175
|
+
if (webuiBackend) env.DD_WEBUI_BACKEND = webuiBackend;
|
|
176
|
+
|
|
169
177
|
env.DD_GAME_DIR = gameDir;
|
|
170
178
|
env.DD_ENTRY = nativeEntry;
|
|
171
179
|
env.DD_RUNTIME = runtime;
|
package/src/release.ts
CHANGED
|
@@ -58,6 +58,7 @@ interface ReleaseArgs {
|
|
|
58
58
|
verbose: boolean;
|
|
59
59
|
runtime: string | null;
|
|
60
60
|
format: string | null;
|
|
61
|
+
webui: string | null;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
// ---------------------------------------------------------------------------
|
|
@@ -103,6 +104,7 @@ function parseReleaseArgs(args: string[]): ReleaseArgs {
|
|
|
103
104
|
verbose: parsed.flags.verbose as boolean,
|
|
104
105
|
runtime: (parsed.flags.runtime as string) || null,
|
|
105
106
|
format: (parsed.flags.format as string) || null,
|
|
107
|
+
webui: (parsed.flags.webui as string) || null,
|
|
106
108
|
};
|
|
107
109
|
}
|
|
108
110
|
|
|
@@ -271,6 +273,7 @@ function packageNative(
|
|
|
271
273
|
`--product-name=${info.productName}`,
|
|
272
274
|
`--product-version=${info.version}`,
|
|
273
275
|
`--app-id=${info.appId}`,
|
|
276
|
+
...(opts.webui ? [`--webui=${opts.webui}`] : []),
|
|
274
277
|
...(opts.retainMcp ? ["--mcp"] : []),
|
|
275
278
|
entry,
|
|
276
279
|
resolve(opts.out),
|
|
@@ -294,6 +297,7 @@ function packageNative(
|
|
|
294
297
|
`--mode=${opts.mode}`,
|
|
295
298
|
`--product-name=${info.productName}`,
|
|
296
299
|
`--product-version=${info.version}`,
|
|
300
|
+
...(opts.webui ? [`--webui=${opts.webui}`] : []),
|
|
297
301
|
...(opts.retainMcp ? ["--mcp"] : []),
|
|
298
302
|
entry,
|
|
299
303
|
outfile,
|
|
@@ -348,6 +352,7 @@ function packageAndroid(
|
|
|
348
352
|
...(opts.libs ? [`--libs=${opts.libs}`] : []),
|
|
349
353
|
...(opts.excludeLibs ? [`--exclude-libs=${opts.excludeLibs}`] : []),
|
|
350
354
|
...(opts.noStrip ? ["--no-strip"] : []),
|
|
355
|
+
...(opts.webui ? [`--webui=${opts.webui}`] : []),
|
|
351
356
|
...(opts.retainMcp ? ["--mcp"] : []),
|
|
352
357
|
entry,
|
|
353
358
|
outfile,
|
package/src/usage.ts
CHANGED
|
@@ -60,6 +60,7 @@ export const COMMANDS: CommandEntry[] = [
|
|
|
60
60
|
{ name: "skip-build", type: "boolean", description: "Alias for --stage=package" },
|
|
61
61
|
{ name: "build-only", type: "boolean", description: "Alias for --stage=build" },
|
|
62
62
|
{ name: "mcp", type: "boolean", description: "Retain the MCP automation endpoint in the packaged binary" },
|
|
63
|
+
{ name: "webui", type: "string", enum: ["cef", "wpe", "none", "auto"], description: "Heavyweight web UI backend to stage: cef (Chromium — desktop default), wpe (embedded WebKit — linux/android), none, or auto (default: webui.backend in downdraft.config.json, else auto)" },
|
|
63
64
|
{ name: "verbose", alias: "v", type: "boolean", description: "Verbose logging" },
|
|
64
65
|
],
|
|
65
66
|
},
|
|
@@ -97,6 +98,7 @@ export const COMMANDS: CommandEntry[] = [
|
|
|
97
98
|
{ name: "engine-dist", type: "boolean", description: "Resolve engine packages from their pre-built dist/ tree (default outside the monorepo; externalized = near-zero transform cost, no engine-file HMR)" },
|
|
98
99
|
{ name: "native", type: "boolean", description: "Back-compat alias — native is the default runtime" },
|
|
99
100
|
{ name: "native-debug", type: "boolean", description: "Load debug native libraries (built via build-native.mjs --debug) — sets DD_NATIVE_PROFILE=debug for the spawned runtime" },
|
|
101
|
+
{ name: "webui", type: "string", enum: ["cef", "wpe", "none", "auto"], description: "Heavyweight web UI backend: cef (Chromium — desktop default), wpe (embedded WebKit — linux dev/android), none, or auto (default: DD_WEBUI_BACKEND, webui.backend in downdraft.config.json, else auto)" },
|
|
100
102
|
{ name: "verbose", alias: "v", type: "boolean", description: "Verbose logging" },
|
|
101
103
|
],
|
|
102
104
|
},
|