@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.
- package/.build-stamp +1 -1
- package/{CodeAgent-h19atbr3.js → CodeAgent-86q8d13j.js} +2 -2
- package/{CodeTui-46zygq8p.js → CodeTui-r8tgxsyb.js} +1 -1
- package/{agent.command-rp505zxa.js → agent.command-q9bmqrag.js} +5 -5
- package/akanCode.js +2 -2
- package/{application.command-1p0cpt6y.js → application.command-712jd2z8.js} +70 -8
- package/{applicationBuildRunner-r6b9rkb8.js → applicationBuildRunner-ez37p161.js} +67 -22
- package/buildBatch.proc.js +3 -3
- package/{cloud.command-ps9dabbg.js → cloud.command-8eegwcsc.js} +9 -9
- package/cloud.script-55s1cmz9.js +20 -0
- package/{code.command-78qw3q3f.js → code.command-n0c2tyzx.js} +6 -6
- package/commandManifest.json +1 -1
- package/{context.command-x5f7t3gk.js → context.command-vxf6d7q2.js} +13 -13
- package/context.runner-70r2fmty.js +21 -0
- package/{guideline.command-d5qmxx29.js → guideline.command-ene55fkr.js} +2 -2
- package/guidelines/conventions/conventions.instruction.md +27 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +116 -1
- package/incrementalBuilder.proc.js +2 -2
- package/{index-hzy4pe24.js → index-0pqzrbpr.js} +2 -2
- package/{index-w5pet0nq.js → index-16k7m3cs.js} +11 -9
- package/index-1hs6jrys.js +994 -0
- package/{index-6dt6zft6.js → index-20r2g1t3.js} +4 -4
- package/{index-xfgdmm5d.js → index-2ctqegdm.js} +2 -2
- package/{index-d65fzxvd.js → index-2s2e5qnd.js} +1 -1
- package/{index-j9z8vg2n.js → index-2yvy9ztj.js} +4 -4
- package/{index-t02g6dpn.js → index-2z30v4k5.js} +3 -3
- package/{index-vxqe24mp.js → index-8ej6r02m.js} +1 -1
- package/{index-58cyna2s.js → index-b68zfsvz.js} +1 -1
- package/{index-bp9b1g2r.js → index-fy42hyb9.js} +7 -3
- package/{index-tg09x14f.js → index-g44da4r2.js} +5 -5
- package/{index-zazvn45w.js → index-genegfnt.js} +4 -4
- package/{index-da72pfdv.js → index-h9ed99ax.js} +7 -7
- package/{index-per2kqy9.js → index-k7809wmg.js} +2 -2
- package/{index-2zcm7fjd.js → index-krwz9ek9.js} +3 -3
- package/{index-118vq9dh.js → index-n9gxx7bp.js} +247 -47
- package/{index-860zjnr1.js → index-nbg9bfnr.js} +1 -1
- package/{index-5mm3g24t.js → index-qtz88kh0.js} +2 -2
- package/{index-0n9q1f23.js → index-r8gp8zk3.js} +231 -98
- package/{index-ggfhz9ab.js → index-rbeahw6j.js} +3 -3
- package/{index-56xwsgyn.js → index-rrt9pnb4.js} +2 -2
- package/{index-v4hnkqz9.js → index-ss828r32.js} +1 -1
- package/{index-5kjsr2pg.js → index-ttn00pr3.js} +38 -3
- package/index.js +21 -21
- package/{library.command-sv6g1h2g.js → library.command-tz7vnwea.js} +3 -3
- package/{localRegistry.command-zppa6w7q.js → localRegistry.command-7dd85czz.js} +8 -8
- package/{module.command-ccdeder9.js → module.command-qhc9ns0s.js} +5 -5
- package/{package.command-xmw605b1.js → package.command-z3epnvas.js} +3 -3
- package/package.json +3 -3
- package/{page.command-ky7vafsn.js → page.command-exd2vakz.js} +3 -3
- package/{primitive.command-bh6j8nfs.js → primitive.command-0w6cha4j.js} +6 -6
- package/{quality.command-rqqrpe98.js → quality.command-je4kc9yv.js} +2 -2
- package/{repair.command-n9nsm9qe.js → repair.command-knvwfgtg.js} +5 -5
- package/{scalar.command-t09gmck8.js → scalar.command-60nxfchc.js} +4 -4
- package/{subspace.command-gx2cjpnv.js → subspace.command-683qxcw9.js} +4 -4
- package/templates/server.ts +1 -1
- package/{tunnel.command-4g95vhsn.js → tunnel.command-7m2kxtat.js} +2 -2
- package/{workflow.command-b91ph8sx.js → workflow.command-j5tk13ds.js} +10 -10
- package/{workspace.command-1r29qzzk.js → workspace.command-jxdm0wrb.js} +20 -20
- package/README.ko.md +0 -72
- package/README.md +0 -88
- package/cloud.script-wrw1kz6k.js +0 -20
- package/context.runner-nfs8ksh3.js +0 -21
- package/index-y736f42m.js +0 -556
|
@@ -37,27 +37,29 @@ import {
|
|
|
37
37
|
import {
|
|
38
38
|
runner,
|
|
39
39
|
script
|
|
40
|
-
} from "./index-
|
|
40
|
+
} from "./index-b68zfsvz.js";
|
|
41
41
|
import {
|
|
42
42
|
WorkspaceExecutor,
|
|
43
43
|
AppExecutor,
|
|
44
44
|
LibExecutor,
|
|
45
45
|
PkgExecutor
|
|
46
|
-
} from "./index-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
1003
|
-
|
|
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-
|
|
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
|
-
|
|
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, {
|
|
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
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
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.#
|
|
2057
|
-
const running = await nativeApp.run(platform, {
|
|
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
|
|
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
|
-
|
|
2067
|
-
|
|
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
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.`);
|