@downdraft/cli 0.1.2-alpha.5 → 0.3.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@downdraft/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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.
|
|
15
|
+
"@downdraft/engine": "^0.3.0",
|
|
16
16
|
"eta": "^3.5.0",
|
|
17
17
|
"jimp": "^1.6.1",
|
|
18
18
|
"resedit": "3.1.0"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// engine-source.mjs — decides whether bundling resolves @downdraft/* via
|
|
2
|
+
// the "downdraft-source" export condition (TypeScript src/) or the default
|
|
3
|
+
// condition (pre-built dist/).
|
|
4
|
+
//
|
|
5
|
+
// Published packages ship dist/ so standalone consumers bundle fast-built
|
|
6
|
+
// ESM. In the engine monorepo (or any install where dist/ hasn't been
|
|
7
|
+
// generated) the default target doesn't exist — without this the packager
|
|
8
|
+
// fails with "Could not resolve: @downdraft/...". Mirrors `draft dev`'s
|
|
9
|
+
// rule: monorepo defaults to source, everything else defaults to dist,
|
|
10
|
+
// --engine-source / --engine-dist override.
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { createRequire } from "node:module";
|
|
13
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
export const SOURCE_CONDITION = "downdraft-source";
|
|
17
|
+
|
|
18
|
+
const monorepoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
|
|
19
|
+
|
|
20
|
+
function enginePkgRoot(fromFile) {
|
|
21
|
+
try {
|
|
22
|
+
const req = createRequire(resolve(fromFile));
|
|
23
|
+
return dirname(req.resolve("@downdraft/engine/package.json"));
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string[]} argv raw flag list (process.argv.slice(2))
|
|
31
|
+
* @param {string} fromFile file to anchor @downdraft/engine resolution at
|
|
32
|
+
* (typically the game entry — covers workspace links and standalone repos)
|
|
33
|
+
* @returns {string[] | undefined} Bun.build `conditions` override
|
|
34
|
+
*/
|
|
35
|
+
export function engineConditions(argv, fromFile) {
|
|
36
|
+
if (argv.includes("--engine-dist")) return undefined;
|
|
37
|
+
if (argv.includes("--engine-source")) return [SOURCE_CONDITION];
|
|
38
|
+
const pkg = enginePkgRoot(fromFile);
|
|
39
|
+
// Workspace-linked engine → always source (a stale dist/ would silently
|
|
40
|
+
// package old code). Standalone consumer → dist when it exists.
|
|
41
|
+
if (!pkg || pkg.startsWith(monorepoRoot + sep) || !existsSync(join(pkg, "dist"))) {
|
|
42
|
+
return [SOURCE_CONDITION];
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
import { tmpdir } from "node:os";
|
|
70
70
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
71
71
|
import { fileURLToPath } from "node:url";
|
|
72
|
+
import { engineConditions } from "./engine-source.mjs";
|
|
72
73
|
import { resolvePlatLibDir } from "./native-lib-fetch.mjs";
|
|
73
74
|
|
|
74
75
|
// ── Args ──
|
|
@@ -101,7 +102,7 @@ if (!["bun", "node", "deno"].includes(runtime)) {
|
|
|
101
102
|
const formats = (flagValue("format") ?? (Array.isArray(linuxCfg.target) ? linuxCfg.target.join(",") : linuxCfg.target) ?? "dir")
|
|
102
103
|
.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
103
104
|
const KNOWN_FORMATS = new Set(["dir", "deb", "appimage", "flatpak"]);
|
|
104
|
-
for (const f of formats) {
|
|
105
|
+
for (const f of formats.values()) {
|
|
105
106
|
if (!KNOWN_FORMATS.has(f)) {
|
|
106
107
|
console.error(`unknown --format "${f}" — expected dir, deb, appimage, or flatpak`);
|
|
107
108
|
process.exit(1);
|
|
@@ -209,7 +210,7 @@ function assetRel(absPath) {
|
|
|
209
210
|
function walkDir(dir, results = []) {
|
|
210
211
|
let entries;
|
|
211
212
|
try { entries = readdirSync(dir); } catch { return results; }
|
|
212
|
-
for (const e of entries) {
|
|
213
|
+
for (const e of entries.values()) {
|
|
213
214
|
const full = `${dir}/${e}`;
|
|
214
215
|
let st;
|
|
215
216
|
try { st = statSync(full); } catch { continue; }
|
|
@@ -462,6 +463,7 @@ const makeEmitPlugin = (collectWorkers) => ({
|
|
|
462
463
|
|
|
463
464
|
// ── Scan pass: discover Worker entrypoints to a fixpoint ──
|
|
464
465
|
|
|
466
|
+
const engineConds = engineConditions(argv, resolve(entry));
|
|
465
467
|
const scanOutdir = mkdtempSync(join(tmpdir(), "dd-scan-"));
|
|
466
468
|
let prevCount = -1;
|
|
467
469
|
while (workerEntries.size !== prevCount) {
|
|
@@ -469,6 +471,7 @@ while (workerEntries.size !== prevCount) {
|
|
|
469
471
|
const scan = await Bun.build({
|
|
470
472
|
entrypoints: [resolve(entry), ...workerEntries],
|
|
471
473
|
target: "node",
|
|
474
|
+
conditions: engineConds,
|
|
472
475
|
plugins: [makeEmitPlugin(true)],
|
|
473
476
|
splitting: false,
|
|
474
477
|
sourcemap: "none",
|
|
@@ -546,7 +549,7 @@ async function stageEngineLibs(nativeDir) {
|
|
|
546
549
|
join(engineRoot, pkgRel, "dist", file),
|
|
547
550
|
] : []),
|
|
548
551
|
];
|
|
549
|
-
for (const p of candidates) {
|
|
552
|
+
for (const p of candidates.values()) {
|
|
550
553
|
if (existsSync(p)) { copyFileSync(p, join(nativeDir, file)); break; }
|
|
551
554
|
}
|
|
552
555
|
}
|
|
@@ -592,6 +595,7 @@ if (runtime === "bun") {
|
|
|
592
595
|
entrypoints,
|
|
593
596
|
target: "node",
|
|
594
597
|
root: repoRoot,
|
|
598
|
+
conditions: engineConds,
|
|
595
599
|
naming: { entry: "[dir]/[name].mjs" },
|
|
596
600
|
plugins: [makeEmitPlugin(false)],
|
|
597
601
|
splitting: false,
|
|
@@ -669,7 +673,7 @@ stageGameNative(nativeDir);
|
|
|
669
673
|
let grew = true;
|
|
670
674
|
while (grew) {
|
|
671
675
|
grew = false;
|
|
672
|
-
for (const f of [...assetFiles]) {
|
|
676
|
+
for (const f of [...assetFiles].values()) {
|
|
673
677
|
if (!/\.(mjs|cjs|js)$/.test(f)) continue;
|
|
674
678
|
let src;
|
|
675
679
|
try { src = readFileSync(f, "utf-8"); } catch { continue; }
|
|
@@ -700,7 +704,7 @@ if (runtime !== "bun") {
|
|
|
700
704
|
}
|
|
701
705
|
|
|
702
706
|
// build.files — electron-builder-style extra files into the appdir root.
|
|
703
|
-
for (const f of Array.isArray(build.files) ? build.files : []) {
|
|
707
|
+
for (const f of (Array.isArray(build.files) ? build.files : []).values()) {
|
|
704
708
|
const src = resolve(gameDir, typeof f === "string" ? f : f.from);
|
|
705
709
|
const dest = join(appdir, typeof f === "string" ? basename(f) : (f.to ?? basename(f.from)));
|
|
706
710
|
if (!existsSync(src)) { console.warn(`[package-desktop] files entry missing: ${src}`); continue; }
|
|
@@ -742,7 +746,7 @@ async function iconSet(destDir) {
|
|
|
742
746
|
const { Jimp } = await import("jimp");
|
|
743
747
|
img = await Jimp.read(iconPath);
|
|
744
748
|
} catch { /* unreadable — verbatim fallback below */ }
|
|
745
|
-
for (const s of sizes) {
|
|
749
|
+
for (const s of sizes.values()) {
|
|
746
750
|
const dir = join(destDir, `${s}x${s}`, "apps");
|
|
747
751
|
mkdirSync(dir, { recursive: true });
|
|
748
752
|
const dest = join(dir, `${exeName}.png`);
|
|
@@ -934,4 +938,4 @@ async function fetchKoffiPrebuild(pkgName, ver) {
|
|
|
934
938
|
}
|
|
935
939
|
|
|
936
940
|
console.log(`[package-desktop] done (${runtime}):`);
|
|
937
|
-
for (const a of artifacts) console.log(` ${a}`);
|
|
941
|
+
for (const a of artifacts.values()) console.log(` ${a}`);
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
import { tmpdir } from "node:os";
|
|
40
40
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
41
41
|
import { fileURLToPath } from "node:url";
|
|
42
|
+
import { engineConditions } from "./engine-source.mjs";
|
|
42
43
|
|
|
43
44
|
// ── args ──
|
|
44
45
|
|
|
@@ -77,11 +78,12 @@ if (!entry || !outfileArg) {
|
|
|
77
78
|
const ABIS = { "arm64-v8a": "aarch64-linux-android", "x86_64": "x86_64-linux-android" };
|
|
78
79
|
const KOFFI_PKGS = { "arm64-v8a": "@koromix/koffi-android-arm64", "x86_64": "@koromix/koffi-android-x64" };
|
|
79
80
|
const abis = abiArg === "all" ? Object.keys(ABIS) : abiArg.split(",").map((s) => s.trim());
|
|
80
|
-
for (const a of abis) {
|
|
81
|
+
for (const a of abis.values()) {
|
|
81
82
|
if (!ABIS[a]) throw new Error(`unknown --abi "${a}" — expected: ${Object.keys(ABIS).join(", ")}, all`);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
const repoRoot = process.cwd();
|
|
86
|
+
const engineConds = engineConditions(argv, resolve(entry));
|
|
85
87
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
86
88
|
const monorepoRoot = resolve(scriptDir, "../../..");
|
|
87
89
|
const gameDir = dirname(dirname(resolve(entry)));
|
|
@@ -117,7 +119,7 @@ const btVer = readdirSync(btRoot).sort().pop();
|
|
|
117
119
|
const bt = join(btRoot, btVer);
|
|
118
120
|
const platformDir = readdirSync(join(sdkRoot, "platforms")).sort().pop();
|
|
119
121
|
const androidJar = join(sdkRoot, "platforms", platformDir, "android.jar");
|
|
120
|
-
for (const tool of ["aapt2", "zipalign", "apksigner"]) {
|
|
122
|
+
for (const tool of ["aapt2", "zipalign", "apksigner"].values()) {
|
|
121
123
|
if (!existsSync(join(bt, tool))) throw new Error(`missing build-tool ${tool} in ${bt}`);
|
|
122
124
|
}
|
|
123
125
|
const adb = join(sdkRoot, "platform-tools", "adb");
|
|
@@ -185,7 +187,7 @@ function assetRel(absPath) {
|
|
|
185
187
|
function walkDir(dir, results = []) {
|
|
186
188
|
let entries;
|
|
187
189
|
try { entries = readdirSync(dir); } catch { return results; }
|
|
188
|
-
for (const e of entries) {
|
|
190
|
+
for (const e of entries.values()) {
|
|
189
191
|
const full = `${dir}/${e}`;
|
|
190
192
|
let st;
|
|
191
193
|
try { st = statSync(full); } catch { continue; }
|
|
@@ -443,6 +445,7 @@ while (workerEntries.size !== prevCount) {
|
|
|
443
445
|
const scan = await Bun.build({
|
|
444
446
|
entrypoints: [resolve(entry), ...workerEntries],
|
|
445
447
|
target: "node",
|
|
448
|
+
conditions: engineConds,
|
|
446
449
|
plugins: [makePlugin(true)],
|
|
447
450
|
splitting: false,
|
|
448
451
|
sourcemap: "none",
|
|
@@ -466,6 +469,7 @@ const build = await Bun.build({
|
|
|
466
469
|
// No `compile` — libnode executes the extracted ESM files.
|
|
467
470
|
target: "node",
|
|
468
471
|
root: repoRoot,
|
|
472
|
+
conditions: engineConds,
|
|
469
473
|
naming: { entry: "[dir]/[name].mjs" },
|
|
470
474
|
plugins: [makePlugin(false)],
|
|
471
475
|
splitting: false,
|
|
@@ -500,7 +504,7 @@ if (!koffiRoot) {
|
|
|
500
504
|
const nm = join(bundleDir, "node_modules");
|
|
501
505
|
cpSync(koffiRoot, join(nm, "koffi"), { recursive: true });
|
|
502
506
|
const koffiVer = JSON.parse(readFileSync(join(koffiRoot, "package.json"), "utf-8")).version;
|
|
503
|
-
for (const abi of abis) {
|
|
507
|
+
for (const abi of abis.values()) {
|
|
504
508
|
const pkgName = KOFFI_PKGS[abi];
|
|
505
509
|
// The @koromix prebuild pkgs carry no importable entry — probe the dir
|
|
506
510
|
// beside koffi (same layout koffi's loader expects), else fetch npm.
|
|
@@ -544,7 +548,7 @@ async function fetchKoffiPrebuild(pkgName, ver) {
|
|
|
544
548
|
let grew = true;
|
|
545
549
|
while (grew) {
|
|
546
550
|
grew = false;
|
|
547
|
-
for (const f of [...assetFiles]) {
|
|
551
|
+
for (const f of [...assetFiles].values()) {
|
|
548
552
|
if (!/\.(mjs|cjs|js)$/.test(f)) continue;
|
|
549
553
|
let src;
|
|
550
554
|
try { src = readFileSync(f, "utf-8"); } catch { continue; }
|
|
@@ -573,7 +577,7 @@ for (const f of assetFiles.values()) {
|
|
|
573
577
|
// dd-env.txt — optional process env for the JS thread (debug probes like
|
|
574
578
|
// DD_BENCH_TEST; Android has no launch-env channel).
|
|
575
579
|
if (envPairs.length) {
|
|
576
|
-
for (const p of envPairs) {
|
|
580
|
+
for (const p of envPairs.values()) {
|
|
577
581
|
if (!/^[A-Za-z_][A-Za-z0-9_]*=/.test(p)) {
|
|
578
582
|
throw new Error(`--env "${p}" must be KEY=VALUE (KEY: [A-Za-z_][A-Za-z0-9_]*)`);
|
|
579
583
|
}
|
|
@@ -591,7 +595,7 @@ if (envPairs.length) {
|
|
|
591
595
|
// (.so siblings are handled per-ABI in the native-libs section below.)
|
|
592
596
|
{
|
|
593
597
|
let staged = 0;
|
|
594
|
-
for (const abi of abis) {
|
|
598
|
+
for (const abi of abis.values()) {
|
|
595
599
|
const platDir = join(gameDir, "native", "dist",
|
|
596
600
|
`android-${abi.startsWith("arm64") ? "arm64" : "x64"}`);
|
|
597
601
|
if (!existsSync(platDir)) continue;
|
|
@@ -661,7 +665,7 @@ const bundleText = walkDir(bundleDir)
|
|
|
661
665
|
.map((f) => readFileSync(f, "utf-8"))
|
|
662
666
|
.join("\n");
|
|
663
667
|
|
|
664
|
-
for (const abi of abis) {
|
|
668
|
+
for (const abi of abis.values()) {
|
|
665
669
|
const triple = ABIS[abi];
|
|
666
670
|
const libDir = join(nativeStage, abi);
|
|
667
671
|
mkdirSync(libDir, { recursive: true });
|
|
@@ -717,7 +721,7 @@ for (const abi of abis) {
|
|
|
717
721
|
// --libs= forces). JS resolves them by soname via the app lib dir.
|
|
718
722
|
const crates = await import(fileURLToPath(new URL("../../../scripts/native-crates.mjs", import.meta.url)));
|
|
719
723
|
const nodePlat = `android-${abi.startsWith("arm64") ? "arm64" : "x64"}`;
|
|
720
|
-
for (const crate of crates.CRATES) {
|
|
724
|
+
for (const crate of crates.CRATES.values()) {
|
|
721
725
|
if (crate.androidOnly || crate.noAndroid) continue;
|
|
722
726
|
const name = crate.lib.replace(/^downdraft_/, "");
|
|
723
727
|
if (excludeLibs.includes(name) || excludeLibs.includes(crate.lib)) continue;
|
|
@@ -46,6 +46,7 @@ import { createRequire } from "node:module";
|
|
|
46
46
|
import { tmpdir } from "node:os";
|
|
47
47
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
48
48
|
import { fileURLToPath } from "node:url";
|
|
49
|
+
import { engineConditions } from "./engine-source.mjs";
|
|
49
50
|
import { resolvePlatLibDir } from "./native-lib-fetch.mjs";
|
|
50
51
|
|
|
51
52
|
// CRATES is shared with the repo's scripts/native-crates.mjs when this file
|
|
@@ -101,6 +102,7 @@ let outfile = outfileArg;
|
|
|
101
102
|
if (target.exe && !outfile.endsWith(".exe")) outfile += ".exe";
|
|
102
103
|
|
|
103
104
|
const repoRoot = process.cwd();
|
|
105
|
+
const engineConds = engineConditions(argv, resolve(entry));
|
|
104
106
|
const QUERY_RE = /\?(raw|url|json)$/;
|
|
105
107
|
const WORKER_RE = /new\s+Worker\(\s*new\s+URL\(\s*["'`]([^"'`]+)["'`]\s*,\s*import\.meta\.url\s*\)/g;
|
|
106
108
|
const ASSET_URL_RE = /new\s+URL\(\s*["'`]([^"'`]+)["'`]\s*,\s*import\.meta\.url\s*\)/g;
|
|
@@ -398,6 +400,7 @@ while (workerEntries.size !== prevCount) {
|
|
|
398
400
|
const scan = await Bun.build({
|
|
399
401
|
entrypoints: [resolve(entry), ...workerEntries],
|
|
400
402
|
target: "bun",
|
|
403
|
+
conditions: engineConds,
|
|
401
404
|
plugins: [makePlugin(true)],
|
|
402
405
|
splitting: false,
|
|
403
406
|
sourcemap: "none",
|
|
@@ -420,6 +423,7 @@ const result = await Bun.build({
|
|
|
420
423
|
entrypoints,
|
|
421
424
|
compile: { outfile: resolve(outfile), ...(target.bun ? { target: target.bun } : {}) },
|
|
422
425
|
target: "bun",
|
|
426
|
+
conditions: engineConds,
|
|
423
427
|
plugins: [makePlugin(false)],
|
|
424
428
|
splitting: false,
|
|
425
429
|
sourcemap: "none",
|
package/src/release.ts
CHANGED
|
@@ -235,7 +235,7 @@ function packageNative(
|
|
|
235
235
|
const requested = opts.runtime ?? info.build.runtime
|
|
236
236
|
?? (readGameConfig(gameDir).runtime as string | undefined) ?? "bun";
|
|
237
237
|
const runtimes = requested === "all" ? RUNTIMES : [requested];
|
|
238
|
-
for (const r of runtimes) {
|
|
238
|
+
for (const r of runtimes.values()) {
|
|
239
239
|
if (!RUNTIMES.includes(r)) {
|
|
240
240
|
log.error("release:package", `Unknown runtime "${r}" — expected bun, node, deno, or all.`);
|
|
241
241
|
return false;
|
|
@@ -250,7 +250,7 @@ function packageNative(
|
|
|
250
250
|
|
|
251
251
|
let ok = true;
|
|
252
252
|
targets.forEach((target) => {
|
|
253
|
-
for (const runtime of runtimes) {
|
|
253
|
+
for (const runtime of runtimes.values()) {
|
|
254
254
|
if (target === "linux") {
|
|
255
255
|
// Linux: package-desktop.mjs owns the format matrix (dir, deb,
|
|
256
256
|
// appimage, flatpak). Formats come from --format → build.linux.target
|