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

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 (61) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-h19atbr3.js → CodeAgent-7acv4ah8.js} +2 -2
  3. package/{CodeTui-46zygq8p.js → CodeTui-9tkh34mj.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-xpnkyx4z.js} +70 -8
  7. package/{applicationBuildRunner-r6b9rkb8.js → applicationBuildRunner-w4917tje.js} +67 -22
  8. package/buildBatch.proc.js +3 -3
  9. package/{cloud.command-ps9dabbg.js → cloud.command-7yd624qd.js} +9 -9
  10. package/cloud.script-25vzkz7b.js +20 -0
  11. package/{code.command-78qw3q3f.js → code.command-fpx4gzmy.js} +6 -6
  12. package/commandManifest.json +1 -1
  13. package/{context.command-x5f7t3gk.js → context.command-snn1k5q3.js} +13 -13
  14. package/context.runner-nzaf8c98.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 +107 -1
  18. package/incrementalBuilder.proc.js +2 -2
  19. package/{index-118vq9dh.js → index-0ej0sr6m.js} +225 -46
  20. package/{index-hzy4pe24.js → index-0pqzrbpr.js} +2 -2
  21. package/{index-6dt6zft6.js → index-20r2g1t3.js} +4 -4
  22. package/{index-xfgdmm5d.js → index-2ctqegdm.js} +2 -2
  23. package/{index-d65fzxvd.js → index-2s2e5qnd.js} +1 -1
  24. package/{index-t02g6dpn.js → index-2z30v4k5.js} +3 -3
  25. package/{index-vxqe24mp.js → index-52n8xr63.js} +1 -1
  26. package/{index-tg09x14f.js → index-9j0kykc6.js} +5 -5
  27. package/{index-58cyna2s.js → index-b68zfsvz.js} +1 -1
  28. package/{index-2zcm7fjd.js → index-ewbek95g.js} +3 -3
  29. package/{index-bp9b1g2r.js → index-fy42hyb9.js} +7 -3
  30. package/{index-zazvn45w.js → index-genegfnt.js} +4 -4
  31. package/{index-da72pfdv.js → index-h9ed99ax.js} +7 -7
  32. package/{index-per2kqy9.js → index-k7809wmg.js} +2 -2
  33. package/{index-j9z8vg2n.js → index-n9704w6y.js} +4 -4
  34. package/{index-860zjnr1.js → index-nbg9bfnr.js} +1 -1
  35. package/{index-5mm3g24t.js → index-qtz88kh0.js} +2 -2
  36. package/{index-0n9q1f23.js → index-r8gp8zk3.js} +231 -98
  37. package/{index-ggfhz9ab.js → index-rbeahw6j.js} +3 -3
  38. package/{index-56xwsgyn.js → index-rrt9pnb4.js} +2 -2
  39. package/{index-v4hnkqz9.js → index-ss828r32.js} +1 -1
  40. package/index-tfe3711h.js +947 -0
  41. package/{index-5kjsr2pg.js → index-ttn00pr3.js} +38 -3
  42. package/{index-w5pet0nq.js → index-vt64c067.js} +11 -9
  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-f39yk7be.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 +2 -2
  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-0e53tvxk.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-wypqk2zb.js} +20 -20
  59. package/cloud.script-wrw1kz6k.js +0 -20
  60. package/context.runner-nfs8ksh3.js +0 -21
  61. 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";
