@akanjs/cli 3.0.0-beta.27 → 3.0.0-beta.29

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.
Files changed (63) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-h19atbr3.js → CodeAgent-86q8d13j.js} +2 -2
  3. package/{CodeTui-46zygq8p.js → CodeTui-r8tgxsyb.js} +1 -1
  4. package/{agent.command-rp505zxa.js → agent.command-q9bmqrag.js} +5 -5
  5. package/akanCode.js +2 -2
  6. package/{application.command-1p0cpt6y.js → application.command-712jd2z8.js} +70 -8
  7. package/{applicationBuildRunner-r6b9rkb8.js → applicationBuildRunner-ez37p161.js} +67 -22
  8. package/buildBatch.proc.js +3 -3
  9. package/{cloud.command-ps9dabbg.js → cloud.command-8eegwcsc.js} +9 -9
  10. package/cloud.script-55s1cmz9.js +20 -0
  11. package/{code.command-78qw3q3f.js → code.command-n0c2tyzx.js} +6 -6
  12. package/commandManifest.json +1 -1
  13. package/{context.command-x5f7t3gk.js → context.command-vxf6d7q2.js} +13 -13
  14. package/context.runner-70r2fmty.js +21 -0
  15. package/{guideline.command-d5qmxx29.js → guideline.command-ene55fkr.js} +2 -2
  16. package/guidelines/conventions/conventions.instruction.md +27 -7
  17. package/guidelines/runtimeRule/runtimeRule.instruction.md +116 -1
  18. package/incrementalBuilder.proc.js +2 -2
  19. package/{index-hzy4pe24.js → index-0pqzrbpr.js} +2 -2
  20. package/{index-w5pet0nq.js → index-16k7m3cs.js} +11 -9
  21. package/index-1hs6jrys.js +994 -0
  22. package/{index-6dt6zft6.js → index-20r2g1t3.js} +4 -4
  23. package/{index-xfgdmm5d.js → index-2ctqegdm.js} +2 -2
  24. package/{index-d65fzxvd.js → index-2s2e5qnd.js} +1 -1
  25. package/{index-j9z8vg2n.js → index-2yvy9ztj.js} +4 -4
  26. package/{index-t02g6dpn.js → index-2z30v4k5.js} +3 -3
  27. package/{index-vxqe24mp.js → index-8ej6r02m.js} +1 -1
  28. package/{index-58cyna2s.js → index-b68zfsvz.js} +1 -1
  29. package/{index-bp9b1g2r.js → index-fy42hyb9.js} +7 -3
  30. package/{index-tg09x14f.js → index-g44da4r2.js} +5 -5
  31. package/{index-zazvn45w.js → index-genegfnt.js} +4 -4
  32. package/{index-da72pfdv.js → index-h9ed99ax.js} +7 -7
  33. package/{index-per2kqy9.js → index-k7809wmg.js} +2 -2
  34. package/{index-2zcm7fjd.js → index-krwz9ek9.js} +3 -3
  35. package/{index-118vq9dh.js → index-n9gxx7bp.js} +247 -47
  36. package/{index-860zjnr1.js → index-nbg9bfnr.js} +1 -1
  37. package/{index-5mm3g24t.js → index-qtz88kh0.js} +2 -2
  38. package/{index-0n9q1f23.js → index-r8gp8zk3.js} +231 -98
  39. package/{index-ggfhz9ab.js → index-rbeahw6j.js} +3 -3
  40. package/{index-56xwsgyn.js → index-rrt9pnb4.js} +2 -2
  41. package/{index-v4hnkqz9.js → index-ss828r32.js} +1 -1
  42. package/{index-5kjsr2pg.js → index-ttn00pr3.js} +38 -3
  43. package/index.js +21 -21
  44. package/{library.command-sv6g1h2g.js → library.command-tz7vnwea.js} +3 -3
  45. package/{localRegistry.command-zppa6w7q.js → localRegistry.command-7dd85czz.js} +8 -8
  46. package/{module.command-ccdeder9.js → module.command-qhc9ns0s.js} +5 -5
  47. package/{package.command-xmw605b1.js → package.command-z3epnvas.js} +3 -3
  48. package/package.json +3 -3
  49. package/{page.command-ky7vafsn.js → page.command-exd2vakz.js} +3 -3
  50. package/{primitive.command-bh6j8nfs.js → primitive.command-0w6cha4j.js} +6 -6
  51. package/{quality.command-rqqrpe98.js → quality.command-je4kc9yv.js} +2 -2
  52. package/{repair.command-n9nsm9qe.js → repair.command-knvwfgtg.js} +5 -5
  53. package/{scalar.command-t09gmck8.js → scalar.command-60nxfchc.js} +4 -4
  54. package/{subspace.command-gx2cjpnv.js → subspace.command-683qxcw9.js} +4 -4
  55. package/templates/server.ts +1 -1
  56. package/{tunnel.command-4g95vhsn.js → tunnel.command-7m2kxtat.js} +2 -2
  57. package/{workflow.command-b91ph8sx.js → workflow.command-j5tk13ds.js} +10 -10
  58. package/{workspace.command-1r29qzzk.js → workspace.command-jxdm0wrb.js} +20 -20
  59. package/README.ko.md +0 -72
  60. package/README.md +0 -88
  61. package/cloud.script-wrw1kz6k.js +0 -20
  62. package/context.runner-nfs8ksh3.js +0 -21
  63. package/index-y736f42m.js +0 -556
