@downdraft/cli 0.1.2-alpha.5 → 0.2.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.1.2-alpha.5",
3
+ "version": "0.2.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.1.2-alpha.5",
15
+ "@downdraft/engine": "^0.2.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