46
+ } from "./index-r8gp8zk3.js";
47
47
  import {
48
48
  LibraryScript
49
- } from "./index-5mm3g24t.js";
49
+ } from "./index-qtz88kh0.js";
50
50
  import {
51
+ DesktopServerStage,
51
52
  resolveMobileTargets,
53
+ resolveAppId,
52
54
  NativeApp
53
- } from "./index-y736f42m.js";
55
+ } from "./index-tfe3711h.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";
@@ -725,6 +727,7 @@ class AkanAppHost {
725
727
  #backendGaveUp = false;
726
728
  #backendLifecycleState = "stopped";
727
729
  #pendingRestartReason = null;
730
+ #backendRestart = null;
728
731
  #pendingRecycle = null;
729
732
  #restartFailedAfterApply = null;
730
733
  #stopping = false;
@@ -796,7 +799,9 @@ class AkanAppHost {
796
799
  this.#cancelIdleSuspend();
797
800
  this.#stopIdleWatcher();
798
801
  this.#clearRestartTimers();
802
+ this.#pendingRestartReason = null;
799
803
  this.#failPendingBuilderMessages("dev server is shutting down");
804
+ await this.#backendRestart;
800
805
  await this.#stopBackend();
801
806
  this.#stopBuilder();
802
807
  return this;
@@ -817,6 +822,12 @@ class AkanAppHost {
817
822
  return await createTunnel(type, { app: this.app, environment });
818
823
  }
819
824
  #startBackend(startStatus = null) {
825
+ if (this.#stopping)
826
+ return;
827
+ if (this.#backend) {
828
+ this.logger.warn(`backend pid=${this.#backend.pid} is still running; not starting a second one`);
829
+ return;
830
+ }
820
831
  this.#builderRequests.startGeneration();
821
832
  this.#discardPendingBuilderMessages("the backend restarted while the builder was away");
822
833
  this.#backendStartStatus = startStatus;
@@ -997,11 +1008,17 @@ class AkanAppHost {
997
1008
  clearTimeout(this.#restartTimer);
998
1009
  this.#restartTimer = setTimeout(() => {
999
1010
  this.#restartTimer = null;
1011
+ this.#backendRestart ??= this.#drainBackendRestarts().finally(() => {
1012
+ this.#backendRestart = null;
1013
+ });
1014
+ }, BACKEND_RESTART_DEBOUNCE_MS);
1015
+ }
1016
+ async#drainBackendRestarts() {
1017
+ while (this.#pendingRestartReason && !this.#restartTimer && !this.#stopping) {
1000
1018
  const next = this.#pendingRestartReason;
1001
1019
  this.#pendingRestartReason = null;
1002
- if (next)
1003
- this.#restartBackend(next);
1004
- }, BACKEND_RESTART_DEBOUNCE_MS);
1020
+ await this.#restartBackend(next);
1021
+ }
1005
1022
  }
1006
1023
  async#restartBackend(reason) {
1007
1024
  this.logger.verbose(`[backend-reload] restarting backend generation=${reason.generation ?? "(unknown)"} files=${reason.files.length} roles=${reason.roles.join(",") || "(none)"}`);
@@ -1306,7 +1323,7 @@ ${this.#backendStderrTail.join(`
1306
1323
  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
1324
  }
1308
1325
  get #restartPending() {
1309
- return !!(this.#pendingRecycle || this.#restartTimer || this.#backendRecoveryTimer || this.#builderRecoveryTimer || this.#rssRecycleReason);
1326
+ return !!(this.#pendingRecycle || this.#restartTimer || this.#backendRestart || this.#backendRecoveryTimer || this.#builderRecoveryTimer || this.#rssRecycleReason);
1310
1327
  }
1311
1328
  async#startIdleWatcher() {
1312
1329
  try {
@@ -1609,6 +1626,7 @@ ${this.#backendStderrTail.join(`
1609
1626
  this.#clearRestartTimers();
1610
1627
  this.#builderRecoveryAttempts = 0;
1611
1628
  this.#pendingRestartReason = null;
1629
+ await this.#backendRestart;
1612
1630
  this.#lastGoodFrontend = {};
1613
1631
  this.#buildStatusByPhase.clear();
1614
1632
  this.#pendingBuildStatusReplay = [];
@@ -1846,7 +1864,7 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
1846
1864
 
1847
1865
  // pkgs/@akanjs/cli/application/application.runner.ts
1848
1866
  import { Logger as Logger3 } from "akanjs/common";
1849
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-r6b9rkb8.js")).ApplicationBuildRunner;
1867
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-w4917tje.js")).ApplicationBuildRunner;
1850
1868
  var loadPrompts = async () => await import("@inquirer/prompts");
1851
1869
 
1852
1870
  class ApplicationRunner extends runner("application") {
@@ -2038,59 +2056,137 @@ try {
2038
2056
  setTimeout(() => openBrowser(`http://localhost:${env.AKAN_PUBLIC_CLIENT_PORT ?? env.PORT ?? "8282"}`), 3000);
2039
2057
  return appHost;
2040
2058
  }
2041
- async buildMobile(app, platform, { target, env = "debug", profile = "release" } = {}) {
2059
+ async buildMobile(app, platform, { target, env = "debug", profile = "release", installer = false } = {}) {
2042
2060
  const targets = await resolveMobileTargets(app, target);
2043
- await this.#buildMobileCsr(app, env);
2061
+ if (installer && platform !== "windows")
2062
+ throw new Error(`--installer builds a Windows setup program; this computer builds for ${platform}.`);
2063
+ const carried = await this.#stageMobile(app, platform, targets, env);
2044
2064
  await this.#runMobileTargets(targets, async (mobileTarget) => {
2045
- this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).build(platform, { profile }));
2065
+ this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget, env).build(platform, {
2066
+ profile,
2067
+ ...carried && ApplicationRunner.carriesServer(mobileTarget, platform) ? { server: carried } : {},
2068
+ ...installer ? { installer } : {}
2069
+ }));
2046
2070
  });