@@ -37,27 +37,29 @@ import {
37
37
  import {
38
38
  runner,
39
39
  script
40
- } from "./index-58cyna2s.js";
40
+ } from "./index-b68zfsvz.js";
41
41
  import {
42
42
  WorkspaceExecutor,
43
43
  AppExecutor,
44
44
  LibExecutor,
45
45
  PkgExecutor
46
- } from "./index-0n9q1f23.js";
47
- import {
48
- LibraryScript
49
- } from "./index-5mm3g24t.js";
46
+ } from "./index-r8gp8zk3.js";
50
47
  import {
48
+ DesktopServerStage,
51
49
  resolveMobileTargets,
50
+ resolveAppId,
52
51
  NativeApp
53
- } from "./index-y736f42m.js";
52
+ } from "./index-1hs6jrys.js";
53
+ import {
54
+ LibraryScript
55
+ } from "./index-qtz88kh0.js";
54
56
  import {
55
57
  stripAnsi
56
58
  } from "./index-7md2g9vw.js";
57
59
  import {
58
60
  SlicePlanner,
59
61
  formatSlicePlan
60
- } from "./index-860zjnr1.js";
62
+ } from "./index-nbg9bfnr.js";
61
63
  import {
62
64
  openBrowser
63
65
  } from "./index-9dp031by.js";
@@ -67,6 +69,7 @@ import { confirm } from "@inquirer/prompts";
67
69
  import { Logger as Logger6 } from "akanjs/common";
68
70
 
69
71
  // pkgs/@akanjs/cli/application/application.runner.ts
72
+ import { realpathSync } from "fs";
70
73
  import path5 from "path";
71
74
 
72
75
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
@@ -725,6 +728,7 @@ class AkanAppHost {
725
728
  #backendGaveUp = false;
726
729
  #backendLifecycleState = "stopped";
727
730
  #pendingRestartReason = null;
731
+ #backendRestart = null;
728
732
  #pendingRecycle = null;
729
733
  #restartFailedAfterApply = null;
730
734
  #stopping = false;
@@ -796,7 +800,9 @@ class AkanAppHost {
796
800
  this.#cancelIdleSuspend();
797
801
  this.#stopIdleWatcher();
798
802
  this.#clearRestartTimers();
803
+ this.#pendingRestartReason = null;
799
804
  this.#failPendingBuilderMessages("dev server is shutting down");
805
+ await this.#backendRestart;
800
806
  await this.#stopBackend();
801
807
  this.#stopBuilder();
802
808
  return this;
@@ -817,6 +823,12 @@ class AkanAppHost {
817
823
  return await createTunnel(type, { app: this.app, environment });
818
824
  }
819
825
  #startBackend(startStatus = null) {
826
+ if (this.#stopping)
827
+ return;
828
+ if (this.#backend) {
829
+ this.logger.warn(`backend pid=${this.#backend.pid} is still running; not starting a second one`);
830
+ return;
831
+ }
820
832
  this.#builderRequests.startGeneration();
821
833
  this.#discardPendingBuilderMessages("the backend restarted while the builder was away");
822
834
  this.#backendStartStatus = startStatus;
@@ -849,6 +861,8 @@ class AkanAppHost {
849
861
  message: msg.data.message
850
862
  });
851
863
  this.#sendOrQueueBuildStatus(status);
864
+ if (!status.ok)
865
+ this.#emitDevEvent("failed", status.message);
852
866
  return;
853
867
  }
854
868
  if (backendMsgTypeSet.has(msg.type))
@@ -997,11 +1011,17 @@ class AkanAppHost {
997
1011
  clearTimeout(this.#restartTimer);
998
1012
  this.#restartTimer = setTimeout(() => {
999
1013
  this.#restartTimer = null;
1014
+ this.#backendRestart ??= this.#drainBackendRestarts().finally(() => {
1015
+ this.#backendRestart = null;
1016
+ });
1017
+ }, BACKEND_RESTART_DEBOUNCE_MS);
1018
+ }
1019
+ async#drainBackendRestarts() {
1020
+ while (this.#pendingRestartReason && !this.#restartTimer && !this.#stopping) {
1000
1021
  const next = this.#pendingRestartReason;
1001
1022
  this.#pendingRestartReason = null;
1002
- if (next)
1003
- this.#restartBackend(next);
1004
- }, BACKEND_RESTART_DEBOUNCE_MS);
1023
+ await this.#restartBackend(next);
1024
+ }
1005
1025
  }
1006
1026
  async#restartBackend(reason) {
1007
1027
  this.logger.verbose(`[backend-reload] restarting backend generation=${reason.generation ?? "(unknown)"} files=${reason.files.length} roles=${reason.roles.join(",") || "(none)"}`);
@@ -1306,7 +1326,7 @@ ${this.#backendStderrTail.join(`
1306
1326
  this.logger.info(`[idle-suspend] no build activity for ${Math.round(idleMs / 1000)}s; released the builder \u2014 the next edit or route request brings it back`);
1307
1327
  }
1308
1328
  get #restartPending() {
1309
- return !!(this.#pendingRecycle || this.#restartTimer || this.#backendRecoveryTimer || this.#builderRecoveryTimer || this.#rssRecycleReason);
1329
+ return !!(this.#pendingRecycle || this.#restartTimer || this.#backendRestart || this.#backendRecoveryTimer || this.#builderRecoveryTimer || this.#rssRecycleReason);
1310
1330
  }
1311
1331
  async#startIdleWatcher() {
1312
1332
  try {
@@ -1609,6 +1629,7 @@ ${this.#backendStderrTail.join(`
1609
1629
  this.#clearRestartTimers();
1610
1630
  this.#builderRecoveryAttempts = 0;
1611
1631
  this.#pendingRestartReason = null;
1632
+ await this.#backendRestart;
1612
1633
  this.#lastGoodFrontend = {};
1613
1634
  this.#buildStatusByPhase.clear();
1614
1635
  this.#pendingBuildStatusReplay = [];
@@ -1846,7 +1867,7 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
1846
1867
 
1847
1868
  // pkgs/@akanjs/cli/application/application.runner.ts
1848
1869
  import { Logger as Logger3 } from "akanjs/common";
1849
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-r6b9rkb8.js")).ApplicationBuildRunner;
1870
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-ez37p161.js")).ApplicationBuildRunner;
1850
1871
  var loadPrompts = async () => await import("@inquirer/prompts");
1851
1872
 
1852
1873
  class ApplicationRunner extends runner("application") {
@@ -2038,59 +2059,155 @@ try {
2038
2059
  setTimeout(() => openBrowser(`http://localhost:${env.AKAN_PUBLIC_CLIENT_PORT ?? env.PORT ?? "8282"}`), 3000);
2039
2060
  return appHost;
2040
2061
  }
2041
- async buildMobile(app, platform, { target, env = "debug", profile = "release" } = {}) {
2062
+ async buildMobile(app, platform, { target, env = "debug", profile = "release", installer = false } = {}) {
2042
2063
  const targets = await resolveMobileTargets(app, target);
2043
- await this.#buildMobileCsr(app, env);
2064
+ if (installer && platform !== "windows")
2065
+ throw new Error(`--installer builds a Windows setup program; this computer builds for ${platform}.`);
2066
+ const carried = await this.#stageMobile(app, platform, targets, env);
2044
2067
  await this.#runMobileTargets(targets, async (mobileTarget) => {
2045
- this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).build(platform, { profile }));
2068
+ this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget, env).build(platform, {
2069
+ profile,
2070
+ ...carried && ApplicationRunner.carriesServer(mobileTarget, platform) ? { server: carried } : {},
2071
+ ...installer ? { installer } : {}
2072
+ }));
2046
2073
  });