2047
2071
  }
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);
2072
+ async buildDesktop(app, options = {}) {
2073
+ await this.buildMobile(app, NativeApp.desktopPlatform(), options);
2074
+ }
2075
+ async startMobile(app, platform, { target, env = "local", operation = "local", device, teamId, interrupt } = {}) {
2076
+ const mobileTarget = await ApplicationRunner.startTarget(app, platform, target);
2077
+ const nativeApp = new NativeApp(app, mobileTarget, env);
2054
2078
  const selection = { ...device ? { device } : {}, ...teamId ? { teamId } : {} };
2055
2079
  if (operation === "release") {
2056
- await this.#buildMobileCsr(app, env);
2057
- const running = await nativeApp.run(platform, { ...selection, profile: "release" });
2080
+ const carried = await this.#stageMobile(app, platform, [mobileTarget], env);
2081
+ const running = await nativeApp.run(platform, {
2082
+ ...selection,
2083
+ profile: "release",
2084
+ ...carried ? { server: carried } : {}
2085
+ });
2058
2086
  await running.exited;
2059
2087
  return;
2060
2088
  }
2061
2089
  const upstream = `http://localhost:${await app.getDevPort()}`;
2062
- if (!await ApplicationRunner.#answers(upstream))
2090
+ if (!await ApplicationRunner.answers(upstream, app.name))
2063
2091
  throw new Error(`No dev server answers on ${upstream}; run \`akan start ${app.name}\` first.`);
2064
2092
  const { i18n } = await app.getConfig();
2065
2093
  const session = await nativeApp.dev(platform, { upstream, lang: i18n.defaultLocale, ...selection });
2066
- process.once("SIGINT", () => {
2067
- session.stop().finally(() => process.exit(130));
2068
- });
2094
+ if (interrupt)
2095
+ interrupt.add(async () => await session.stop(), "Abandoning the app's shutdown; its window may stay open.", 130);
2096
+ else
2097
+ process.once("SIGINT", () => {
2098
+ session.stop().finally(() => process.exit(130));
2099
+ });
2069
2100
  await session.exited;
2070
2101
  }
2071
2102
  async startDesktop(app, options = {}) {
2072
2103
  await this.startMobile(app, NativeApp.desktopPlatform(), options);
2073
2104
  }
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 {
2105
+ static async startTarget(app, platform = NativeApp.desktopPlatform(), target) {
2106
+ const targets = await resolveMobileTargets(app, target);
2107
+ const [mobileTarget] = targets;
2108
+ if (!mobileTarget || targets.length > 1)
2109
+ throw new Error(`start-${platform === "ios" || platform === "android" ? platform : "desktop"} runs one mobile target at a time; pass --target <name>.`);
2110
+ return mobileTarget;
2111
+ }
2112
+ static async answers(url, appName) {
2113
+ const signal = AbortSignal.timeout(3000);
2114
+ const health = await fetch(new URL("/_akan/app/health", url), { signal }).catch(() => null);
2115
+ if (!health)
2079
2116
  return false;
2080
- }
2117
+ const info = await fetch(new URL("/_akan/app/info", url), { signal }).then(async (res) => res.ok ? await res.json() : null).catch(() => null);
2118
+ if (info?.appName === appName)
2119
+ return true;
2120
+ const stopIt = `Stop it (\`akan start ${appName} --kill\` takes the port over) or give ${appName} another port with AKAN_DEV_PORT`;
2121
+ if (typeof info?.appName === "string")
2122
+ throw new Error(`${url} is the dev server of ${info.appName}, not ${appName}. ${stopIt}.`);
2123
+ const { pid } = await health.json().catch(() => null) ?? {};
2124
+ 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.`);
2081
2125
  }
2082
2126
  async releaseIos(app, { target, env = "main", teamId, adHoc = false } = {}) {
2083
2127
  const targets = await resolveMobileTargets(app, target);
2084
2128
  await this.#buildMobileCsr(app, env);
2085
2129
  for (const mobileTarget of targets)
2086
- this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).releaseIos({ teamId, adHoc }));
2130
+ this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget, env).releaseIos({ teamId, adHoc }));
2087
2131
  }
2088
2132
  async releaseAndroid(app, format, { target, env = "main" } = {}) {
2089
2133
  const targets = await resolveMobileTargets(app, target);
2090
2134
  NativeApp.androidSigning();
2091
2135
  await this.#buildMobileCsr(app, env);
2092
2136
  for (const mobileTarget of targets)
2093
- this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).releaseAndroid({ formats: [format] }));
2137
+ this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget, env).releaseAndroid({ formats: [format] }));
2138
+ }
2139
+ async packUpdate(app, platform, { target, env = "main", out, against }) {
2140
+ const targets = await resolveMobileTargets(app, target);
2141
+ const [mobileTarget] = targets;
2142
+ if (!mobileTarget || targets.length > 1)
2143
+ throw new Error("pack-update packs one mobile target at a time; pass --target <name>.");
2144
+ await this.#buildMobileCsr(app, env);
2145
+ const packed = await new NativeApp(app, mobileTarget, env).packUpdate(platform, { out });
2146
+ const size = packed.manifest.files.reduce((sum, file) => sum + file.size, 0);
2147
+ 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}`);
2148
+ if (!against)
2149
+ return;
2150
+ const bundle = path5.join(packed.out, "bundle.json");
2151
+ const compat = await NativeApp.compareBundles(app.cwdPath, against, bundle);
2152
+ await Bun.write(path5.join(packed.out, "compat.json"), `${JSON.stringify({ against, ...compat }, null, 2)}
2153
+ `);
2154
+ if (!compat.compatible)
2155
+ throw new Error(`This bundle needs a new store build; the one ${against} came from provides another native API:
2156
+ - ${compat.problems.join(`
2157
+ - `)}`);
2158
+ app.logger.info(`runs in the store build of ${against}`);
2159
+ }
2160
+ async updateKeygen(app, platform, target) {
2161
+ const nativePlatform = platform === "desktop" ? NativeApp.desktopPlatform() : platform;
2162
+ const seen = new Set;
2163
+ for (const mobileTarget of await resolveMobileTargets(app, target ?? "all")) {
2164
+ const appId = resolveAppId(mobileTarget.config.appId, nativePlatform);
2165
+ if (seen.has(appId))
2166
+ continue;
2167
+ seen.add(appId);
2168
+ const { publicKey, keyPath, created } = await new NativeApp(app, mobileTarget).updateKeygen(nativePlatform);
2169
+ app.log(`${appId}: ${created ? "made" : "read"} ${keyPath}${created ? " (keep it private and backed up: without it, installed apps take no more updates)" : ""}`);
2170
+ app.log(` mobile: { updates: { url: "https://\u2026/${app.name}", publicKey: ${JSON.stringify(publicKey)} } }`);
2171
+ const declared = mobileTarget.config.updates?.publicKey;
2172
+ if (declared && declared !== publicKey)
2173
+ app.logger.warn(`mobile.targets.${mobileTarget.name}.updates.publicKey is another key's; releases would not verify.`);
2174
+ }
2175
+ }
2176
+ async publishUpdate(app, platform, { target, env = "main", channel } = {}) {
2177
+ const targets = await resolveMobileTargets(app, target);
2178
+ const nativePlatform = platform === "desktop" ? NativeApp.desktopPlatform() : platform;
2179
+ const carried = await this.#stageMobile(app, nativePlatform, targets, env);
2180
+ for (const mobileTarget of targets) {
2181
+ const nativeApp = new NativeApp(app, mobileTarget, env);
2182
+ const published = await nativeApp.publishUpdate(nativePlatform, {
2183
+ ...channel ? { channel } : {},
2184
+ ...carried && ApplicationRunner.carriesServer(mobileTarget, nativePlatform) ? { server: carried } : {}
2185
+ });
2186
+ this.#reportBuild(app, mobileTarget, published.build);
2187
+ app.log(`${app.name}/${mobileTarget.name} ${published.bundle}: ${published.dir}/${published.channel}.json, ${published.files} files, ${Math.round(published.size / 1024)} KiB`);
2188
+ app.log(`Upload ${nativeApp.updatesDir} to ${mobileTarget.config.updates?.url}, the manifests last.`);
2189
+ }
2094
2190
  }