2047
2074
  }
2048
- async startMobile(app, platform, { target, env = "local", operation = "local", device, teamId } = {}) {
2049
- const targets = await resolveMobileTargets(app, target);
2050
- const [mobileTarget] = targets;
2051
- if (!mobileTarget || targets.length > 1)
2052
- throw new Error(`start-${platform === "ios" || platform === "android" ? platform : "desktop"} runs one mobile target at a time; pass --target <name>.`);
2053
- const nativeApp = new NativeApp(app, mobileTarget);
2075
+ async buildDesktop(app, options = {}) {
2076
+ await this.buildMobile(app, NativeApp.desktopPlatform(), options);
2077
+ }
2078
+ async startMobile(app, platform, { target, env = "local", operation = "local", device, teamId, interrupt } = {}) {
2079
+ const mobileTarget = await ApplicationRunner.startTarget(app, platform, target);
2080
+ const nativeApp = new NativeApp(app, mobileTarget, env);
2054
2081
  const selection = { ...device ? { device } : {}, ...teamId ? { teamId } : {} };
2055
2082
  if (operation === "release") {
2056
- await this.#buildMobileCsr(app, env);
2057
- const running = await nativeApp.run(platform, { ...selection, profile: "release" });
2083
+ const carried = await this.#stageMobile(app, platform, [mobileTarget], env);
2084
+ const running = await nativeApp.run(platform, {
2085
+ ...selection,
2086
+ profile: "release",
2087
+ ...carried ? { server: carried } : {}
2088
+ });
2058
2089
  await running.exited;
2059
2090
  return;
2060
2091
  }
2061
2092
  const upstream = `http://localhost:${await app.getDevPort()}`;
2062
- if (!await ApplicationRunner.#answers(upstream))
2093
+ if (!await ApplicationRunner.answers(upstream, app.name, app.workspace.workspaceRoot))
2063
2094
  throw new Error(`No dev server answers on ${upstream}; run \`akan start ${app.name}\` first.`);
2064
2095
  const { i18n } = await app.getConfig();
2065
2096
  const session = await nativeApp.dev(platform, { upstream, lang: i18n.defaultLocale, ...selection });
2066
- process.once("SIGINT", () => {
2067
- session.stop().finally(() => process.exit(130));
2068
- });
2097
+ if (interrupt)
2098
+ interrupt.add(async () => await session.stop(), "Abandoning the app's shutdown; its window may stay open.", 130);
2099
+ else
2100
+ process.once("SIGINT", () => {
2101
+ session.stop().finally(() => process.exit(130));
2102
+ });
2069
2103
  await session.exited;
2070
2104
  }
2071
2105
  async startDesktop(app, options = {}) {
2072
2106
  await this.startMobile(app, NativeApp.desktopPlatform(), options);
2073
2107
  }
2074
- static async#answers(url) {
2075
- try {
2076
- await fetch(new URL("/_akan/app/health", url), { signal: AbortSignal.timeout(3000) });
2077
- return true;
2078
- } catch {
2108
+ static async startTarget(app, platform = NativeApp.desktopPlatform(), target) {
2109
+ const targets = await resolveMobileTargets(app, target);
2110
+ const [mobileTarget] = targets;
2111
+ if (!mobileTarget || targets.length > 1)
2112
+ throw new Error(`start-${platform === "ios" || platform === "android" ? platform : "desktop"} runs one mobile target at a time; pass --target <name>.`);
2113
+ return mobileTarget;
2114
+ }
2115
+ static async answers(url, appName, workspaceRoot) {
2116
+ const signal = AbortSignal.timeout(3000);
2117
+ const health = await fetch(new URL("/_akan/app/health", url), { signal }).catch(() => null);
2118
+ if (!health)
2079
2119
  return false;
2120
+ const info = await fetch(new URL("/_akan/app/info", url), { signal }).then(async (res) => res.ok ? await res.json() : null).catch(() => null);
2121
+ const stopIt = `Stop it (\`akan start ${appName} --kill\` takes the port over) or give ${appName} another port with AKAN_DEV_PORT`;
2122
+ if (info?.appName === appName) {
2123
+ if (typeof info.workspaceRoot !== "string" || ApplicationRunner.#sameCheckout(info.workspaceRoot, workspaceRoot))
2124
+ return true;
2125
+ throw new Error(`${url} is the dev server of ${appName} in ${info.workspaceRoot}, not in ${workspaceRoot}. ${stopIt}.`);
2080
2126
  }
2127
+ if (typeof info?.appName === "string")
2128
+ throw new Error(`${url} is the dev server of ${info.appName}, not ${appName}. ${stopIt}.`);
2129
+ const { pid } = await health.json().catch(() => null) ?? {};
2130
+ throw new Error(typeof pid === "number" ? `${url} is an akan dev server (pid ${pid}) too old to say which app it serves. ${stopIt}.` : `${url} answers, but not as an akan dev server. Stop what holds the port or give ${appName} another port with AKAN_DEV_PORT.`);
2131
+ }
2132
+ static #sameCheckout(reported, own) {
2133
+ const canonical = (root) => {
2134
+ try {
2135
+ return realpathSync.native(root);
2136
+ } catch {
2137
+ return path5.resolve(root);
2138
+ }
2139
+ };
2140
+ return canonical(reported) === canonical(own);
2081
2141
  }
2082
2142
  async releaseIos(app, { target, env = "main", teamId, adHoc = false } = {}) {
2083
2143
  const targets = await resolveMobileTargets(app, target);
2084
2144
  await this.#buildMobileCsr(app, env);
2085
2145
  for (const mobileTarget of targets)
2086
- this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).releaseIos({ teamId, adHoc }));
2146
+ this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget, env).releaseIos({ teamId, adHoc }));
2087
2147
  }