2095
2191
  #reportBuild(app, mobileTarget, { artifacts, warnings, signing }) {
2096
2192
  for (const warning of warnings)
@@ -2100,6 +2196,16 @@ try {
2100
2196
  for (const artifact of artifacts)
2101
2197
  app.log(`${app.name}/${mobileTarget.name} ${artifact.kind}: ${artifact.path}`);
2102
2198
  }
2199
+ static carriesServer({ config }, platform = NativeApp.desktopPlatform()) {
2200
+ return platform !== "ios" && platform !== "android" && config.native?.desktop?.server === true;
2201
+ }
2202
+ async#stageMobile(app, platform, targets, env) {
2203
+ const carries = targets.some((mobileTarget) => ApplicationRunner.carriesServer(mobileTarget, platform));
2204
+ if (carries)
2205
+ DesktopServerStage.assertCarriable(await app.getConfig());
2206
+ await this.#buildMobileCsr(app, env);
2207
+ return carries ? await new DesktopServerStage(app).prepare(env) : undefined;
2208
+ }
2103
2209
  async#buildMobileCsr(app, env) {
2104
2210
  const prevEnv = {
2105
2211
  AKAN_PUBLIC_ENV: process.env.AKAN_PUBLIC_ENV,
@@ -2112,7 +2218,7 @@ try {
2112
2218
  APP_OPERATION_MODE: "release"
2113
2219
  });