2088
2148
  async releaseAndroid(app, format, { target, env = "main" } = {}) {
2089
2149
  const targets = await resolveMobileTargets(app, target);
2090
2150
  NativeApp.androidSigning();
2091
2151
  await this.#buildMobileCsr(app, env);
2092
2152
  for (const mobileTarget of targets)
2093
- this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).releaseAndroid({ formats: [format] }));
2153
+ this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget, env).releaseAndroid({ formats: [format] }));
2154
+ }
2155
+ async packUpdate(app, platform, { target, env = "main", out, against }) {
2156
+ const targets = await resolveMobileTargets(app, target);
2157
+ const [mobileTarget] = targets;
2158
+ if (!mobileTarget || targets.length > 1)
2159
+ throw new Error("pack-update packs one mobile target at a time; pass --target <name>.");
2160
+ await this.#buildMobileCsr(app, env);
2161
+ const packed = await new NativeApp(app, mobileTarget, env).packUpdate(platform, { out });
2162
+ const size = packed.manifest.files.reduce((sum, file) => sum + file.size, 0);
2163
+ app.logger.info(`packed ${mobileTarget.name} ${platform} for channel '${packed.channel}' to ${packed.out}: ${packed.manifest.files.length} files, ${(size / 1024).toFixed(0)} KiB, native API ${packed.manifest.nativeApi}`);
2164
+ if (!against)
2165
+ return;
2166
+ const bundle = path5.join(packed.out, "bundle.json");
2167
+ const compat = await NativeApp.compareBundles(app.cwdPath, against, bundle);
2168
+ await Bun.write(path5.join(packed.out, "compat.json"), `${JSON.stringify({ against, ...compat }, null, 2)}
2169
+ `);
2170
+ if (!compat.compatible)
2171
+ throw new Error(`This bundle needs a new store build; the one ${against} came from provides another native API:
2172
+ - ${compat.problems.join(`
2173
+ - `)}`);
2174
+ app.logger.info(`runs in the store build of ${against}`);
2175
+ }
2176
+ async updateKeygen(app, platform, target) {
2177
+ const nativePlatform = platform === "desktop" ? NativeApp.desktopPlatform() : platform;
2178
+ const seen = new Set;
2179
+ for (const mobileTarget of await resolveMobileTargets(app, target ?? "all")) {
2180
+ const appId = resolveAppId(mobileTarget.config.appId, nativePlatform);
2181
+ if (seen.has(appId))
2182
+ continue;
2183
+ seen.add(appId);
2184
+ const { publicKey, keyPath, created } = await new NativeApp(app, mobileTarget).updateKeygen(nativePlatform);
2185
+ app.log(`${appId}: ${created ? "made" : "read"} ${keyPath}${created ? " (keep it private and backed up: without it, installed apps take no more updates)" : ""}`);
2186
+ app.log(` mobile: { updates: { url: "https://\u2026/${app.name}", publicKey: ${JSON.stringify(publicKey)} } }`);
2187
+ const declared = mobileTarget.config.updates?.publicKey;
2188
+ if (declared && declared !== publicKey)
2189
+ app.logger.warn(`mobile.targets.${mobileTarget.name}.updates.publicKey is another key's; releases would not verify.`);
2190
+ }
2191
+ }
2192
+ async publishUpdate(app, platform, { target, env = "main", channel } = {}) {
2193
+ const targets = await resolveMobileTargets(app, target);
2194
+ const nativePlatform = platform === "desktop" ? NativeApp.desktopPlatform() : platform;
2195
+ for (const mobileTarget of targets)
2196
+ await new NativeApp(app, mobileTarget, env).assertPublishable(nativePlatform, {
2197
+ server: ApplicationRunner.carriesServer(mobileTarget, nativePlatform),
2198
+ ...channel ? { channel } : {}
2199
+ });
2200
+ const carried = await this.#stageMobile(app, nativePlatform, targets, env);
2201
+ for (const mobileTarget of targets) {
2202
+ const nativeApp = new NativeApp(app, mobileTarget, env);
2203
+ const published = await nativeApp.publishUpdate(nativePlatform, {
2204
+ ...channel ? { channel } : {},
2205
+ ...carried && ApplicationRunner.carriesServer(mobileTarget, nativePlatform) ? { server: carried } : {}
2206
+ });
2207
+ this.#reportBuild(app, mobileTarget, published.build);
2208
+ app.log(`${app.name}/${mobileTarget.name} ${published.bundle}: ${published.dir}/${published.channel}.json, ${published.files} files, ${Math.round(published.size / 1024)} KiB`);
2209
+ app.log(`Upload ${nativeApp.updatesDir} to ${mobileTarget.config.updates?.url}, the manifests last.`);
2210
+ }
2094
2211
  }
2095
2212
  #reportBuild(app, mobileTarget, { artifacts, warnings, signing }) {
2096
2213
  for (const warning of warnings)
@@ -2100,6 +2217,15 @@ try {
2100
2217
  for (const artifact of artifacts)
2101
2218
  app.log(`${app.name}/${mobileTarget.name} ${artifact.kind}: ${artifact.path}`);
2102
2219
  }
2220
+ static carriesServer({ config }, platform = NativeApp.desktopPlatform()) {
2221
+ return platform !== "ios" && platform !== "android" && config.native?.desktop?.server === true;
2222
+ }
2223
+ async#stageMobile(app, platform, targets, env) {
2224
+ const stage = targets.some((mobileTarget) => ApplicationRunner.carriesServer(mobileTarget, platform)) ? new DesktopServerStage(app) : null;
2225
+ await stage?.install();
2226
+ await this.#buildMobileCsr(app, env);
2227
+ return await stage?.prepare(env);
2228
+ }
2103
2229
  async#buildMobileCsr(app, env) {
2104
2230
  const prevEnv = {
2105
2231
  AKAN_PUBLIC_ENV: process.env.AKAN_PUBLIC_ENV,
@@ -2112,7 +2238,7 @@ try {
2112
2238
  APP_OPERATION_MODE: "release"
2113
2239
  });