2114
2220
  try {
2115
- await new (await loadBuildRunner())(app).build({ spinner: true });
2221
+ await new (await loadBuildRunner())(app, { environment: env }).build({ spinner: true });
2116
2222
  } finally {
2117
2223
  for (const [key, value] of Object.entries(prevEnv)) {
2118
2224
  if (value === undefined)
@@ -2956,6 +3062,7 @@ class InterruptTeardown {
2956
3062
  #listen;
2957
3063
  #report;
2958
3064
  #interrupted = false;
3065
+ #chain = Promise.resolve();
2959
3066
  ownsExit = true;
2960
3067
  constructor({ exit, listen, report } = {}) {
2961
3068
  this.#exit = exit ?? ((code) => process.exit(code));
@@ -2964,36 +3071,48 @@ class InterruptTeardown {
2964
3071
  });
2965
3072
  this.#report = report ?? ((message) => Logger5.rawLog(message, undefined, "error"));
2966
3073
  }
2967
- add(run, abandoned) {
2968
- this.#teardowns.push({ run, abandoned, running: false });
3074
+ add(run, abandoned, exitCode = 0) {
3075
+ this.#teardowns.push({ run, abandoned, exitCode, state: "pending" });
2969
3076
  if (this.#teardowns.length === 1)
2970
3077
  this.#listen(() => this.#onSignal());
2971
3078
  }
3079
+ async runAll() {
3080
+ await this.#runTeardowns();
3081
+ }
2972
3082
  #onSignal() {
2973
3083
  if (this.#interrupted) {
2974
3084
  for (const one of this.#teardowns)
2975
- if (one.running)
3085
+ if (one.state !== "done")
2976
3086
  this.#report(one.abandoned);
2977
3087
  this.#exit(130);
2978
3088
  return;
2979
3089
  }
2980
3090
  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(() => {
3091
+ this.#runTeardowns().finally(() => {
2988
3092
  if (this.ownsExit)
2989
- this.#exit(0);
3093
+ this.#exit(Math.max(0, ...this.#teardowns.map((one) => one.exitCode)));
2990
3094
  });
2991
3095
  }
3096
+ async#runTeardowns() {
3097
+ this.#chain = this.#chain.then(async () => {
3098
+ for (const one of this.#teardowns.toReversed()) {
3099
+ if (one.state !== "pending")
3100
+ continue;
3101
+ one.state = "running";
3102
+ await one.run().catch(() => {
3103
+ return;
3104
+ });
3105
+ one.state = "done";
3106
+ }
3107
+ });
3108
+ await this.#chain;
3109
+ }
2992
3110
  }
2993
3111
 
2994
3112
  // pkgs/@akanjs/cli/application/application.script.ts