2114
2240
  try {
2115
- await new (await loadBuildRunner())(app).build({ spinner: true });
2241
+ await new (await loadBuildRunner())(app, { environment: env }).build({ spinner: true });
2116
2242
  } finally {
2117
2243
  for (const [key, value] of Object.entries(prevEnv)) {
2118
2244
  if (value === undefined)
@@ -2956,6 +3082,7 @@ class InterruptTeardown {
2956
3082
  #listen;
2957
3083
  #report;
2958
3084
  #interrupted = false;
3085
+ #chain = Promise.resolve();
2959
3086
  ownsExit = true;
2960
3087
  constructor({ exit, listen, report } = {}) {
2961
3088
  this.#exit = exit ?? ((code) => process.exit(code));
@@ -2964,36 +3091,48 @@ class InterruptTeardown {
2964
3091
  });
2965
3092
  this.#report = report ?? ((message) => Logger5.rawLog(message, undefined, "error"));
2966
3093
  }
2967
- add(run, abandoned) {
2968
- this.#teardowns.push({ run, abandoned, running: false });
3094
+ add(run, abandoned, exitCode = 0) {
3095
+ this.#teardowns.push({ run, abandoned, exitCode, state: "pending" });
2969
3096
  if (this.#teardowns.length === 1)
2970
3097
  this.#listen(() => this.#onSignal());
2971
3098
  }
3099
+ async runAll() {
3100
+ await this.#runTeardowns();
3101
+ }
2972
3102
  #onSignal() {
2973
3103
  if (this.#interrupted) {
2974
3104
  for (const one of this.#teardowns)
2975
- if (one.running)
3105
+ if (one.state !== "done")
2976
3106
  this.#report(one.abandoned);
2977
3107
  this.#exit(130);
2978
3108
  return;
2979
3109
  }
2980
3110
  this.#interrupted = true;
2981
- Promise.all(this.#teardowns.map(async (one) => {
2982
- one.running = true;
2983
- await one.run().catch(() => {
2984
- return;
2985
- });
2986
- one.running = false;
2987
- })).finally(() => {
3111
+ this.#runTeardowns().finally(() => {
2988
3112
  if (this.ownsExit)
2989
- this.#exit(0);
3113
+ this.#exit(Math.max(0, ...this.#teardowns.map((one) => one.exitCode)));
2990
3114
  });
2991
3115
  }
3116
+ async#runTeardowns() {
3117
+ this.#chain = this.#chain.then(async () => {
3118
+ for (const one of this.#teardowns.toReversed()) {
3119
+ if (one.state !== "pending")
3120
+ continue;
3121
+ one.state = "running";
3122
+ await one.run().catch(() => {
3123
+ return;
3124
+ });
3125
+ one.state = "done";
3126
+ }
3127
+ });
3128
+ await this.#chain;
3129
+ }
2992
3130
  }
2993
3131
 
2994
3132
  // pkgs/@akanjs/cli/application/application.script.ts
2995
3133
  class ApplicationScript extends script("application", [ApplicationRunner, LibraryScript]) {
2996
3134
  static dbShutdownTimeoutMs = 20000;
3135
+ static devServerReadyTimeoutMs = DevSupervisor.bootTimeoutMs;
2997
3136
  #interrupt = new InterruptTeardown;
2998
3137
  async confirmDatabaseModeDependencyInstall(databaseMode, installSpecs) {
2999
3138
  return await confirm({
@@ -3205,6 +3344,10 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3205
3344
  await app.scanSync({ write });
3206
3345
  await this.applicationRunner.buildMobile(app, "android", options);
3207
3346
  }
3347
+ async buildDesktop(app, { write = true, ...options } = {}) {
3348
+ await app.scanSync({ write });
3349
+ await this.applicationRunner.buildDesktop(app, options);
3350
+ }
3208
3351
  async startIos(app, { write = true, ...options } = {}) {
3209
3352
  await app.scanSync({ write });
3210
3353
  await this.applicationRunner.startMobile(app, "ios", options);
@@ -3215,7 +3358,43 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3215
3358
  }
3216
3359
  async startDesktop(app, { write = true, ...options } = {}) {
3217
3360
  await app.scanSync({ write });
3218
- await this.applicationRunner.startDesktop(app, options);
3361
+ if (options.operation === "release")
3362
+ return await this.applicationRunner.startDesktop(app, options);
3363
+ const mobileTarget = await ApplicationRunner.startTarget(app, undefined, options.target);
3364
+ if (!ApplicationRunner.carriesServer(mobileTarget))
3365
+ return await this.applicationRunner.startDesktop(app, options);
3366
+ DesktopServerStage.assertCarriable(await app.getConfig());
3367
+ const upstream = `http://localhost:${await app.getDevPort()}`;
3368
+ try {
3369
+ if (await ApplicationRunner.answers(upstream, app.name, app.workspace.workspaceRoot))
3370
+ app.log(`The desktop app follows the dev server on ${upstream}.`);
3371
+ else
3372
+ await this.#startDevServerFor(app, upstream);
3373
+ await this.applicationRunner.startDesktop(app, { ...options, interrupt: this.#interrupt });
3374
+ } finally {
3375
+ await this.#interrupt.runAll();
3376
+ }
3377
+ }
3378
+ async#startDevServerFor(app, upstream) {
3379
+ app.log(`No dev server answers on ${upstream}; starting \`akan start ${app.name}\` for the desktop app.`);
3380
+ let settle = () => {};
3381
+ const settled = new Promise((resolve) => settle = resolve);
3382
+ const appHost = await this.startOne(app, {
3383
+ write: false,
3384
+ onDevEvent: (event) => {
3385
+ if ("state" in event && (event.state === "ready" || event.state === "failed"))
3386
+ settle(event.state);
3387
+ }
3388
+ });
3389
+ this.#interrupt.add(async () => {
3390
+ await appHost.stop();
3391
+ }, "Abandoning the dev server shutdown; its processes may still be running.", 130);
3392
+ let state;
3393
+ const timedOut = await DevSupervisor.timesOut(settled.then((settledState) => state = settledState), ApplicationScript.devServerReadyTimeoutMs);
3394
+ if (state === "ready")
3395
+ return;
3396
+ await this.#interrupt.runAll();
3397
+ throw new Error(timedOut ? `akan start ${app.name} did not answer within ${ApplicationScript.devServerReadyTimeoutMs / 1000}s; see its log above.` : `akan start ${app.name} gave up restarting its server; see its log above.`);
3219
3398
  }
3220
3399
  async releaseIos(app, {
3221
3400
  write = true,
@@ -3235,6 +3414,27 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3235
3414
  ApplicationScript.#assertReleaseEnv("releaseAndroid", options.env ?? "main", allowLocalRelease);
3236
3415
  await this.applicationRunner.releaseAndroid(app, format, options);
3237
3416
  }
3417
+ async packUpdate(app, platform, {
3418
+ write = true,
3419
+ allowLocalRelease = false,
3420
+ ...options
3421
+ } = {}) {
3422
+ await app.scanSync({ write });
3423
+ ApplicationScript.#assertReleaseEnv("packUpdate", options.env ?? "main", allowLocalRelease);
3424
+ await this.applicationRunner.packUpdate(app, platform, options);
3425
+ }
3426
+ async updateKeygen(app, platform, { target } = {}) {
3427
+ await this.applicationRunner.updateKeygen(app, platform, target);
3428
+ }
3429
+ async publishUpdate(app, platform, {
3430
+ write = true,
3431
+ allowLocalRelease = false,
3432
+ ...options
3433
+ } = {}) {
3434
+ await app.scanSync({ write });
3435
+ ApplicationScript.#assertReleaseEnv("publishUpdate", options.env ?? "main", allowLocalRelease);
3436
+ await this.applicationRunner.publishUpdate(app, platform, options);
3437
+ }
3238
3438
  static #assertReleaseEnv(command, env, allowLocalRelease) {
3239
3439
  if (env === "local" && !allowLocalRelease)
3240
3440
  throw new Error(`${command} --env local is blocked. Pass allowLocalRelease only for explicit local release testing.`);
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  AppInfo
4
- } from "./index-0n9q1f23.js";
4
+ } from "./index-r8gp8zk3.js";
5
5
 
6
6
  // pkgs/@akanjs/devkit/slicePlanner.ts
7
7
  class SlicePlanner {
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-58cyna2s.js";
5
+ } from "./index-b68zfsvz.js";
6
6
  import {
7
7
  LibExecutor
8
- } from "./index-0n9q1f23.js";
8
+ } from "./index-r8gp8zk3.js";
9
9
  import {
10
10
  LibSource,
11
11
  formatLibStatuses