2995
3113
  class ApplicationScript extends script("application", [ApplicationRunner, LibraryScript]) {
2996
3114
  static dbShutdownTimeoutMs = 20000;
3115
+ static devServerReadyTimeoutMs = DevSupervisor.bootTimeoutMs;
2997
3116
  #interrupt = new InterruptTeardown;
2998
3117
  async confirmDatabaseModeDependencyInstall(databaseMode, installSpecs) {
2999
3118
  return await confirm({
@@ -3205,6 +3324,10 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3205
3324
  await app.scanSync({ write });
3206
3325
  await this.applicationRunner.buildMobile(app, "android", options);
3207
3326
  }
3327
+ async buildDesktop(app, { write = true, ...options } = {}) {
3328
+ await app.scanSync({ write });
3329
+ await this.applicationRunner.buildDesktop(app, options);
3330
+ }
3208
3331
  async startIos(app, { write = true, ...options } = {}) {
3209
3332
  await app.scanSync({ write });
3210
3333
  await this.applicationRunner.startMobile(app, "ios", options);
@@ -3215,7 +3338,42 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3215
3338
  }
3216
3339
  async startDesktop(app, { write = true, ...options } = {}) {
3217
3340
  await app.scanSync({ write });
3218
- await this.applicationRunner.startDesktop(app, options);
3341
+ if (options.operation === "release")
3342
+ return await this.applicationRunner.startDesktop(app, options);
3343
+ const mobileTarget = await ApplicationRunner.startTarget(app, undefined, options.target);
3344
+ if (!ApplicationRunner.carriesServer(mobileTarget))
3345
+ return await this.applicationRunner.startDesktop(app, options);
3346
+ const upstream = `http://localhost:${await app.getDevPort()}`;
3347
+ if (await ApplicationRunner.answers(upstream, app.name))
3348
+ app.log(`The desktop app follows the dev server on ${upstream}.`);
3349
+ else
3350
+ await this.#startDevServerFor(app, upstream);
3351
+ try {
3352
+ await this.applicationRunner.startDesktop(app, { ...options, interrupt: this.#interrupt });
3353
+ } finally {
3354
+ await this.#interrupt.runAll();
3355
+ }
3356
+ }
3357
+ async#startDevServerFor(app, upstream) {
3358
+ app.log(`No dev server answers on ${upstream}; starting \`akan start ${app.name}\` for the desktop app.`);
3359
+ let settle = () => {};
3360
+ const settled = new Promise((resolve) => settle = resolve);
3361
+ const appHost = await this.startOne(app, {
3362
+ write: false,
3363
+ onDevEvent: (event) => {
3364
+ if ("state" in event && (event.state === "ready" || event.state === "failed"))
3365
+ settle(event.state);
3366
+ }
3367
+ });
3368
+ this.#interrupt.add(async () => {
3369
+ await appHost.stop();
3370
+ }, "Abandoning the dev server shutdown; its processes may still be running.", 130);
3371
+ let state;
3372
+ const timedOut = await DevSupervisor.timesOut(settled.then((settledState) => state = settledState), ApplicationScript.devServerReadyTimeoutMs);
3373
+ if (state === "ready")
3374
+ return;
3375
+ await this.#interrupt.runAll();
3376
+ 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
3377
  }
3220
3378
  async releaseIos(app, {
3221
3379
  write = true,
@@ -3235,6 +3393,27 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3235
3393
  ApplicationScript.#assertReleaseEnv("releaseAndroid", options.env ?? "main", allowLocalRelease);
3236
3394
  await this.applicationRunner.releaseAndroid(app, format, options);
3237
3395
  }
3396
+ async packUpdate(app, platform, {
3397
+ write = true,
3398
+ allowLocalRelease = false,
3399
+ ...options
3400
+ } = {}) {
3401
+ await app.scanSync({ write });
3402
+ ApplicationScript.#assertReleaseEnv("packUpdate", options.env ?? "main", allowLocalRelease);
3403
+ await this.applicationRunner.packUpdate(app, platform, options);
3404
+ }
3405
+ async updateKeygen(app, platform, { target } = {}) {
3406
+ await this.applicationRunner.updateKeygen(app, platform, target);
3407
+ }
3408
+ async publishUpdate(app, platform, {
3409
+ write = true,
3410
+ allowLocalRelease = false,
3411
+ ...options
3412
+ } = {}) {
3413
+ await app.scanSync({ write });
3414
+ ApplicationScript.#assertReleaseEnv("publishUpdate", options.env ?? "main", allowLocalRelease);
3415
+ await this.applicationRunner.publishUpdate(app, platform, options);
3416
+ }
3238
3417
  static #assertReleaseEnv(command, env, allowLocalRelease) {
3239
3418
  if (env === "local" && !allowLocalRelease)
3240
3419
  throw new Error(`${command} --env local is blocked. Pass allowLocalRelease only for explicit local release testing.`);
@@ -8,7 +8,7 @@ import {
8
8
  isScannedRootEntry,
9
9
  AppExecutor,
10
10
  LibExecutor
11
- } from "./index-0n9q1f23.js";
11
+ } from "./index-r8gp8zk3.js";
12
12
  import {
13
13
  FileSys2
14
14
  } from "./index-52w0fvh8.js";
@@ -21,7 +21,7 @@ import {
21
21
  workflowPathsForPlan,
22
22
  workflowRunArtifactPath,
23
23
  workflowSyncDir
24
- } from "./index-d65fzxvd.js";
24
+ } from "./index-2s2e5qnd.js";
25
25
 
26
26
  // pkgs/@akanjs/devkit/akanContext.ts
27
27
  import { readdir } from "fs/promises";
@@ -1,12 +1,12 @@
1
1
  // @bun
2
2
  import {
3
3
  script
4
- } from "./index-58cyna2s.js";
4
+ } from "./index-b68zfsvz.js";
5
5
  import {
6
6
  AppExecutor,
7
7
  LibExecutor,
8
8
  ModuleExecutor
9
- } from "./index-0n9q1f23.js";
9
+ } from "./index-r8gp8zk3.js";
10
10
  import {
11
11
  workflowStatus,
12
12
  compactDiagnostics,
@@ -41,10 +41,10 @@ import {
41
41
  hasSignalFactoryEntry,
42
42
  insertSignalFactoryEntry,
43
43
  addFieldUiPolicyForType
44
- } from "./index-d65fzxvd.js";
44
+ } from "./index-2s2e5qnd.js";
45
45
  import {
46
46
  ModuleScript
47
- } from "./index-zazvn45w.js";
47
+ } from "./index-genegfnt.js";
48
48
 
49
49
  // pkgs/@akanjs/cli/primitive/primitive.script.ts
50
50
  import { capitalize, lowerlize } from "akanjs/common";
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-58cyna2s.js";
5
+ } from "./index-b68zfsvz.js";
6
6
  import {
7
7
  generatedFilesForSync,
8
8
  createPassedPrimitiveReport,
9
9
  scalarChangedFiles
10
- } from "./index-d65fzxvd.js";
10
+ } from "./index-2s2e5qnd.js";
11
11
  import {
12
12
  pluralizeName
13
13
  } from "./index-nz8prx9g.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  AppExecutor,
4
4
  LibExecutor
5
- } from "./index-0n9q1f23.js";
5
+ } from "./index-r8gp8zk3.js";
6
6
 
7
7
  // pkgs/@akanjs/devkit/workflow/utils.ts
8
8
  var jsonText = (value, { trailingNewline = true } = {}) => `${JSON.stringify(value, null, 2)}${trailingNewline ? `
@@ -5,13 +5,13 @@ import {
5
5
  writeGeneratedSyncState,
6
6
  createRepairReport,
7
7
  renderRepairReport
8
- } from "./index-d65fzxvd.js";
8
+ } from "./index-2s2e5qnd.js";
9
9
  import {
10
10
  AkanContextAnalyzer
11
- } from "./index-hzy4pe24.js";
11
+ } from "./index-0pqzrbpr.js";
12
12
  import {
13
13
  runner
14
- } from "./index-58cyna2s.js";
14
+ } from "./index-b68zfsvz.js";
15
15
 
16
16
  // pkgs/@akanjs/cli/repair/repair.runner.ts
17
17
  var spawnShell = async (workspace, command) => await workspace.spawn("bash", ["-lc", command.startsWith("akan ") ? `bun run ${command}` : command], {
@@ -116,7 +116,7 @@ class SubagentPool2 {
116
116
  }
117
117
  }
118
118
  async#run(id, kind, prompt, signal) {
119
- const { CodeAgent } = await import("./CodeAgent-h19atbr3.js");
119
+ const { CodeAgent } = await import("./CodeAgent-7acv4ah8.js");
120
120
  const budget = this.#options.parent.tools.subagent;
121
121
  const child = SubagentPool2.childOf(this.#options.parent, kind);
122
122
  const agent = await CodeAgent.create({
@@ -5,19 +5,19 @@ import {
5
5
  } from "./index-94s080qm.js";
6
6
  import {
7
7
  script
8
- } from "./index-58cyna2s.js";
8
+ } from "./index-b68zfsvz.js";
9
9
  import {
10
10
  PkgExecutor
11
- } from "./index-0n9q1f23.js";
11
+ } from "./index-r8gp8zk3.js";
12
12
  import {
13
13
  ApplicationScript
14
- } from "./index-118vq9dh.js";
14
+ } from "./index-0ej0sr6m.js";
15
15
  import {
16
16
  PackageScript
17
- } from "./index-per2kqy9.js";
17
+ } from "./index-k7809wmg.js";
18
18
  import {
19
19
  CloudRunner
20
- } from "./index-56xwsgyn.js";
20
+ } from "./index-rrt9pnb4.js";
21
21
 
22
22
  // pkgs/@akanjs/cli/cloud/cloud.script.ts
23
23
  import { Logger } from "akanjs/common";
@@ -10,7 +10,7 @@ import {
10
10
  LibExecutor,
11
11
  PkgExecutor,
12
12
  ModuleExecutor
13
- } from "./index-0n9q1f23.js";
13
+ } from "./index-r8gp8zk3.js";
14
14
 
15
15
  // pkgs/@akanjs/devkit/commandDecorators/camelToKebabCase.ts
16
16
  var camelToKebabCase = (str) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
@@ -4,7 +4,7 @@ import {
4
4
  akanCodePaths2,
5
5
  McpServerConfig,
6
6
  McpSignIn
7
- } from "./index-vxqe24mp.js";
7
+ } from "./index-52n8xr63.js";
8
8
  import {
9
9
  stripAnsi
10
10
  } from "./index-7md2g9vw.js";
@@ -511,7 +511,7 @@ class AkanToolPack2 {
511
511
  return { name: "akan-tools", factory: async (pi) => await this.#register(pi) };
512
512
  }
513
513
  async names() {
514
- const { ContextRunner } = await import("./context.runner-nfs8ksh3.js");
514
+ const { ContextRunner } = await import("./context.runner-nzaf8c98.js");
515
515
  return [
516
516
  ...new ContextRunner().listMcpTools(this.#options.mode).map((tool) => tool.name),
517
517
  AkanToolPack2.verifyToolName
@@ -519,7 +519,7 @@ class AkanToolPack2 {
519
519
  }
520
520
  static verifyToolName = "akan_verify";
521
521
  async#register(pi) {
522
- const { ContextRunner } = await import("./context.runner-nfs8ksh3.js");
522
+ const { ContextRunner } = await import("./context.runner-nzaf8c98.js");
523
523
  const runner = new ContextRunner;
524
524
  const guidelineNames = await AkanToolPack2.#guidelineNames();
525
525
  for (const tool of runner.listMcpTools(this.#options.mode, { guidelineNames })) {
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  resolveSsrPageEntriesForApp
4
- } from "./index-0n9q1f23.js";
4
+ } from "./index-r8gp8zk3.js";
5
5
  import {
6
6
  computeRouteSeedIndex,
7
7
  saveRouteSeedIndex,
@@ -16,7 +16,7 @@ import {
16
16
  CsrEntryFiles,
17
17
  PagesEntrySourceGenerator,
18
18
  PagesBundleBuilder
19
- } from "./index-5kjsr2pg.js";
19
+ } from "./index-ttn00pr3.js";
20
20
  import {
21
21
  SOURCE_EXTS
22
22
  } from "./index-48k96p55.js";
@@ -1640,7 +1640,8 @@ class SsrBaseArtifactBuilder {
1640
1640
  web: SsrBaseArtifactBuilder.servedWeb(this.#command, akanConfig.web),
1641
1641
  deepLinkAssociations: Object.values(akanConfig.mobile.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
1642
1642
  targetName: target.name,
1643
- appId: target.appId,
1643
+ iosAppId: SsrBaseArtifactBuilder.#appIdOn(target.appId, "ios"),
1644
+ androidAppId: SsrBaseArtifactBuilder.#appIdOn(target.appId, "android"),
1644
1645
  domains: target.deepLinks?.domains ?? [],
1645
1646
  iosTeamId: target.deepLinks?.ios?.teamId,
1646
1647
  androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
@@ -1736,6 +1737,9 @@ ${preparedCssText}`).toString(36);
1736
1737
  this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
1737
1738
  return [basePath, { cssUrl, cssRelPath }];
1738
1739
  }
1740
+ static #appIdOn(appId, platform) {
1741
+ return typeof appId === "string" ? appId : appId[platform] ?? appId.default ?? null;
1742
+ }
1739
1743
  static #warnDroppedImports(app, cssRelPath, css, imported) {
1740
1744
  for (const { cssPath, declaredNames } of imported) {
1741
1745
  if (declaredNames.length === 0 || declaredNames.some((name) => css.includes(`${name}:`)))