@akanjs/cli 3.0.0-beta.2 → 3.0.0-beta.21
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-q2m47rqp.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-pdqhc3sn.js +1457 -0
- package/README.md +3 -0
- package/TunnelShare-scf4e4pd.js +9 -0
- package/{agent.command-d2gfxfck.js → agent.command-ttn09rx3.js} +8 -9
- package/akanCode.js +102 -0
- package/application.command-7gweenv7.js +179 -0
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-38exmejd.js} +63 -64
- package/buildBatch.proc.js +26 -25
- package/cloud.command-bhztw7c2.js +95 -0
- package/cloud.script-aabyhgba.js +20 -0
- package/code.command-6s8c0k91.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-phmgywt2.js +48 -0
- package/context.runner-yvyw5g2d.js +21 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-ye2e6pdz.js} +2 -2
- package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
- package/fileSys-5ngz93ga.js +9 -0
- package/guideline.command-7jk47fgq.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +209 -28
- package/guidelines/conventions/conventions.instruction.md +96 -29
- package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/guidelines/queryRule/queryRule.instruction.md +17 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +77 -3
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +51 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
- package/incrementalBuilder.proc.js +31 -48
- package/{index-h7yw2z9n.js → index-0bq14vr1.js} +8 -14
- package/{index-259dsnz3.js → index-1db2cw0y.js} +5 -8
- package/index-22d57v4c.js +266 -0
- package/index-3hx1z96c.js +87 -0
- package/{index-kcxmv48c.js → index-3t7g9e4r.js} +45 -79
- package/{index-ne2qy0nk.js → index-4dn1p115.js} +12 -12
- package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
- package/index-7cqjvraf.js +69 -0
- package/index-7md2g9vw.js +6 -0
- package/{index-zry5nz81.js → index-8kb5p54p.js} +51 -30
- package/{index-ktb5njgy.js → index-9629w7ns.js} +49 -99
- package/index-9dp031by.js +12 -0
- package/{index-a64esmtb.js → index-aadpbnt2.js} +172 -308
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/index-c6eggbee.js +123 -0
- package/index-d0arnrm8.js +700 -0
- package/index-d2242ksy.js +99 -0
- package/{index-600zhnve.js → index-dg97qp24.js} +70 -200
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-w8kmx1b6.js → index-fgq75haa.js} +198 -290
- package/{index-2tv3fdx4.js → index-ga6hygxr.js} +944 -1333
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/index-hr5n7t4x.js +465 -0
- package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
- package/{index-7737gxcp.js → index-m0ms3n1c.js} +107 -241
- package/{index-ws5e0j51.js → index-mgfx3mfs.js} +43 -26
- package/index-p6pbz9n5.js +25 -0
- package/index-pp54p5zk.js +53 -0
- package/{index-j9yhg404.js → index-q44jbw48.js} +4 -7
- package/{index-mak3cd28.js → index-rmcw8yc8.js} +137 -255
- package/{index-pnqbkf5p.js → index-rnt66vym.js} +6 -2
- package/index-smme4r47.js +49 -0
- package/index-tgjy8xf3.js +187 -0
- package/index-v5eayqqw.js +2729 -0
- package/index-wa5jbxj1.js +760 -0
- package/{index-4edzf1gr.js → index-wp43xrjv.js} +77 -150
- package/{index-fz5q3gd7.js → index-ya5qbmyg.js} +10 -17
- package/index-yjkte9aj.js +1119 -0
- package/{index-gyd220p2.js → index-yy4s1j72.js} +554 -577
- package/{index-v0qh106m.js → index-z50k041j.js} +580 -862
- package/index.js +23 -22
- package/{library.command-nbsky672.js → library.command-ccb1cp92.js} +4 -5
- package/{localRegistry.command-yeq57wjr.js → localRegistry.command-ajb9n87j.js} +23 -42
- package/{module.command-29vqzrh8.js → module.command-5k6h407d.js} +8 -11
- package/{package.command-axv0abmn.js → package.command-e6ntvpez.js} +6 -7
- package/package.json +14 -6
- package/{page.command-vs9s3zrh.js → page.command-v4f90wb8.js} +4 -5
- package/{primitive.command-cbxmqbtc.js → primitive.command-g6km57q5.js} +9 -22
- package/prompter-ptaa3nk0.js +8 -0
- package/{quality.command-gmbs7nws.js → quality.command-de3p36pz.js} +205 -377
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-bqsjptsj.js → repair.command-d3x1javq.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-6k1vhe9s.js → scalar.command-7ezrac2b.js} +7 -10
- package/skills/akan-code/SKILL.md +130 -0
- package/skills/akan-debug/SKILL.md +42 -0
- package/skills/akan-deps/SKILL.md +50 -0
- package/skills/akan-err/SKILL.md +39 -0
- package/skills/akan-module/SKILL.md +45 -0
- package/skills/akan-scaffold/SKILL.md +76 -0
- package/skills/akan-scalar/SKILL.md +52 -0
- package/skills/akan-service/SKILL.md +34 -0
- package/skills/akan-store/SKILL.md +67 -0
- package/skills/akan-ui/SKILL.md +63 -0
- package/skills/akan-validate/SKILL.md +44 -0
- package/{subspace.command-fwjfcts7.js → subspace.command-ps71ynp6.js} +138 -67
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +1 -1
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
- package/templates/client.ts +7 -30
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/server.ts +0 -2
- package/templates/service/__model__.abstract.md.template +8 -0
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/tunnel.command-hsrv2mny.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-z2p32nmf.js +79 -0
- package/{workspace.command-8s050m0h.js → workspace.command-tmdqkrh0.js} +77 -109
- package/abstractCompactor-mjh5ppsq.js +0 -101
- package/application.command-pj2m7584.js +0 -204
- package/applicationReleasePackager-4yasxwv9.js +0 -244
- package/capacitorApp-36rbr858.js +0 -12
- package/cloud.command-jshwdq22.js +0 -93
- package/context.command-ez8a0zyz.js +0 -80
- package/devTuiView-c08ccbat.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/guideline.command-xmqwx8zm.js +0 -359
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
- package/index-2hq52jq3.js +0 -465
- package/index-47pj3aee.js +0 -76
- package/index-6wwzs1bc.js +0 -7
- package/index-8pm30m73.js +0 -841
- package/index-kn1h7p1x.js +0 -560
- package/index-meh558jy.js +0 -65
- package/index-rwg2aa4f.js +0 -229
- package/index-wt7qrtd4.js +0 -1516
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
- package/workflow.command-nwpcdc8p.js +0 -107
|
@@ -1,178 +1,75 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
ApplicationBuildReporter,
|
|
4
|
+
BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS,
|
|
5
|
+
SOURCE_EXTS,
|
|
6
|
+
shouldRestartBackendByDevPlan,
|
|
7
|
+
shouldRestartBuilderByDevPlan,
|
|
8
|
+
shouldAbandonBackendRecovery,
|
|
9
|
+
normalizeBackendReportedGeneration,
|
|
10
|
+
shouldRestartDevHostByDevPlan,
|
|
11
|
+
devHostStateOf,
|
|
12
|
+
isLegacyBackendFallbackFile,
|
|
13
|
+
shouldMarkBuildPhaseRecovered,
|
|
14
|
+
createBackendBuildStatus,
|
|
15
|
+
backendRestartReasonFromMessage,
|
|
16
|
+
mergeBackendRestartReasons,
|
|
17
|
+
shouldReplaceLastGoodMessage,
|
|
18
|
+
shouldQueueBuildStatusReplay,
|
|
19
|
+
hasBuildFailureForGeneration,
|
|
20
|
+
decideBuilderRssRecycle,
|
|
21
|
+
decideBuilderRssSettle,
|
|
22
|
+
decideIdleSuspend,
|
|
23
|
+
resolveIdleSuspendMs,
|
|
24
|
+
hasAnyBuildFailure,
|
|
25
|
+
shouldRefreshConfigOnIdleWake,
|
|
26
|
+
shouldHoldForReturningBuilder,
|
|
27
|
+
shouldWarnBuilderRssCeilingTight,
|
|
28
|
+
isRssCeilingUnreachable,
|
|
29
|
+
shouldRelayRecycledFrontendState,
|
|
30
|
+
mergeInvalidateMessages,
|
|
31
|
+
buildStatusReplaySequence,
|
|
32
|
+
filesChangedSince,
|
|
33
|
+
HmrWatcher,
|
|
34
|
+
WatchRootResolver
|
|
35
|
+
} from "./index-khpfpcsd.js";
|
|
2
36
|
import {
|
|
3
37
|
runner,
|
|
4
38
|
script
|
|
5
|
-
} from "./index-
|
|
39
|
+
} from "./index-rmcw8yc8.js";
|
|
6
40
|
import {
|
|
7
41
|
WorkspaceExecutor,
|
|
8
42
|
AppExecutor,
|
|
9
43
|
LibExecutor,
|
|
10
44
|
PkgExecutor
|
|
11
|
-
} from "./index-
|
|
45
|
+
} from "./index-ga6hygxr.js";
|
|
12
46
|
import {
|
|
13
47
|
LibraryScript
|
|
14
|
-
} from "./index-
|
|
48
|
+
} from "./index-1db2cw0y.js";
|
|
15
49
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "./index-
|
|
50
|
+
resolveMobileTargets,
|
|
51
|
+
NativeApp
|
|
52
|
+
} from "./index-hr5n7t4x.js";
|
|
53
|
+
import {
|
|
54
|
+
stripAnsi
|
|
55
|
+
} from "./index-7md2g9vw.js";
|
|
19
56
|
import {
|
|
20
57
|
SlicePlanner,
|
|
21
58
|
formatSlicePlan
|
|
22
|
-
} from "./index-
|
|
59
|
+
} from "./index-q44jbw48.js";
|
|
23
60
|
import {
|
|
24
|
-
|
|
25
|
-
} from "./index-
|
|
26
|
-
|
|
27
|
-
// pkgs/@akanjs/cli/openBrowser.ts
|
|
28
|
-
import { spawn } from "child_process";
|
|
29
|
-
function openBrowser(url) {
|
|
30
|
-
const command = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
|
|
31
|
-
const child = spawn(command[0], command.slice(1), { detached: true, stdio: "ignore" });
|
|
32
|
-
child.on("error", () => {});
|
|
33
|
-
child.unref();
|
|
34
|
-
return Promise.resolve();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// pkgs/@akanjs/cli/application/devLogBuffer.ts
|
|
38
|
-
var ansi = new RegExp(`${String.fromCharCode(27)}(?:[@-Z\\\\-_]|\\[[\\s\\S]*?[@-~])`, "g");
|
|
39
|
-
var stripAnsi = (text) => text.replace(ansi, "");
|
|
40
|
-
var plainTextOf = (lines, { withApp = false } = {}) => {
|
|
41
|
-
const width = withApp ? lines.reduce((max, line) => Math.max(max, line.app.length), 0) : 0;
|
|
42
|
-
return lines.map((line) => `${withApp ? `${line.app.padEnd(width)} \u2502 ` : ""}${stripAnsi(line.text)}`).join(`
|
|
43
|
-
`);
|
|
44
|
-
};
|
|
45
|
-
var HOST_SOURCE = "host";
|
|
46
|
-
var childPrefix = /^\[child:(\d+) ([a-z-]+)\] \[(?:stdout|stderr)\] /;
|
|
47
|
-
var sourceOf = (text) => {
|
|
48
|
-
const match = childPrefix.exec(text);
|
|
49
|
-
return match ? `#${match[1]} ${match[2]}` : HOST_SOURCE;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
class DevLogBuffer {
|
|
53
|
-
static defaultLimit = 5000;
|
|
54
|
-
#limit;
|
|
55
|
-
#lines = [];
|
|
56
|
-
#partial = new Map;
|
|
57
|
-
#sources = new Map;
|
|
58
|
-
#seq = 0;
|
|
59
|
-
constructor({ limit = DevLogBuffer.defaultLimit } = {}) {
|
|
60
|
-
this.#limit = Math.max(1, limit);
|
|
61
|
-
}
|
|
62
|
-
get size() {
|
|
63
|
-
return this.#lines.length;
|
|
64
|
-
}
|
|
65
|
-
sourcesOf(app) {
|
|
66
|
-
return [...this.#sources.get(app) ?? []];
|
|
67
|
-
}
|
|
68
|
-
push(app, kind, chunk) {
|
|
69
|
-
const key = `${app}:${kind}`;
|
|
70
|
-
const parts = `${this.#partial.get(key) ?? ""}${chunk}`.split(`
|
|
71
|
-
`);
|
|
72
|
-
this.#partial.set(key, parts.pop() ?? "");
|
|
73
|
-
return this.#append(app, kind, parts);
|
|
74
|
-
}
|
|
75
|
-
flushPartials() {
|
|
76
|
-
const flushed = [];
|
|
77
|
-
for (const [key, remainder] of [...this.#partial]) {
|
|
78
|
-
this.#partial.set(key, "");
|
|
79
|
-
if (!remainder)
|
|
80
|
-
continue;
|
|
81
|
-
const split = key.lastIndexOf(":");
|
|
82
|
-
const app = key.slice(0, split);
|
|
83
|
-
const kind = key.slice(split + 1) === "stderr" ? "stderr" : "stdout";
|
|
84
|
-
flushed.push(...this.#append(app, kind, [remainder]));
|
|
85
|
-
}
|
|
86
|
-
return flushed;
|
|
87
|
-
}
|
|
88
|
-
select({ app = null, source = null, grep, errorsOnly = false } = {}) {
|
|
89
|
-
const needle = grep?.trim().toLowerCase();
|
|
90
|
-
return this.#lines.filter((line) => {
|
|
91
|
-
if (app !== null && line.app !== app)
|
|
92
|
-
return false;
|
|
93
|
-
if (source !== null && line.source !== source)
|
|
94
|
-
return false;
|
|
95
|
-
if (errorsOnly && line.kind !== "stderr")
|
|
96
|
-
return false;
|
|
97
|
-
if (needle && !stripAnsi(line.text).toLowerCase().includes(needle))
|
|
98
|
-
return false;
|
|
99
|
-
return true;
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
clear(app = null) {
|
|
103
|
-
const kept = app === null ? [] : this.#lines.filter((line) => line.app !== app);
|
|
104
|
-
this.#lines.length = 0;
|
|
105
|
-
this.#lines.push(...kept);
|
|
106
|
-
}
|
|
107
|
-
#append(app, kind, texts) {
|
|
108
|
-
const known = this.#sources.get(app) ?? new Set;
|
|
109
|
-
this.#sources.set(app, known);
|
|
110
|
-
const added = texts.map((text) => {
|
|
111
|
-
const source = sourceOf(text);
|
|
112
|
-
known.add(source);
|
|
113
|
-
return { seq: ++this.#seq, app, source, kind, text };
|
|
114
|
-
});
|
|
115
|
-
this.#lines.push(...added);
|
|
116
|
-
if (this.#lines.length > this.#limit)
|
|
117
|
-
this.#lines.splice(0, this.#lines.length - this.#limit);
|
|
118
|
-
return added;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// pkgs/@akanjs/devkit/applicationBuildReporter.ts
|
|
123
|
-
import { Logger } from "akanjs/common";
|
|
124
|
-
|
|
125
|
-
class ApplicationBuildReporter {
|
|
126
|
-
static create() {
|
|
127
|
-
return {
|
|
128
|
-
phaseDone: (phase) => Logger.rawLog(ApplicationBuildReporter.formatPhaseLine(phase))
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
static printSummary(result) {
|
|
132
|
-
Logger.rawLog("");
|
|
133
|
-
Logger.rawLog(`Route artifacts: ${result.artifactDir}`);
|
|
134
|
-
Logger.rawLog(`Server output: ${result.outputDir}`);
|
|
135
|
-
Logger.rawLog(`Done in ${ApplicationBuildReporter.formatDuration(result.durationMs)}`);
|
|
136
|
-
}
|
|
137
|
-
static formatError(error) {
|
|
138
|
-
if (error instanceof AggregateError) {
|
|
139
|
-
const nestedMessages = error.errors.map((nestedError, index) => ApplicationBuildReporter.formatError(nestedError).trim() || `Unknown error ${index + 1}`).map((message) => message.replace(/^/gm, " ")).join(`
|
|
140
|
-
`);
|
|
141
|
-
return nestedMessages ? `${error.message}
|
|
142
|
-
${nestedMessages}` : error.message;
|
|
143
|
-
}
|
|
144
|
-
if (error instanceof Error) {
|
|
145
|
-
const causeMessage = error.cause ? `
|
|
146
|
-
Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
|
|
147
|
-
return `${error.message}${causeMessage}`;
|
|
148
|
-
}
|
|
149
|
-
if (typeof error === "object" && error !== null && "message" in error)
|
|
150
|
-
return String(error.message);
|
|
151
|
-
return String(error);
|
|
152
|
-
}
|
|
153
|
-
static formatDuration(ms) {
|
|
154
|
-
if (ms < 1000)
|
|
155
|
-
return `${ms}ms`;
|
|
156
|
-
if (ms < 60000)
|
|
157
|
-
return `${Math.round(ms / 100) / 10}s`;
|
|
158
|
-
const seconds = Math.floor(ms / 1000);
|
|
159
|
-
return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
160
|
-
}
|
|
161
|
-
static formatPhaseLine(phase) {
|
|
162
|
-
const summary = phase.summary ? `: ${phase.summary}` : "";
|
|
163
|
-
return `\u2713 ${phase.label}${summary} (${ApplicationBuildReporter.formatDuration(phase.durationMs)})`;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
61
|
+
openBrowser
|
|
62
|
+
} from "./index-9dp031by.js";
|
|
166
63
|
|
|
167
64
|
// pkgs/@akanjs/cli/application/application.script.ts
|
|
168
65
|
import { confirm } from "@inquirer/prompts";
|
|
169
66
|
import { Logger as Logger6 } from "akanjs/common";
|
|
170
67
|
|
|
171
68
|
// pkgs/@akanjs/cli/application/application.runner.ts
|
|
172
|
-
import
|
|
69
|
+
import path4 from "path";
|
|
173
70
|
|
|
174
71
|
// pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
|
|
175
|
-
import { Logger as
|
|
72
|
+
import { Logger as Logger2 } from "akanjs/common";
|
|
176
73
|
|
|
177
74
|
// pkgs/@akanjs/devkit/sshTunnel.ts
|
|
178
75
|
import { Client } from "ssh2";
|
|
@@ -261,7 +158,7 @@ var createSshTunnel = async (options) => {
|
|
|
261
158
|
for (const socket of sockets)
|
|
262
159
|
closeSocket(socket, error);
|
|
263
160
|
closeQuietly(() => client.end());
|
|
264
|
-
|
|
161
|
+
server.stop();
|
|
265
162
|
};
|
|
266
163
|
client.once("error", (error) => closeTunnel(error));
|
|
267
164
|
client.once("close", () => closeTunnel());
|
|
@@ -281,22 +178,10 @@ var closeBunSocket = (socket, error) => {
|
|
|
281
178
|
socket.data.stream?.end();
|
|
282
179
|
closeQuietly(() => socket.end());
|
|
283
180
|
};
|
|
284
|
-
var closeBunServer = (server) => {
|
|
285
|
-
server.stop();
|
|
286
|
-
};
|
|
287
181
|
|
|
288
182
|
// pkgs/@akanjs/devkit/createTunnel.ts
|
|
289
|
-
var getSshTunnelOptions = (app, environment) => {
|
|
290
|
-
const { serveDomain, repoName } = WorkspaceExecutor.getBaseDevEnv();
|
|
291
|
-
return {
|
|
292
|
-
host: `${app.name}-${environment}.${serveDomain}`,
|
|
293
|
-
port: process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767,
|
|
294
|
-
username: process.env.SSH_TUNNEL_USERNAME ?? "root",
|
|
295
|
-
password: process.env.SSH_TUNNEL_PASSWORD ?? repoName
|
|
296
|
-
};
|
|
297
|
-
};
|
|
298
183
|
var createTunnel = async (service, { app, environment, port = service === "postgres" ? 5432 : 6379 }) => {
|
|
299
|
-
const
|
|
184
|
+
const { serveDomain, repoName } = WorkspaceExecutor.getBaseDevEnv();
|
|
300
185
|
await createSshTunnel({
|
|
301
186
|
localHost: "0.0.0.0",
|
|
302
187
|
localPort: port,
|
|
@@ -304,13 +189,18 @@ var createTunnel = async (service, { app, environment, port = service === "postg
|
|
|
304
189
|
srcPort: port,
|
|
305
190
|
dstHost: `${service}-0.${service}-svc.${app.name}-${environment}.svc.cluster.local`,
|
|
306
191
|
dstPort: service === "postgres" ? 5432 : 6379,
|
|
307
|
-
sshOptions
|
|
192
|
+
sshOptions: {
|
|
193
|
+
host: `${app.name}-${environment}.${serveDomain}`,
|
|
194
|
+
port: process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767,
|
|
195
|
+
username: process.env.SSH_TUNNEL_USERNAME ?? "root",
|
|
196
|
+
password: process.env.SSH_TUNNEL_PASSWORD ?? repoName
|
|
197
|
+
}
|
|
308
198
|
});
|
|
309
199
|
return `localhost:${port}`;
|
|
310
200
|
};
|
|
311
201
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.host.ts
|
|
312
202
|
import path from "path";
|
|
313
|
-
import { Logger
|
|
203
|
+
import { Logger } from "akanjs/common";
|
|
314
204
|
import { MemoryLimit } from "akanjs/server/memoryLimit";
|
|
315
205
|
var builderMsgTypeSet = new Set([
|
|
316
206
|
"build-route-res",
|
|
@@ -328,7 +218,7 @@ class IncrementalBuilderHost {
|
|
|
328
218
|
static #restartMaxDelayMs = 30000;
|
|
329
219
|
static #recycleDrainTimeoutMs = 30000;
|
|
330
220
|
static #devMaxRssBytes = 1200 * 1024 * 1024;
|
|
331
|
-
logger = new
|
|
221
|
+
logger = new Logger("IncrementalBuilderHost");
|
|
332
222
|
entry;
|
|
333
223
|
env;
|
|
334
224
|
app;
|
|
@@ -518,12 +408,8 @@ class IncrementalBuilderHost {
|
|
|
518
408
|
const lost = [...this.#inFlight];
|
|
519
409
|
this.#inFlight.clear();
|
|
520
410
|
this.logger.warn(`failing ${lost.length} unanswered builder request(s): ${reason}`);
|
|
521
|
-
for (const [id, type] of lost)
|
|
522
|
-
|
|
523
|
-
this.#onMessage({ type: "build-route-res", id, ok: false, error: reason });
|
|
524
|
-
else
|
|
525
|
-
this.#onMessage({ type: "build-csr-res", id, ok: false, error: reason });
|
|
526
|
-
}
|
|
411
|
+
for (const [id, type] of lost)
|
|
412
|
+
this.#onMessage({ type: `${type}-res`, id, ok: false, error: reason });
|
|
527
413
|
}
|
|
528
414
|
stop() {
|
|
529
415
|
this.#manualStop = true;
|
|
@@ -592,211 +478,7 @@ class BuilderRequestRouter {
|
|
|
592
478
|
|
|
593
479
|
// pkgs/@akanjs/devkit/akanApp/BackendImportGraph.ts
|
|
594
480
|
import { stat } from "fs/promises";
|
|
595
|
-
import path3 from "path";
|
|
596
|
-
|
|
597
|
-
// pkgs/@akanjs/devkit/akanApp/devHostPolicy.ts
|
|
598
481
|
import path2 from "path";
|
|
599
|
-
var BACKEND_RECOVERY_MAX_ATTEMPTS = 5;
|
|
600
|
-
var HELD_BUILDER_REQUEST_LIMIT = 64;
|
|
601
|
-
var BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS = 30000;
|
|
602
|
-
var BUILDER_TIGHT_RSS_REPORT_LIMIT = 2;
|
|
603
|
-
var BUILDER_RSS_HARD_MULTIPLE = 1.5;
|
|
604
|
-
var DEV_IDLE_SUSPEND_MS = 300000;
|
|
605
|
-
var DEV_IDLE_MIN_UPTIME_MS = 30000;
|
|
606
|
-
var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
607
|
-
var SERVER_SUFFIXES = [".service.ts", ".document.ts"];
|
|
608
|
-
var SHARED_SUFFIXES = [".constant.ts", ".dictionary.ts", ".signal.ts"];
|
|
609
|
-
var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
|
|
610
|
-
var shouldRestartBackendByDevPlan = (message) => {
|
|
611
|
-
if (!message.devPlan)
|
|
612
|
-
return null;
|
|
613
|
-
if (message.devPlan.actions.includes("report-error"))
|
|
614
|
-
return false;
|
|
615
|
-
if (message.devPlan.actions.includes("restart-builder"))
|
|
616
|
-
return false;
|
|
617
|
-
return message.devPlan.actions.includes("restart-backend");
|
|
618
|
-
};
|
|
619
|
-
var shouldRestartBuilderByDevPlan = (message) => message.devPlan?.actions.includes("restart-builder") ?? false;
|
|
620
|
-
var shouldAbandonBackendRecovery = (attempts, maxAttempts = BACKEND_RECOVERY_MAX_ATTEMPTS) => attempts >= maxAttempts;
|
|
621
|
-
var normalizeBackendReportedGeneration = (generation) => generation >= 0 ? generation : undefined;
|
|
622
|
-
var shouldRestartDevHostByDevPlan = (message) => message.devPlan?.actions.includes("restart-dev-host") ?? message.kinds.includes("config");
|
|
623
|
-
var devHostStateByBackendState = {
|
|
624
|
-
starting: "starting",
|
|
625
|
-
ready: "ready",
|
|
626
|
-
"restart-pending": "restarting",
|
|
627
|
-
recovering: "recovering",
|
|
628
|
-
stopping: "stopped",
|
|
629
|
-
stopped: "stopped"
|
|
630
|
-
};
|
|
631
|
-
var devHostStateOf = (state, gaveUp) => state === "stopped" && gaveUp ? "failed" : devHostStateByBackendState[state];
|
|
632
|
-
var RESTART_ROLE_ORDER = ["server", "shared", "barrel", "config"];
|
|
633
|
-
var generationValue = (generation) => generation ?? -1;
|
|
634
|
-
var isLegacyBackendFallbackFile = (file, workspaceRoot) => {
|
|
635
|
-
const abs = path2.resolve(file);
|
|
636
|
-
const ext = path2.extname(abs).toLowerCase();
|
|
637
|
-
if (!SOURCE_EXTS.has(ext))
|
|
638
|
-
return false;
|
|
639
|
-
const rel = path2.relative(path2.resolve(workspaceRoot), abs);
|
|
640
|
-
if (!rel || rel.startsWith("..") || path2.isAbsolute(rel))
|
|
641
|
-
return false;
|
|
642
|
-
const parts = rel.split(path2.sep).filter(Boolean);
|
|
643
|
-
const [scope] = parts;
|
|
644
|
-
if (scope !== "apps" && scope !== "libs" && scope !== "pkgs")
|
|
645
|
-
return false;
|
|
646
|
-
const base = path2.basename(abs);
|
|
647
|
-
return parts.includes("srvkit") || parts.includes("common") || SERVER_SUFFIXES.some((suffix) => base.endsWith(suffix)) || SHARED_SUFFIXES.some((suffix) => base.endsWith(suffix)) || RUNTIME_METADATA_BASENAMES.has(base) || base === "main.ts" || base === "server.ts";
|
|
648
|
-
};
|
|
649
|
-
var shouldMarkBuildPhaseRecovered = (previousByPhase, status) => {
|
|
650
|
-
const previous = previousByPhase.get(status.phase);
|
|
651
|
-
return Boolean(previous && status.ok && !previous.ok && generationValue(status.generation) >= previous.generation);
|
|
652
|
-
};
|
|
653
|
-
var createBackendBuildStatus = ({
|
|
654
|
-
generation,
|
|
655
|
-
ok,
|
|
656
|
-
files = [],
|
|
657
|
-
message
|
|
658
|
-
}) => ({
|
|
659
|
-
generation,
|
|
660
|
-
phase: "backend",
|
|
661
|
-
ok,
|
|
662
|
-
files,
|
|
663
|
-
message
|
|
664
|
-
});
|
|
665
|
-
var backendRestartReasonFromMessage = (message) => {
|
|
666
|
-
const roleSet = new Set;
|
|
667
|
-
for (const role of message.devPlan?.roles ?? []) {
|
|
668
|
-
if (role === "server" || role === "shared" || role === "barrel" || role === "config")
|
|
669
|
-
roleSet.add(role);
|
|
670
|
-
}
|
|
671
|
-
return {
|
|
672
|
-
generation: message.devPlan?.generation ?? message.generation,
|
|
673
|
-
files: [...new Set(message.files)].sort(),
|
|
674
|
-
roles: RESTART_ROLE_ORDER.filter((role) => roleSet.has(role))
|
|
675
|
-
};
|
|
676
|
-
};
|
|
677
|
-
var mergeBackendRestartReasons = (current, next) => ({
|
|
678
|
-
generation: generationValue(next.generation) >= generationValue(current?.generation) ? next.generation : current?.generation,
|
|
679
|
-
files: [...new Set([...current?.files ?? [], ...next.files])].sort(),
|
|
680
|
-
roles: RESTART_ROLE_ORDER.filter((role) => current?.roles.includes(role) || next.roles.includes(role))
|
|
681
|
-
});
|
|
682
|
-
var shouldReplaceLastGoodMessage = (current, next) => !current || generationValue(next.data.generation) >= generationValue(current.data.generation);
|
|
683
|
-
var shouldQueueBuildStatusReplay = (backendReady, pendingReplayCount) => !backendReady || pendingReplayCount > 0;
|
|
684
|
-
var hasBuildFailureForGeneration = (statusByPhase, generation) => {
|
|
685
|
-
if (typeof generation !== "number")
|
|
686
|
-
return false;
|
|
687
|
-
for (const status of statusByPhase.values()) {
|
|
688
|
-
if (!status.ok && status.generation === generation)
|
|
689
|
-
return true;
|
|
690
|
-
}
|
|
691
|
-
return false;
|
|
692
|
-
};
|
|
693
|
-
var decideBuilderRssRecycle = ({
|
|
694
|
-
rssBytes,
|
|
695
|
-
ceilingBytes,
|
|
696
|
-
buildFailed,
|
|
697
|
-
msSinceLastRecycle,
|
|
698
|
-
minIntervalMs = BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS
|
|
699
|
-
}) => {
|
|
700
|
-
if (!ceilingBytes)
|
|
701
|
-
return "unbounded";
|
|
702
|
-
if (rssBytes < ceilingBytes)
|
|
703
|
-
return "below-ceiling";
|
|
704
|
-
if (buildFailed)
|
|
705
|
-
return "build-failed";
|
|
706
|
-
if (msSinceLastRecycle !== null && msSinceLastRecycle < minIntervalMs)
|
|
707
|
-
return "too-soon";
|
|
708
|
-
return "recycle";
|
|
709
|
-
};
|
|
710
|
-
var decideBuilderRssSettle = ({
|
|
711
|
-
rssBytes,
|
|
712
|
-
ceilingBytes,
|
|
713
|
-
hardMultiple = BUILDER_RSS_HARD_MULTIPLE
|
|
714
|
-
}) => rssBytes >= ceilingBytes * hardMultiple ? "recycle-now" : "wait-and-recheck";
|
|
715
|
-
var decideIdleSuspend = ({
|
|
716
|
-
enabled,
|
|
717
|
-
suspended,
|
|
718
|
-
builderReady,
|
|
719
|
-
backendReady,
|
|
720
|
-
buildFailed,
|
|
721
|
-
restartPending,
|
|
722
|
-
msSinceWake,
|
|
723
|
-
minUptimeMs = DEV_IDLE_MIN_UPTIME_MS
|
|
724
|
-
}) => {
|
|
725
|
-
if (!enabled)
|
|
726
|
-
return "disabled";
|
|
727
|
-
if (suspended)
|
|
728
|
-
return "already-suspended";
|
|
729
|
-
if (!builderReady)
|
|
730
|
-
return "builder-not-ready";
|
|
731
|
-
if (!backendReady)
|
|
732
|
-
return "backend-not-ready";
|
|
733
|
-
if (buildFailed)
|
|
734
|
-
return "build-failed";
|
|
735
|
-
if (restartPending)
|
|
736
|
-
return "restart-pending";
|
|
737
|
-
if (msSinceWake !== null && msSinceWake < minUptimeMs)
|
|
738
|
-
return "too-soon";
|
|
739
|
-
return "suspend";
|
|
740
|
-
};
|
|
741
|
-
var resolveIdleSuspendMs = (raw) => {
|
|
742
|
-
if (raw === undefined || raw === "")
|
|
743
|
-
return DEV_IDLE_SUSPEND_MS;
|
|
744
|
-
const parsed = Number(raw);
|
|
745
|
-
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
746
|
-
return null;
|
|
747
|
-
return Math.round(parsed);
|
|
748
|
-
};
|
|
749
|
-
var hasAnyBuildFailure = (statusByPhase) => [...statusByPhase.values()].some((status) => !status.ok);
|
|
750
|
-
var shouldRefreshConfigOnIdleWake = (batch) => !!batch && batch.kinds.has("config");
|
|
751
|
-
var RETURNING_BUILDER_STATUSES = new Set(["starting", "recycling", "restarting"]);
|
|
752
|
-
var shouldHoldForReturningBuilder = ({
|
|
753
|
-
status,
|
|
754
|
-
heldCount,
|
|
755
|
-
limit = HELD_BUILDER_REQUEST_LIMIT
|
|
756
|
-
}) => RETURNING_BUILDER_STATUSES.has(status) && heldCount < limit;
|
|
757
|
-
var shouldWarnBuilderRssCeilingTight = (reportsSinceRecycle, limit = BUILDER_TIGHT_RSS_REPORT_LIMIT) => reportsSinceRecycle >= limit;
|
|
758
|
-
var isRssCeilingUnreachable = (freshRssBytes, ceilingBytes) => freshRssBytes !== null && ceilingBytes !== null && freshRssBytes >= ceilingBytes;
|
|
759
|
-
var shouldRelayRecycledFrontendState = (current, next) => {
|
|
760
|
-
if (!current || current.type !== next.type)
|
|
761
|
-
return true;
|
|
762
|
-
if (current.type === "pages-updated" && next.type === "pages-updated")
|
|
763
|
-
return current.data.bundlePath !== next.data.bundlePath;
|
|
764
|
-
if (current.type === "css-updated" && next.type === "css-updated")
|
|
765
|
-
return JSON.stringify(current.data.cssAssets) !== JSON.stringify(next.data.cssAssets);
|
|
766
|
-
return true;
|
|
767
|
-
};
|
|
768
|
-
var mergeDevPlans = (current, next) => {
|
|
769
|
-
if (!current)
|
|
770
|
-
return next;
|
|
771
|
-
if (!next)
|
|
772
|
-
return current;
|
|
773
|
-
const reasonByFile = { ...current.reasonByFile };
|
|
774
|
-
for (const [file, reasons] of Object.entries(next.reasonByFile)) {
|
|
775
|
-
reasonByFile[file] = [...new Set([...reasonByFile[file] ?? [], ...reasons])].sort();
|
|
776
|
-
}
|
|
777
|
-
return {
|
|
778
|
-
generation: Math.max(current.generation, next.generation),
|
|
779
|
-
files: [...new Set([...current.files, ...next.files])].sort(),
|
|
780
|
-
generatedFiles: [...new Set([...current.generatedFiles, ...next.generatedFiles])].sort(),
|
|
781
|
-
roles: [...new Set([...current.roles, ...next.roles])].sort(),
|
|
782
|
-
actions: [...new Set([...current.actions, ...next.actions])].sort(),
|
|
783
|
-
reasonByFile
|
|
784
|
-
};
|
|
785
|
-
};
|
|
786
|
-
var mergeInvalidateMessages = (current, next) => {
|
|
787
|
-
const generation = Math.max(generationValue(current.generation), generationValue(next.generation));
|
|
788
|
-
return {
|
|
789
|
-
type: "invalidate",
|
|
790
|
-
kinds: [...new Set([...current.kinds, ...next.kinds])].sort(),
|
|
791
|
-
files: [...new Set([...current.files, ...next.files])].sort(),
|
|
792
|
-
generation: generation >= 0 ? generation : undefined,
|
|
793
|
-
devPlan: mergeDevPlans(current.devPlan, next.devPlan)
|
|
794
|
-
};
|
|
795
|
-
};
|
|
796
|
-
var buildStatusReplaySequence = (pendingReplay, latestByPhase) => [...pendingReplay, ...latestByPhase.values()];
|
|
797
|
-
var filesChangedSince = (before, after) => [...before].filter(([file, stamp]) => after.get(file) !== stamp).map(([file]) => file);
|
|
798
|
-
|
|
799
|
-
// pkgs/@akanjs/devkit/akanApp/BackendImportGraph.ts
|
|
800
482
|
var NON_SOURCE_EXT_RE = /\.(css|scss|sass|less|json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav|html)$/i;
|
|
801
483
|
var GRAPH_IMPORT_KINDS = new Set([
|
|
802
484
|
"import-statement",
|
|
@@ -827,7 +509,7 @@ class BackendImportGraph {
|
|
|
827
509
|
return this.#lastRefreshSucceeded;
|
|
828
510
|
}
|
|
829
511
|
has(file) {
|
|
830
|
-
return this.#files.has(
|
|
512
|
+
return this.#files.has(path2.resolve(file));
|
|
831
513
|
}
|
|
832
514
|
async fingerprint() {
|
|
833
515
|
const stamps = await Promise.all([...this.#files].map(async (file) => {
|
|
@@ -855,9 +537,9 @@ class BackendImportGraph {
|
|
|
855
537
|
const roots = await this.#entrypoints();
|
|
856
538
|
const files = new Set;
|
|
857
539
|
const queue = [...roots];
|
|
858
|
-
const workspaceRoot =
|
|
540
|
+
const workspaceRoot = path2.resolve(this.#app.workspace.workspaceRoot);
|
|
859
541
|
while (queue.length > 0) {
|
|
860
|
-
const current =
|
|
542
|
+
const current = path2.resolve(queue.pop());
|
|
861
543
|
if (files.has(current))
|
|
862
544
|
continue;
|
|
863
545
|
if (!this.#isWorkspaceSource(current, workspaceRoot))
|
|
@@ -866,7 +548,7 @@ class BackendImportGraph {
|
|
|
866
548
|
if (!imports)
|
|
867
549
|
continue;
|
|
868
550
|
files.add(current);
|
|
869
|
-
const importerDir =
|
|
551
|
+
const importerDir = path2.dirname(current);
|
|
870
552
|
for (const imp of imports) {
|
|
871
553
|
if (!GRAPH_IMPORT_KINDS.has(imp.kind) || !imp.path || NON_SOURCE_EXT_RE.test(imp.path))
|
|
872
554
|
continue;
|
|
@@ -897,7 +579,7 @@ class BackendImportGraph {
|
|
|
897
579
|
const roots = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
|
|
898
580
|
const existing = [];
|
|
899
581
|
for (const root of roots) {
|
|
900
|
-
const abs =
|
|
582
|
+
const abs = path2.resolve(root);
|
|
901
583
|
if (await Bun.file(abs).exists())
|
|
902
584
|
existing.push(abs);
|
|
903
585
|
}
|
|
@@ -906,25 +588,25 @@ class BackendImportGraph {
|
|
|
906
588
|
#resolve(specifier, importerDir) {
|
|
907
589
|
try {
|
|
908
590
|
const resolved = Bun.resolveSync(specifier, importerDir);
|
|
909
|
-
if (!
|
|
591
|
+
if (!path2.isAbsolute(resolved))
|
|
910
592
|
return null;
|
|
911
|
-
if (!SOURCE_EXTS.has(
|
|
593
|
+
if (!SOURCE_EXTS.has(path2.extname(resolved).toLowerCase()))
|
|
912
594
|
return null;
|
|
913
|
-
return
|
|
595
|
+
return path2.resolve(resolved);
|
|
914
596
|
} catch {
|
|
915
597
|
return null;
|
|
916
598
|
}
|
|
917
599
|
}
|
|
918
600
|
#isWorkspaceSource(file, workspaceRoot) {
|
|
919
|
-
const rel =
|
|
920
|
-
if (rel.startsWith("..") ||
|
|
601
|
+
const rel = path2.relative(workspaceRoot, file);
|
|
602
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel))
|
|
921
603
|
return false;
|
|
922
|
-
if (rel.includes(`${
|
|
604
|
+
if (rel.includes(`${path2.sep}node_modules${path2.sep}`) || rel.includes(`${path2.sep}.akan${path2.sep}`))
|
|
923
605
|
return false;
|
|
924
|
-
return SOURCE_EXTS.has(
|
|
606
|
+
return SOURCE_EXTS.has(path2.extname(file).toLowerCase());
|
|
925
607
|
}
|
|
926
608
|
#scanImports(file, source) {
|
|
927
|
-
const ext =
|
|
609
|
+
const ext = path2.extname(file).toLowerCase();
|
|
928
610
|
if (ext === ".tsx")
|
|
929
611
|
return this.#tsxTranspiler.scanImports(source);
|
|
930
612
|
if (ext === ".jsx")
|
|
@@ -937,6 +619,7 @@ class BackendImportGraph {
|
|
|
937
619
|
|
|
938
620
|
// pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
|
|
939
621
|
var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
|
|
622
|
+
var asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
940
623
|
var BACKEND_RESTART_DEBOUNCE_MS = 120;
|
|
941
624
|
var BACKEND_GRACEFUL_TIMEOUT_MS = 8000;
|
|
942
625
|
var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
|
|
@@ -952,7 +635,7 @@ var BUILDER_RECOVERY_MAX_DELAY_MS = 60000;
|
|
|
952
635
|
|
|
953
636
|
class AkanAppHost {
|
|
954
637
|
app;
|
|
955
|
-
logger = new
|
|
638
|
+
logger = new Logger2("AkanAppHost");
|
|
956
639
|
stdio;
|
|
957
640
|
env;
|
|
958
641
|
#onDevEvent;
|
|
@@ -1030,23 +713,18 @@ class AkanAppHost {
|
|
|
1030
713
|
async stop() {
|
|
1031
714
|
this.#cancelIdleSuspend();
|
|
1032
715
|
this.#stopIdleWatcher();
|
|
1033
|
-
|
|
1034
|
-
clearTimeout(this.#restartTimer);
|
|
1035
|
-
this.#restartTimer = null;
|
|
1036
|
-
}
|
|
1037
|
-
if (this.#backendRecoveryTimer) {
|
|
1038
|
-
clearTimeout(this.#backendRecoveryTimer);
|
|
1039
|
-
this.#backendRecoveryTimer = null;
|
|
1040
|
-
}
|
|
1041
|
-
if (this.#builderRecoveryTimer) {
|
|
1042
|
-
clearTimeout(this.#builderRecoveryTimer);
|
|
1043
|
-
this.#builderRecoveryTimer = null;
|
|
1044
|
-
}
|
|
716
|
+
this.#clearRestartTimers();
|
|
1045
717
|
this.#failPendingBuilderMessages("dev server is shutting down");
|
|
1046
718
|
await this.#stopBackend();
|
|
1047
719
|
this.#stopBuilder();
|
|
1048
720
|
return this;
|
|
1049
721
|
}
|
|
722
|
+
#clearRestartTimers() {
|
|
723
|
+
for (const timer of [this.#restartTimer, this.#backendRecoveryTimer, this.#builderRecoveryTimer])
|
|
724
|
+
if (timer)
|
|
725
|
+
clearTimeout(timer);
|
|
726
|
+
this.#restartTimer = this.#backendRecoveryTimer = this.#builderRecoveryTimer = null;
|
|
727
|
+
}
|
|
1050
728
|
kill() {
|
|
1051
729
|
this.stop();
|
|
1052
730
|
}
|
|
@@ -1342,7 +1020,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1342
1020
|
if (this.#rssCeilingAbandoned)
|
|
1343
1021
|
return;
|
|
1344
1022
|
const ceilingBytes = IncrementalBuilderHost.maxRssBytes();
|
|
1345
|
-
const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
1346
1023
|
const decision = decideBuilderRssRecycle({
|
|
1347
1024
|
rssBytes: metrics.rssBytes,
|
|
1348
1025
|
ceilingBytes,
|
|
@@ -1380,7 +1057,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1380
1057
|
if (!isRssCeilingUnreachable(freshRssBytes, ceilingBytes))
|
|
1381
1058
|
return;
|
|
1382
1059
|
this.#rssCeilingAbandoned = true;
|
|
1383
|
-
const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
1384
1060
|
this.logger.error(`[builder-recycle] a freshly recycled builder is already at ${asMib(freshRssBytes ?? 0)}MiB with nothing built on demand, so the ${asMib(ceilingBytes)}MiB ceiling cannot be met for this app; no longer enforcing it this session. Raise AKAN_BUILDER_MAX_RSS_MB, or set it to 0 to leave the builder unbounded.`);
|
|
1385
1061
|
}
|
|
1386
1062
|
#armRssRecycle(reason, over) {
|
|
@@ -1407,7 +1083,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1407
1083
|
}, BUILDER_RSS_RECYCLE_QUIET_MS);
|
|
1408
1084
|
}
|
|
1409
1085
|
async#recycleBuilderForRssWhenStillOver(reason, { rssBytes, ceilingBytes }) {
|
|
1410
|
-
const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
1411
1086
|
if (decideBuilderRssSettle({ rssBytes, ceilingBytes }) === "recycle-now") {
|
|
1412
1087
|
this.#recycleBuilderForRss(reason);
|
|
1413
1088
|
return;
|
|
@@ -1433,16 +1108,18 @@ ${this.#backendStderrTail.join(`
|
|
|
1433
1108
|
this.#recycleBuilderForRss(settledBytes === null ? reason : `${reason}; still ${asMib(settledBytes)}MiB after settling`);
|
|
1434
1109
|
}
|
|
1435
1110
|
static async readProcessRssBytes(pid) {
|
|
1111
|
+
if (process.platform === "win32")
|
|
1112
|
+
return await AkanAppHost.#readRssKbVia(["tasklist", "/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], AkanAppHost.#tasklistRssKb);
|
|
1436
1113
|
const status = await Bun.file(`/proc/${pid}/status`).text().catch(() => null);
|
|
1437
1114
|
const vmRssKb = status === null ? null : /VmRSS:\s+(\d+) kB/.exec(status)?.[1];
|
|
1438
1115
|
if (vmRssKb)
|
|
1439
1116
|
return Number(vmRssKb) * 1024;
|
|
1440
|
-
return await AkanAppHost.#
|
|
1117
|
+
return await AkanAppHost.#readRssKbVia(["ps", "-o", "rss=", "-p", String(pid)], (output) => Number(output.trim()));
|
|
1441
1118
|
}
|
|
1442
|
-
static async#
|
|
1119
|
+
static async#readRssKbVia(command, parseKb, timeoutMs = PS_RSS_TIMEOUT_MS) {
|
|
1443
1120
|
let proc;
|
|
1444
1121
|
try {
|
|
1445
|
-
proc = Bun.spawn(
|
|
1122
|
+
proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"] });
|
|
1446
1123
|
} catch {
|
|
1447
1124
|
return null;
|
|
1448
1125
|
}
|
|
@@ -1450,7 +1127,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1450
1127
|
try {
|
|
1451
1128
|
const output = await new Response(proc.stdout).text();
|
|
1452
1129
|
await proc.exited;
|
|
1453
|
-
const rssKb =
|
|
1130
|
+
const rssKb = parseKb(output);
|
|
1454
1131
|
return Number.isFinite(rssKb) && rssKb > 0 ? rssKb * 1024 : null;
|
|
1455
1132
|
} catch {
|
|
1456
1133
|
return null;
|
|
@@ -1458,6 +1135,10 @@ ${this.#backendStderrTail.join(`
|
|
|
1458
1135
|
clearTimeout(killer);
|
|
1459
1136
|
}
|
|
1460
1137
|
}
|
|
1138
|
+
static #tasklistRssKb(output) {
|
|
1139
|
+
const memUsage = /"([^"]*)"\s*$/m.exec(output)?.[1];
|
|
1140
|
+
return memUsage ? Number(memUsage.replace(/\D/g, "")) : Number.NaN;
|
|
1141
|
+
}
|
|
1461
1142
|
#cancelRssRecycle() {
|
|
1462
1143
|
this.#rssRecycleReason = null;
|
|
1463
1144
|
this.#rssRecycleOver = null;
|
|
@@ -1593,7 +1274,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1593
1274
|
await this.#backendGraph.refresh();
|
|
1594
1275
|
await this.#startBuilder({ announceBootState: true });
|
|
1595
1276
|
const missed = await this.#takeBuilderGapChanges();
|
|
1596
|
-
const backendFiles = [...new Set([...files.filter((file) => this.#
|
|
1277
|
+
const backendFiles = [...new Set([...files.filter((file) => this.#backendGraph.has(file)), ...missed])];
|
|
1597
1278
|
if (backendFiles.length === 0)
|
|
1598
1279
|
return;
|
|
1599
1280
|
this.logger.verbose(`[idle-suspend] ${backendFiles.length} backend file(s) changed while suspended`);
|
|
@@ -1648,10 +1329,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1648
1329
|
for (const message of pending)
|
|
1649
1330
|
this.#sendToBuilder(message);
|
|
1650
1331
|
}
|
|
1651
|
-
#holdUntilBuilderReady(message) {
|
|
1652
|
-
this.#pendingBuilderMessages.push(message);
|
|
1653
|
-
this.logger.verbose(`[builder] holding ${message.type} until the builder is ready (${this.#pendingBuilderMessages.length} waiting)`);
|
|
1654
|
-
}
|
|
1655
1332
|
#discardPendingBuilderMessages(reason) {
|
|
1656
1333
|
const held = this.#pendingBuilderMessages.splice(0);
|
|
1657
1334
|
if (held.length === 0)
|
|
@@ -1663,12 +1340,17 @@ ${this.#backendStderrTail.join(`
|
|
|
1663
1340
|
if (held.length === 0)
|
|
1664
1341
|
return;
|
|
1665
1342
|
this.logger.warn(`failing ${held.length} held builder request(s): ${reason}`);
|
|
1666
|
-
for (const message of held)
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1343
|
+
for (const message of held)
|
|
1344
|
+
this.#failBuilderRequest(message, reason);
|
|
1345
|
+
}
|
|
1346
|
+
#failBuilderRequest(message, error) {
|
|
1347
|
+
if (message.type === "build-route")
|
|
1348
|
+
this.#sendToBackend({ type: "build-route-res", id: message.id, ok: false, error });
|
|
1349
|
+
else if (message.type === "build-csr")
|
|
1350
|
+
this.#sendToBackend({ type: "build-csr-res", id: message.id, ok: false, error });
|
|
1351
|
+
else
|
|
1352
|
+
return false;
|
|
1353
|
+
return true;
|
|
1672
1354
|
}
|
|
1673
1355
|
#recycleBuilderForRss(reason) {
|
|
1674
1356
|
if (this.#pendingRecycle || this.#restartTimer) {
|
|
@@ -1789,18 +1471,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1789
1471
|
}
|
|
1790
1472
|
async#recycleDevChildren(message, { refreshConfig = false } = {}) {
|
|
1791
1473
|
const generation = message.devPlan?.generation ?? message.generation;
|
|
1792
|
-
|
|
1793
|
-
clearTimeout(this.#restartTimer);
|
|
1794
|
-
this.#restartTimer = null;
|
|
1795
|
-
}
|
|
1796
|
-
if (this.#backendRecoveryTimer) {
|
|
1797
|
-
clearTimeout(this.#backendRecoveryTimer);
|
|
1798
|
-
this.#backendRecoveryTimer = null;
|
|
1799
|
-
}
|
|
1800
|
-
if (this.#builderRecoveryTimer) {
|
|
1801
|
-
clearTimeout(this.#builderRecoveryTimer);
|
|
1802
|
-
this.#builderRecoveryTimer = null;
|
|
1803
|
-
}
|
|
1474
|
+
this.#clearRestartTimers();
|
|
1804
1475
|
this.#builderRecoveryAttempts = 0;
|
|
1805
1476
|
this.#pendingRestartReason = null;
|
|
1806
1477
|
this.#lastGoodFrontend = {};
|
|
@@ -1890,7 +1561,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1890
1561
|
}
|
|
1891
1562
|
if (message.kinds.includes("code"))
|
|
1892
1563
|
await this.#backendGraph.refresh();
|
|
1893
|
-
if (message.files.some((file) => this.#
|
|
1564
|
+
if (message.files.some((file) => this.#backendGraph.has(file)))
|
|
1894
1565
|
return true;
|
|
1895
1566
|
if (!this.#backendGraph.lastRefreshSucceeded) {
|
|
1896
1567
|
const fallbackFiles = message.files.filter((file) => isLegacyBackendFallbackFile(file, this.app.workspace.workspaceRoot));
|
|
@@ -1901,9 +1572,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1901
1572
|
}
|
|
1902
1573
|
return false;
|
|
1903
1574
|
}
|
|
1904
|
-
#isBackendFile(file) {
|
|
1905
|
-
return this.#backendGraph.has(file);
|
|
1906
|
-
}
|
|
1907
1575
|
async#startBuilder({
|
|
1908
1576
|
announceBootState = false
|
|
1909
1577
|
} = {}) {
|
|
@@ -1989,27 +1657,12 @@ ${this.#backendStderrTail.join(`
|
|
|
1989
1657
|
return;
|
|
1990
1658
|
const status = this.#builder?.status ?? "stopped";
|
|
1991
1659
|
if (shouldHoldForReturningBuilder({ status, heldCount: this.#pendingBuilderMessages.length })) {
|
|
1992
|
-
this.#
|
|
1993
|
-
|
|
1994
|
-
}
|
|
1995
|
-
if (message.type === "build-route") {
|
|
1996
|
-
this.#sendToBackend({
|
|
1997
|
-
type: "build-route-res",
|
|
1998
|
-
id: message.id,
|
|
1999
|
-
ok: false,
|
|
2000
|
-
error: `builder is ${status}; reload after the builder is ready`
|
|
2001
|
-
});
|
|
1660
|
+
this.#pendingBuilderMessages.push(message);
|
|
1661
|
+
this.logger.verbose(`[builder] holding ${message.type} until the builder is ready (${this.#pendingBuilderMessages.length} waiting)`);
|
|
2002
1662
|
return;
|
|
2003
1663
|
}
|
|
2004
|
-
if (message
|
|
2005
|
-
this.#sendToBackend({
|
|
2006
|
-
type: "build-csr-res",
|
|
2007
|
-
id: message.id,
|
|
2008
|
-
ok: false,
|
|
2009
|
-
error: `builder is ${status}; reload after the builder is ready`
|
|
2010
|
-
});
|
|
1664
|
+
if (this.#failBuilderRequest(message, `builder is ${status}; reload after the builder is ready`))
|
|
2011
1665
|
return;
|
|
2012
|
-
}
|
|
2013
1666
|
this.logger.warn("akanAppHost builder is not running");
|
|
2014
1667
|
}
|
|
2015
1668
|
#stopBuilder() {
|
|
@@ -2021,25 +1674,20 @@ ${this.#backendStderrTail.join(`
|
|
|
2021
1674
|
}
|
|
2022
1675
|
}
|
|
2023
1676
|
// pkgs/@akanjs/devkit/applicationTestPreload.ts
|
|
2024
|
-
import
|
|
1677
|
+
import path3 from "path";
|
|
2025
1678
|
var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
|
|
2026
1679
|
async function resolveSignalTestPreloadPath(target) {
|
|
2027
1680
|
const candidates = [];
|
|
2028
|
-
const
|
|
1681
|
+
for (const basePath of [target.cwdPath, process.cwd(), path3.dirname(Bun.main), import.meta.dir]) {
|
|
2029
1682
|
try {
|
|
2030
|
-
candidates.push(
|
|
1683
|
+
candidates.push(path3.join(path3.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
|
|
2031
1684
|
} catch {}
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
addResolvedPackageCandidate(process.cwd());
|
|
2035
|
-
addResolvedPackageCandidate(path4.dirname(Bun.main));
|
|
2036
|
-
addResolvedPackageCandidate(import.meta.dir);
|
|
2037
|
-
candidates.push(path4.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.join(path4.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
|
|
1685
|
+
}
|
|
1686
|
+
candidates.push(path3.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.join(path3.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
|
|
2038
1687
|
const uniqueCandidates = [...new Set(candidates)];
|
|
2039
|
-
for (const candidate of uniqueCandidates)
|
|
1688
|
+
for (const candidate of uniqueCandidates)
|
|
2040
1689
|
if (await Bun.file(candidate).exists())
|
|
2041
1690
|
return candidate;
|
|
2042
|
-
}
|
|
2043
1691
|
throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
|
|
2044
1692
|
Probed paths:
|
|
2045
1693
|
${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
|
|
@@ -2047,12 +1695,8 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
|
|
|
2047
1695
|
}
|
|
2048
1696
|
|
|
2049
1697
|
// pkgs/@akanjs/cli/application/application.runner.ts
|
|
2050
|
-
import { Logger as
|
|
2051
|
-
|
|
2052
|
-
var loadBuildRunner = async () => (await import("./applicationBuildRunner-kxwbssgn.js")).ApplicationBuildRunner;
|
|
2053
|
-
var loadReleasePackager = async () => (await import("./applicationReleasePackager-4yasxwv9.js")).ApplicationReleasePackager;
|
|
2054
|
-
var loadCapacitorApp = async () => (await import("./capacitorApp-36rbr858.js")).CapacitorApp;
|
|
2055
|
-
var loadAbstractCompactor = async () => (await import("./abstractCompactor-mjh5ppsq.js")).AbstractCompactor;
|
|
1698
|
+
import { Logger as Logger3 } from "akanjs/common";
|
|
1699
|
+
var loadBuildRunner = async () => (await import("./applicationBuildRunner-38exmejd.js")).ApplicationBuildRunner;
|
|
2056
1700
|
var loadPrompts = async () => await import("@inquirer/prompts");
|
|
2057
1701
|
|
|
2058
1702
|
class ApplicationRunner extends runner("application") {
|
|
@@ -2071,9 +1715,6 @@ class ApplicationRunner extends runner("application") {
|
|
|
2071
1715
|
async removeApplication(app) {
|
|
2072
1716
|
await app.workspace.removeDir(`apps/${app.name}`);
|
|
2073
1717
|
}
|
|
2074
|
-
async getConfig(app) {
|
|
2075
|
-
return await app.getConfig();
|
|
2076
|
-
}
|
|
2077
1718
|
async getScriptFilename(app) {
|
|
2078
1719
|
if (!await app.exists("script")) {
|
|
2079
1720
|
await app.mkdir("script");
|
|
@@ -2095,12 +1736,12 @@ class ApplicationRunner extends runner("application") {
|
|
|
2095
1736
|
if (!await app.exists(scriptPath))
|
|
2096
1737
|
throw new Error(`Script file not found: apps/${app.name}/${scriptPath}`);
|
|
2097
1738
|
await app.spawn("bun", [scriptPath], {
|
|
2098
|
-
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script" }),
|
|
1739
|
+
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script", ...await app.getDatabaseModeEnv() }),
|
|
2099
1740
|
stdio: "inherit"
|
|
2100
1741
|
});
|
|
2101
1742
|
}
|
|
2102
1743
|
#runtimeDirOf(app, override) {
|
|
2103
|
-
return
|
|
1744
|
+
return path4.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path4.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
|
|
2104
1745
|
}
|
|
2105
1746
|
async runLogs(app, options) {
|
|
2106
1747
|
const { LogControlUnavailableError, LogTailClient } = await import("akanjs/server/logging/logTailClient");
|
|
@@ -2110,13 +1751,10 @@ class ApplicationRunner extends runner("application") {
|
|
|
2110
1751
|
const { level, grep, endpoint, trace, child, role, origin, since } = options;
|
|
2111
1752
|
const query = Object.fromEntries(Object.entries({ level, grep, endpoint, trace, child, role, origin, since }).filter(([, value]) => value !== undefined && value !== null && value !== ""));
|
|
2112
1753
|
const print = (record) => process.stdout.write(options.json ? `${JSON.stringify(record)}
|
|
2113
|
-
` :
|
|
1754
|
+
` : Logger3.render(record));
|
|
2114
1755
|
const note = (text) => process.stderr.write(`[akan logs] ${text}
|
|
2115
1756
|
`);
|
|
2116
|
-
|
|
2117
|
-
const closed = new Promise((resolve) => {
|
|
2118
|
-
finish = resolve;
|
|
2119
|
-
});
|
|
1757
|
+
const { promise: closed, resolve: finish } = Promise.withResolvers();
|
|
2120
1758
|
let client;
|
|
2121
1759
|
try {
|
|
2122
1760
|
client = await LogTailClient.connect(socketPath, {
|
|
@@ -2125,11 +1763,11 @@ class ApplicationRunner extends runner("application") {
|
|
|
2125
1763
|
if (response.type === "dropped")
|
|
2126
1764
|
note(`${response.count} records dropped (reader too slow)`);
|
|
2127
1765
|
},
|
|
2128
|
-
onClose: () => finish
|
|
1766
|
+
onClose: () => finish()
|
|
2129
1767
|
});
|
|
2130
1768
|
} catch (error) {
|
|
2131
1769
|
if (error instanceof LogControlUnavailableError)
|
|
2132
|
-
throw new Error(`${app.name} is not running (no ${
|
|
1770
|
+
throw new Error(`${app.name} is not running (no ${path4.basename(socketPath)} in ${runtimeDir}). Start it with \`akan start ${app.name}\`, or pass --runtime-dir for a built app.`);
|
|
2133
1771
|
throw error;
|
|
2134
1772
|
}
|
|
2135
1773
|
const sinceMs = since ? LogQueryMatcher.parseSince(since) : undefined;
|
|
@@ -2167,7 +1805,7 @@ const serverModule = await import(${JSON.stringify(serverPath)});
|
|
|
2167
1805
|
const { assertAkanConsoleAllowed, startAkanConsole } = await import("akanjs/server");
|
|
2168
1806
|
const server = serverModule.server;
|
|
2169
1807
|
if (!server?.start) throw new Error("server.ts must export server with start()");
|
|
2170
|
-
assertAkanConsoleAllowed(
|
|
1808
|
+
assertAkanConsoleAllowed();
|
|
2171
1809
|
await server.start({ listen: false, web: false });
|
|
2172
1810
|
try {
|
|
2173
1811
|
await startAkanConsole(server, {
|
|
@@ -2186,17 +1824,35 @@ try {
|
|
|
2186
1824
|
}
|
|
2187
1825
|
`;
|
|
2188
1826
|
await app.spawn("bun", ["-e", code], {
|
|
2189
|
-
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "console" }),
|
|
1827
|
+
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "console", ...await app.getDatabaseModeEnv() }),
|
|
1828
|
+
stdio: "inherit"
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
async transferDatabase(app, direction, dir) {
|
|
1832
|
+
const serverPath = `${app.cwdPath}/server.ts`;
|
|
1833
|
+
if (!await app.exists("server.ts"))
|
|
1834
|
+
throw new Error(`Server file not found: apps/${app.name}/server.ts`);
|
|
1835
|
+
const target = path4.resolve(app.workspace.workspaceRoot, dir);
|
|
1836
|
+
const code = `
|
|
1837
|
+
const { server } = await import(${JSON.stringify(serverPath)});
|
|
1838
|
+
const { DatabaseAdaptorRole, DocumentTransfer } = await import("akanjs/service");
|
|
1839
|
+
await server.start({ listen: false, web: false });
|
|
1840
|
+
try {
|
|
1841
|
+
const transfer = new DocumentTransfer(server.get(DatabaseAdaptorRole));
|
|
1842
|
+
const reports = await transfer.${direction === "export" ? "exportTo" : "importFrom"}(${JSON.stringify(target)});
|
|
1843
|
+
for (const { table, rows } of reports) console.info(\`${direction === "export" ? "exported" : "imported"} \${table}: \${rows} rows\`);
|
|
1844
|
+
} finally {
|
|
1845
|
+
await server.stop();
|
|
1846
|
+
}
|
|
1847
|
+
`;
|
|
1848
|
+
await app.spawn("bun", ["-e", code], {
|
|
1849
|
+
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script", ...await app.getDatabaseModeEnv() }),
|
|
2190
1850
|
stdio: "inherit"
|
|
2191
1851
|
});
|
|
2192
1852
|
}
|
|
2193
1853
|
async typecheck(app, options = {}) {
|
|
2194
1854
|
await new (await loadBuildRunner())(app).typecheck(options);
|
|
2195
1855
|
}
|
|
2196
|
-
async compact(sys, { module, minLines, interactive } = {}) {
|
|
2197
|
-
const compactor = new (await loadAbstractCompactor())(sys, { minLines, interactive });
|
|
2198
|
-
return await compactor.compactAll({ module });
|
|
2199
|
-
}
|
|
2200
1856
|
async test(exec) {
|
|
2201
1857
|
const isSignalTarget = exec instanceof AppExecutor || exec instanceof LibExecutor;
|
|
2202
1858
|
const preloadPath = isSignalTarget ? await resolveSignalTestPreloadPath(exec) : null;
|
|
@@ -2232,71 +1888,68 @@ try {
|
|
|
2232
1888
|
setTimeout(() => openBrowser(`http://localhost:${env.AKAN_PUBLIC_CLIENT_PORT ?? env.PORT ?? "8282"}`), 3000);
|
|
2233
1889
|
return appHost;
|
|
2234
1890
|
}
|
|
2235
|
-
async
|
|
1891
|
+
async buildMobile(app, platform, { target, env = "debug", profile = "release" } = {}) {
|
|
2236
1892
|
const targets = await resolveMobileTargets(app, target);
|
|
2237
1893
|
await this.#buildMobileCsr(app, env);
|
|
2238
1894
|
await this.#runMobileTargets(targets, async (mobileTarget) => {
|
|
2239
|
-
await new (
|
|
1895
|
+
this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).build(platform, { profile }));
|
|
2240
1896
|
});
|
|
2241
1897
|
}
|
|
2242
|
-
async
|
|
2243
|
-
open = false,
|
|
2244
|
-
operation = "local",
|
|
2245
|
-
env = "local",
|
|
2246
|
-
target,
|
|
2247
|
-
device,
|
|
2248
|
-
regenerate = false,
|
|
2249
|
-
noAllowProvisioningUpdates = false
|
|
2250
|
-
} = {}) {
|
|
1898
|
+
async startMobile(app, platform, { target, env = "local", operation = "local", device, teamId } = {}) {
|
|
2251
1899
|
const targets = await resolveMobileTargets(app, target);
|
|
2252
|
-
|
|
1900
|
+
const [mobileTarget] = targets;
|
|
1901
|
+
if (!mobileTarget || targets.length > 1)
|
|
1902
|
+
throw new Error(`start-${platform === "ios" || platform === "android" ? platform : "desktop"} runs one mobile target at a time; pass --target <name>.`);
|
|
1903
|
+
const nativeApp = new NativeApp(app, mobileTarget);
|
|
1904
|
+
const selection = { ...device ? { device } : {}, ...teamId ? { teamId } : {} };
|
|
1905
|
+
if (operation === "release") {
|
|
2253
1906
|
await this.#buildMobileCsr(app, env);
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
1907
|
+
const running = await nativeApp.run(platform, { ...selection, profile: "release" });
|
|
1908
|
+
await running.exited;
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
const upstream = `http://localhost:${await app.getDevPort()}`;
|
|
1912
|
+
if (!await ApplicationRunner.#answers(upstream))
|
|
1913
|
+
throw new Error(`No dev server answers on ${upstream}; run \`akan start ${app.name}\` first.`);
|
|
1914
|
+
const { i18n } = await app.getConfig();
|
|
1915
|
+
const session = await nativeApp.dev(platform, { upstream, lang: i18n.defaultLocale, ...selection });
|
|
1916
|
+
app.log(`${app.name}/${mobileTarget.name} on ${platform} follows ${upstream} through ${session.gateway}.`);
|
|
1917
|
+
process.once("SIGINT", () => {
|
|
1918
|
+
session.stop().finally(() => process.exit(130));
|
|
2259
1919
|
});
|
|
1920
|
+
await session.exited;
|
|
2260
1921
|
}
|
|
2261
|
-
async
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
1922
|
+
async startDesktop(app, options = {}) {
|
|
1923
|
+
await this.startMobile(app, NativeApp.desktopPlatform(), options);
|
|
1924
|
+
}
|
|
1925
|
+
static async#answers(url) {
|
|
1926
|
+
try {
|
|
1927
|
+
await fetch(url, { signal: AbortSignal.timeout(3000) });
|
|
1928
|
+
return true;
|
|
1929
|
+
} catch {
|
|
1930
|
+
return false;
|
|
2266
1931
|
}
|
|
2267
1932
|
}
|
|
2268
|
-
async
|
|
1933
|
+
async releaseIos(app, { target, env = "main", teamId, adHoc = false } = {}) {
|
|
2269
1934
|
const targets = await resolveMobileTargets(app, target);
|
|
2270
1935
|
await this.#buildMobileCsr(app, env);
|
|
2271
|
-
|
|
2272
|
-
await new (
|
|
2273
|
-
});
|
|
1936
|
+
for (const mobileTarget of targets)
|
|
1937
|
+
this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).releaseIos({ teamId, adHoc }));
|
|
2274
1938
|
}
|
|
2275
|
-
async
|
|
2276
|
-
open = false,
|
|
2277
|
-
operation = "local",
|
|
2278
|
-
env = "local",
|
|
2279
|
-
target,
|
|
2280
|
-
regenerate = false
|
|
2281
|
-
} = {}) {
|
|
2282
|
-
const targets = await resolveMobileTargets(app, target);
|
|
2283
|
-
if (operation === "release")
|
|
2284
|
-
await this.#buildMobileCsr(app, env);
|
|
2285
|
-
await this.#runMobileTargets(targets, async (mobileTarget) => {
|
|
2286
|
-
const capacitorApp = new (await loadCapacitorApp())(app, mobileTarget.config);
|
|
2287
|
-
await capacitorApp.runAndroid({ operation, env, regenerate });
|
|
2288
|
-
if (open)
|
|
2289
|
-
await capacitorApp.openAndroid();
|
|
2290
|
-
});
|
|
2291
|
-
}
|
|
2292
|
-
async releaseAndroid(app, assembleType, { target, env = "main", regenerate = false } = {}) {
|
|
1939
|
+
async releaseAndroid(app, format, { target, env = "main" } = {}) {
|
|
2293
1940
|
const targets = await resolveMobileTargets(app, target);
|
|
1941
|
+
NativeApp.androidSigning();
|
|
2294
1942
|
await this.#buildMobileCsr(app, env);
|
|
2295
|
-
for (const mobileTarget of targets)
|
|
2296
|
-
await new (
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
1943
|
+
for (const mobileTarget of targets)
|
|
1944
|
+
this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).releaseAndroid({ formats: [format] }));
|
|
1945
|
+
}
|
|
1946
|
+
#reportBuild(app, mobileTarget, { artifacts, warnings, signing }) {
|
|
1947
|
+
for (const warning of warnings)
|
|
1948
|
+
app.logger.warn(warning);
|
|
1949
|
+
if (signing)
|
|
1950
|
+
app.log(`Signed with ${signing.identity} (${signing.profile}, team ${signing.teamId})`);
|
|
1951
|
+
for (const artifact of artifacts)
|
|
1952
|
+
app.log(`${app.name}/${mobileTarget.name} ${artifact.kind}: ${artifact.path}`);
|
|
2300
1953
|
}
|
|
2301
1954
|
async#buildMobileCsr(app, env) {
|
|
2302
1955
|
const prevEnv = {
|
|
@@ -2335,24 +1988,23 @@ try {
|
|
|
2335
1988
|
return;
|
|
2336
1989
|
for (const failure of failures) {
|
|
2337
1990
|
const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
|
|
2338
|
-
|
|
1991
|
+
Logger3.rawLog(`Mobile target ${failure.target} failed: ${message}`, undefined, "error");
|
|
2339
1992
|
}
|
|
2340
1993
|
throw new Error(`${failures.length}/${results.length} mobile targets failed`);
|
|
2341
1994
|
}
|
|
2342
|
-
async codepush(app, os) {
|
|
2343
|
-
const [target] = await resolveMobileTargets(app, undefined);
|
|
2344
|
-
if (!target)
|
|
2345
|
-
throw new Error(`No mobile target configured for ${app.name}`);
|
|
2346
|
-
const capacitorApp = new (await loadCapacitorApp())(app, target.config);
|
|
2347
|
-
await capacitorApp.init();
|
|
2348
|
-
}
|
|
2349
1995
|
#getLocalDatabaseServices(mode) {
|
|
2350
1996
|
if (mode === "single")
|
|
2351
1997
|
return [];
|
|
2352
1998
|
if (mode === "multiple")
|
|
2353
|
-
return ["redis"
|
|
1999
|
+
return ["redis"];
|
|
2354
2000
|
return ["redis", "postgres"];
|
|
2355
2001
|
}
|
|
2002
|
+
async#assertComposeHas(workspace, services) {
|
|
2003
|
+
const compose = Bun.YAML.parse(await Bun.file(`${workspace.workspaceRoot}/local/docker-compose.yaml`).text());
|
|
2004
|
+
const missing = services.filter((service) => !compose?.services?.[service]);
|
|
2005
|
+
if (missing.length)
|
|
2006
|
+
throw new Error(`local/docker-compose.yaml declares no ${missing.join(" or ")} service. Add it, or move the file aside so the next akan dbup writes the current template.`);
|
|
2007
|
+
}
|
|
2356
2008
|
async#isLocalDatabaseUp(workspace, mode) {
|
|
2357
2009
|
const requiredServices = this.#getLocalDatabaseServices(mode);
|
|
2358
2010
|
if (!requiredServices.length)
|
|
@@ -2366,16 +2018,18 @@ try {
|
|
|
2366
2018
|
async dbup(workspace, mode = "multiple") {
|
|
2367
2019
|
if (mode === "single")
|
|
2368
2020
|
return true;
|
|
2021
|
+
const services = this.#getLocalDatabaseServices(mode);
|
|
2022
|
+
await workspace.applyTemplate({
|
|
2023
|
+
basePath: "local",
|
|
2024
|
+
template: "localDev",
|
|
2025
|
+
dict: { repoName: workspace.repoName },
|
|
2026
|
+
overwrite: false
|
|
2027
|
+
});
|
|
2028
|
+
await this.#assertComposeHas(workspace, services);
|
|
2369
2029
|
try {
|
|
2370
|
-
await workspace.applyTemplate({
|
|
2371
|
-
basePath: "local",
|
|
2372
|
-
template: "localDev",
|
|
2373
|
-
dict: { repoName: workspace.repoName },
|
|
2374
|
-
overwrite: false
|
|
2375
|
-
});
|
|
2376
2030
|
const wasAlreadyUp = await this.#isLocalDatabaseUp(workspace, mode);
|
|
2377
2031
|
if (!wasAlreadyUp)
|
|
2378
|
-
await workspace.spawn(`docker`, ["compose", "up", "-d", ...
|
|
2032
|
+
await workspace.spawn(`docker`, ["compose", "up", "-d", ...services], {
|
|
2379
2033
|
cwd: `${workspace.workspaceRoot}/local`
|
|
2380
2034
|
});
|
|
2381
2035
|
return wasAlreadyUp;
|
|
@@ -2393,74 +2047,182 @@ ${detail}`
|
|
|
2393
2047
|
async dbdown(workspace) {
|
|
2394
2048
|
await workspace.spawn(`docker`, ["compose", "down"], { cwd: `${workspace.workspaceRoot}/local` });
|
|
2395
2049
|
}
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
// pkgs/@akanjs/cli/application/hostProcessProbe.ts
|
|
2053
|
+
import { readdirSync, readFileSync, readlinkSync } from "fs";
|
|
2054
|
+
|
|
2055
|
+
class HostProcessProbe {
|
|
2056
|
+
static timeoutMs = 3000;
|
|
2057
|
+
static win32TimeoutMs = 20000;
|
|
2058
|
+
static #procTcpListen = "0A";
|
|
2059
|
+
static #win32ProcessQuery = "[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine,CreationDate | ConvertTo-Json -Compress";
|
|
2060
|
+
platform;
|
|
2061
|
+
#win32Table = null;
|
|
2062
|
+
constructor(platform = process.platform) {
|
|
2063
|
+
this.platform = platform;
|
|
2064
|
+
}
|
|
2065
|
+
async listenersOn(port) {
|
|
2066
|
+
if (this.platform === "win32")
|
|
2067
|
+
return HostProcessProbe.parseNetstat(await HostProcessProbe.run(["netstat", "-ano"]), port);
|
|
2068
|
+
if (this.platform === "linux")
|
|
2069
|
+
return HostProcessProbe.#procListenersOn(port);
|
|
2070
|
+
const output = await HostProcessProbe.run(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
|
|
2071
|
+
return [
|
|
2072
|
+
...new Set(output.split(`
|
|
2073
|
+
`).map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0))
|
|
2074
|
+
];
|
|
2420
2075
|
}
|
|
2421
|
-
async
|
|
2422
|
-
|
|
2076
|
+
async parentOf(pid) {
|
|
2077
|
+
if (this.platform === "win32") {
|
|
2078
|
+
const child = await this.#win32Process(pid);
|
|
2079
|
+
const parent = child ? await this.#win32Process(child.ppid) : null;
|
|
2080
|
+
return child && parent ? HostProcessProbe.parentPidOf(child, parent) : null;
|
|
2081
|
+
}
|
|
2082
|
+
if (this.platform === "linux") {
|
|
2083
|
+
const stat = HostProcessProbe.#readProc(pid, "stat");
|
|
2084
|
+
return stat === null ? null : HostProcessProbe.parseProcStatPpid(stat);
|
|
2085
|
+
}
|
|
2086
|
+
const parsed = Number((await HostProcessProbe.run(["ps", "-p", String(pid), "-o", "ppid="])).trim());
|
|
2087
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
2423
2088
|
}
|
|
2424
|
-
async
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
environment,
|
|
2433
|
-
local
|
|
2434
|
-
});
|
|
2435
|
-
return;
|
|
2089
|
+
async commandOf(pid) {
|
|
2090
|
+
if (this.platform === "win32")
|
|
2091
|
+
return (await this.#win32Process(pid))?.command ?? null;
|
|
2092
|
+
if (this.platform === "linux") {
|
|
2093
|
+
const cmdline = HostProcessProbe.#readProc(pid, "cmdline");
|
|
2094
|
+
return cmdline?.split("\x00").filter(Boolean).join(" ") || null;
|
|
2095
|
+
}
|
|
2096
|
+
return (await HostProcessProbe.run(["ps", "-ww", "-p", String(pid), "-o", "command="])).trim() || null;
|
|
2436
2097
|
}
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2098
|
+
static parentPidOf(child, parent) {
|
|
2099
|
+
if (child.ppid !== parent.pid)
|
|
2100
|
+
return null;
|
|
2101
|
+
if (parent.startedAt !== null && child.startedAt !== null && parent.startedAt > child.startedAt)
|
|
2102
|
+
return null;
|
|
2103
|
+
return parent.pid;
|
|
2104
|
+
}
|
|
2105
|
+
static parseNetstat(output, port) {
|
|
2106
|
+
const pids = new Set;
|
|
2107
|
+
for (const line of output.split(/\r?\n/)) {
|
|
2108
|
+
const fields = line.trim().split(/\s+/);
|
|
2109
|
+
if (fields[0] !== "TCP" || fields.length < 5)
|
|
2110
|
+
continue;
|
|
2111
|
+
const [, local, remote] = fields;
|
|
2112
|
+
if (!local?.endsWith(`:${port}`) || !remote?.endsWith(":0"))
|
|
2113
|
+
continue;
|
|
2114
|
+
const pid = Number(fields.at(-1));
|
|
2115
|
+
if (Number.isInteger(pid) && pid > 0)
|
|
2116
|
+
pids.add(pid);
|
|
2117
|
+
}
|
|
2118
|
+
return [...pids];
|
|
2119
|
+
}
|
|
2120
|
+
static parseProcNetTcp(text, port) {
|
|
2121
|
+
const inodes = [];
|
|
2122
|
+
for (const line of text.split(`
|
|
2123
|
+
`).slice(1)) {
|
|
2124
|
+
const fields = line.trim().split(/\s+/);
|
|
2125
|
+
const [, local, , state, , , , , , inode] = fields;
|
|
2126
|
+
if (!local || !inode || state !== HostProcessProbe.#procTcpListen || inode === "0")
|
|
2127
|
+
continue;
|
|
2128
|
+
if (Number.parseInt(local.slice(local.lastIndexOf(":") + 1), 16) === port)
|
|
2129
|
+
inodes.push(inode);
|
|
2130
|
+
}
|
|
2131
|
+
return inodes;
|
|
2132
|
+
}
|
|
2133
|
+
static parseProcStatPpid(stat) {
|
|
2134
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
2135
|
+
return Number.isInteger(ppid) && ppid > 0 ? ppid : null;
|
|
2136
|
+
}
|
|
2137
|
+
static parseWin32Processes(json) {
|
|
2138
|
+
const table = new Map;
|
|
2139
|
+
let parsed;
|
|
2140
|
+
try {
|
|
2141
|
+
parsed = JSON.parse(json.trim() || "[]");
|
|
2142
|
+
} catch {
|
|
2143
|
+
return table;
|
|
2144
|
+
}
|
|
2145
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
2146
|
+
for (const row of rows) {
|
|
2147
|
+
if (typeof row?.ProcessId !== "number")
|
|
2148
|
+
continue;
|
|
2149
|
+
table.set(row.ProcessId, {
|
|
2150
|
+
pid: row.ProcessId,
|
|
2151
|
+
ppid: row.ParentProcessId ?? 0,
|
|
2152
|
+
command: row.CommandLine?.trim() || null,
|
|
2153
|
+
startedAt: HostProcessProbe.#parseCimDate(row.CreationDate)
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
return table;
|
|
2157
|
+
}
|
|
2158
|
+
static async run(command, timeoutMs = HostProcessProbe.timeoutMs) {
|
|
2159
|
+
let proc;
|
|
2160
|
+
try {
|
|
2161
|
+
proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
|
|
2162
|
+
} catch {
|
|
2163
|
+
return "";
|
|
2164
|
+
}
|
|
2165
|
+
const timer = setTimeout(() => proc.kill("SIGKILL"), timeoutMs);
|
|
2166
|
+
try {
|
|
2167
|
+
return await new Response(proc.stdout).text();
|
|
2168
|
+
} catch {
|
|
2169
|
+
return "";
|
|
2170
|
+
} finally {
|
|
2171
|
+
clearTimeout(timer);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
async#win32Process(pid) {
|
|
2175
|
+
this.#win32Table ??= await HostProcessProbe.#queryWin32Processes();
|
|
2176
|
+
if (!this.#win32Table.has(pid))
|
|
2177
|
+
this.#win32Table = await HostProcessProbe.#queryWin32Processes();
|
|
2178
|
+
return this.#win32Table.get(pid) ?? null;
|
|
2179
|
+
}
|
|
2180
|
+
static async#queryWin32Processes() {
|
|
2181
|
+
const output = await HostProcessProbe.run(["powershell", "-NoProfile", "-NonInteractive", "-Command", HostProcessProbe.#win32ProcessQuery], HostProcessProbe.win32TimeoutMs);
|
|
2182
|
+
return HostProcessProbe.parseWin32Processes(output);
|
|
2183
|
+
}
|
|
2184
|
+
static #parseCimDate(value) {
|
|
2185
|
+
if (!value)
|
|
2186
|
+
return null;
|
|
2187
|
+
const legacy = /\/Date\((\d+)/.exec(value)?.[1];
|
|
2188
|
+
const ms = legacy ? Number(legacy) : Date.parse(value);
|
|
2189
|
+
return Number.isFinite(ms) ? ms : null;
|
|
2190
|
+
}
|
|
2191
|
+
static #procListenersOn(port) {
|
|
2192
|
+
const inodes = new Set(["tcp", "tcp6"].flatMap((table) => HostProcessProbe.parseProcNetTcp(HostProcessProbe.#readProc("net", table) ?? "", port)));
|
|
2193
|
+
if (!inodes.size)
|
|
2194
|
+
return [];
|
|
2195
|
+
const pids = [];
|
|
2196
|
+
for (const entry of HostProcessProbe.#listDir("/proc")) {
|
|
2197
|
+
if (!/^\d+$/.test(entry))
|
|
2198
|
+
continue;
|
|
2199
|
+
const holds = HostProcessProbe.#listDir(`/proc/${entry}/fd`).some((fd) => {
|
|
2200
|
+
const link = HostProcessProbe.#orElse(() => readlinkSync(`/proc/${entry}/fd/${fd}`), "");
|
|
2201
|
+
const inode = /^socket:\[(\d+)\]$/.exec(link)?.[1];
|
|
2202
|
+
return !!inode && inodes.has(inode);
|
|
2203
|
+
});
|
|
2204
|
+
if (holds)
|
|
2205
|
+
pids.push(Number(entry));
|
|
2206
|
+
}
|
|
2207
|
+
return pids;
|
|
2208
|
+
}
|
|
2209
|
+
static #readProc(pid, file) {
|
|
2210
|
+
return HostProcessProbe.#orElse(() => readFileSync(`/proc/${pid}/${file}`, "utf8"), null);
|
|
2211
|
+
}
|
|
2212
|
+
static #listDir(dir) {
|
|
2213
|
+
return HostProcessProbe.#orElse(() => readdirSync(dir), []);
|
|
2214
|
+
}
|
|
2215
|
+
static #orElse(read, fallback) {
|
|
2216
|
+
try {
|
|
2217
|
+
return read();
|
|
2218
|
+
} catch {
|
|
2219
|
+
return fallback;
|
|
2220
|
+
}
|
|
2458
2221
|
}
|
|
2459
2222
|
}
|
|
2460
2223
|
|
|
2461
2224
|
// pkgs/@akanjs/cli/application/devPortReclaimer.ts
|
|
2462
2225
|
class DevPortReclaimer {
|
|
2463
|
-
static lsofTimeoutMs = 3000;
|
|
2464
2226
|
static releaseTimeoutMs = 8000;
|
|
2465
2227
|
static maxAncestorDepth = 8;
|
|
2466
2228
|
static #releasePollMs = 100;
|
|
@@ -2469,7 +2231,12 @@ class DevPortReclaimer {
|
|
|
2469
2231
|
return [...new Set(ports.flatMap((port) => [port, port + DevPortReclaimer.wsPortOffset]))].sort((left, right) => left - right);
|
|
2470
2232
|
}
|
|
2471
2233
|
static isAkanCommand(command) {
|
|
2472
|
-
|
|
2234
|
+
const normalized = command.replaceAll("\\", "/");
|
|
2235
|
+
return /(^|\/)akan(\.exe)?"?(\s|$)/.test(normalized) || normalized.includes("@akanjs/cli") || normalized.includes("akanjs/server") || /\bapps\/[^/\s]+\/main\.ts\b/.test(normalized) || normalized.includes("incrementalBuilder.proc") || normalized.includes("rscWorker");
|
|
2236
|
+
}
|
|
2237
|
+
#probe;
|
|
2238
|
+
constructor(probe = new HostProcessProbe) {
|
|
2239
|
+
this.#probe = probe;
|
|
2473
2240
|
}
|
|
2474
2241
|
async reclaim(ports) {
|
|
2475
2242
|
const killed = [];
|
|
@@ -2494,10 +2261,10 @@ class DevPortReclaimer {
|
|
|
2494
2261
|
}
|
|
2495
2262
|
async holdersOf(port) {
|
|
2496
2263
|
const holders = [];
|
|
2497
|
-
for (const listenerPid of await this.#
|
|
2264
|
+
for (const listenerPid of await this.#probe.listenersOn(port)) {
|
|
2498
2265
|
if (listenerPid === process.pid || listenerPid === process.ppid)
|
|
2499
2266
|
continue;
|
|
2500
|
-
const command = await this.#commandOf(listenerPid);
|
|
2267
|
+
const command = await this.#probe.commandOf(listenerPid);
|
|
2501
2268
|
if (!command)
|
|
2502
2269
|
continue;
|
|
2503
2270
|
if (!DevPortReclaimer.isAkanCommand(command)) {
|
|
@@ -2512,44 +2279,37 @@ class DevPortReclaimer {
|
|
|
2512
2279
|
async#akanRootOf(pid, command) {
|
|
2513
2280
|
let root = { pid, command };
|
|
2514
2281
|
for (let depth = 0;depth < DevPortReclaimer.maxAncestorDepth; depth += 1) {
|
|
2515
|
-
const parentPid = await this.#
|
|
2282
|
+
const parentPid = await this.#probe.parentOf(root.pid);
|
|
2516
2283
|
if (parentPid === null || parentPid <= 1 || parentPid === process.pid || parentPid === process.ppid)
|
|
2517
2284
|
break;
|
|
2518
|
-
const parentCommand = await this.#commandOf(parentPid);
|
|
2285
|
+
const parentCommand = await this.#probe.commandOf(parentPid);
|
|
2519
2286
|
if (!parentCommand || !DevPortReclaimer.isAkanCommand(parentCommand))
|
|
2520
2287
|
break;
|
|
2521
2288
|
root = { pid: parentPid, command: parentCommand };
|
|
2522
2289
|
}
|
|
2523
2290
|
return root;
|
|
2524
2291
|
}
|
|
2525
|
-
async#ppidOf(pid) {
|
|
2526
|
-
const output = await this.#run(["ps", "-p", String(pid), "-o", "ppid="]);
|
|
2527
|
-
const parsed = Number(output.trim());
|
|
2528
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
2529
|
-
}
|
|
2530
|
-
async#pidsOn(port) {
|
|
2531
|
-
const output = await this.#run(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
|
|
2532
|
-
return [
|
|
2533
|
-
...new Set(output.split(`
|
|
2534
|
-
`).map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0))
|
|
2535
|
-
];
|
|
2536
|
-
}
|
|
2537
|
-
async#commandOf(pid) {
|
|
2538
|
-
const output = await this.#run(["ps", "-p", String(pid), "-o", "command="]);
|
|
2539
|
-
return output.trim() || null;
|
|
2540
|
-
}
|
|
2541
2292
|
async#terminate(pid) {
|
|
2293
|
+
if (this.#probe.platform === "win32") {
|
|
2294
|
+
await HostProcessProbe.run(["taskkill", "/PID", String(pid), "/T", "/F"]);
|
|
2295
|
+
return await this.#released(pid);
|
|
2296
|
+
}
|
|
2542
2297
|
if (!this.#signal(pid, "SIGTERM"))
|
|
2543
2298
|
return !this.#alive(pid);
|
|
2299
|
+
if (await this.#released(pid))
|
|
2300
|
+
return true;
|
|
2301
|
+
this.#signal(pid, "SIGKILL");
|
|
2302
|
+
await Bun.sleep(DevPortReclaimer.#releasePollMs);
|
|
2303
|
+
return !this.#alive(pid);
|
|
2304
|
+
}
|
|
2305
|
+
async#released(pid) {
|
|
2544
2306
|
const deadline = Date.now() + DevPortReclaimer.releaseTimeoutMs;
|
|
2545
2307
|
while (Date.now() < deadline) {
|
|
2546
2308
|
if (!this.#alive(pid))
|
|
2547
2309
|
return true;
|
|
2548
2310
|
await Bun.sleep(DevPortReclaimer.#releasePollMs);
|
|
2549
2311
|
}
|
|
2550
|
-
|
|
2551
|
-
await Bun.sleep(DevPortReclaimer.#releasePollMs);
|
|
2552
|
-
return !this.#alive(pid);
|
|
2312
|
+
return false;
|
|
2553
2313
|
}
|
|
2554
2314
|
#signal(pid, signal) {
|
|
2555
2315
|
try {
|
|
@@ -2567,26 +2327,15 @@ class DevPortReclaimer {
|
|
|
2567
2327
|
return error.code === "EPERM";
|
|
2568
2328
|
}
|
|
2569
2329
|
}
|
|
2570
|
-
async#run(command) {
|
|
2571
|
-
const proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"] });
|
|
2572
|
-
const timer = setTimeout(() => proc.kill("SIGKILL"), DevPortReclaimer.lsofTimeoutMs);
|
|
2573
|
-
try {
|
|
2574
|
-
return await new Response(proc.stdout).text();
|
|
2575
|
-
} catch {
|
|
2576
|
-
return "";
|
|
2577
|
-
} finally {
|
|
2578
|
-
clearTimeout(timer);
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
2330
|
}
|
|
2582
2331
|
|
|
2583
2332
|
// pkgs/@akanjs/cli/application/devStreamView.ts
|
|
2584
|
-
import { Logger as
|
|
2333
|
+
import { Logger as Logger4 } from "akanjs/common";
|
|
2585
2334
|
import chalk from "chalk";
|
|
2586
2335
|
var appColors = ["cyan", "magenta", "yellow", "green", "blue", "red"];
|
|
2587
2336
|
|
|
2588
2337
|
class DevStreamView {
|
|
2589
|
-
#logger = new
|
|
2338
|
+
#logger = new Logger4("akan start");
|
|
2590
2339
|
#partial = new Map;
|
|
2591
2340
|
#lastState = new Map;
|
|
2592
2341
|
#color = new Map;
|
|
@@ -2690,8 +2439,9 @@ class DevBootConcurrency {
|
|
|
2690
2439
|
|
|
2691
2440
|
// pkgs/@akanjs/cli/application/devSessionLog.ts
|
|
2692
2441
|
import { mkdir, rename } from "fs/promises";
|
|
2693
|
-
import
|
|
2442
|
+
import path5 from "path";
|
|
2694
2443
|
import dayjs from "dayjs";
|
|
2444
|
+
|
|
2695
2445
|
class DevSessionLog {
|
|
2696
2446
|
static fileName = "dev.log";
|
|
2697
2447
|
static previousFileName = "dev.prev.log";
|
|
@@ -2713,19 +2463,19 @@ class DevSessionLog {
|
|
|
2713
2463
|
return [...this.#appNames];
|
|
2714
2464
|
}
|
|
2715
2465
|
pathOf(app) {
|
|
2716
|
-
return
|
|
2466
|
+
return path5.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
|
|
2717
2467
|
}
|
|
2718
2468
|
previousPathOf(app) {
|
|
2719
|
-
return
|
|
2469
|
+
return path5.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
|
|
2720
2470
|
}
|
|
2721
2471
|
relativePathOf(app) {
|
|
2722
|
-
return
|
|
2472
|
+
return path5.relative(this.#workspaceRoot, this.pathOf(app));
|
|
2723
2473
|
}
|
|
2724
2474
|
async open() {
|
|
2725
2475
|
const stamp = dayjs(this.#now()).format("YYYY-MM-DD HH:mm:ss");
|
|
2726
2476
|
for (const app of this.#appNames) {
|
|
2727
2477
|
const file = this.pathOf(app);
|
|
2728
|
-
await mkdir(
|
|
2478
|
+
await mkdir(path5.dirname(file), { recursive: true });
|
|
2729
2479
|
await rename(file, this.previousPathOf(app)).catch(() => {
|
|
2730
2480
|
return;
|
|
2731
2481
|
});
|
|
@@ -2769,7 +2519,7 @@ class DevSessionLog {
|
|
|
2769
2519
|
this.#writers.clear();
|
|
2770
2520
|
}
|
|
2771
2521
|
#runtimeDirOf(app) {
|
|
2772
|
-
return
|
|
2522
|
+
return path5.join(this.#workspaceRoot, "local", "apps", app, "runtime");
|
|
2773
2523
|
}
|
|
2774
2524
|
#writeLine(app, text) {
|
|
2775
2525
|
const writer = this.#writers.get(app);
|
|
@@ -2792,6 +2542,16 @@ class DevSessionLog {
|
|
|
2792
2542
|
|
|
2793
2543
|
// pkgs/@akanjs/cli/application/devSupervisor.ts
|
|
2794
2544
|
class DevSupervisor {
|
|
2545
|
+
static resolveDevUi(plain, {
|
|
2546
|
+
isTty = !!process.stdout.isTTY,
|
|
2547
|
+
columns = process.stdout.columns ?? 0
|
|
2548
|
+
} = {}) {
|
|
2549
|
+
if (plain)
|
|
2550
|
+
return { mode: "stream", downgraded: false };
|
|
2551
|
+
if (!isTty || columns <= 0)
|
|
2552
|
+
return { mode: "stream", downgraded: true };
|
|
2553
|
+
return { mode: "tui", downgraded: false };
|
|
2554
|
+
}
|
|
2795
2555
|
static supervisedEnvKey = "AKAN_DEV_SUPERVISED";
|
|
2796
2556
|
static childHooks() {
|
|
2797
2557
|
if (process.env[DevSupervisor.supervisedEnvKey] !== "1" || !process.send)
|
|
@@ -2855,16 +2615,14 @@ class DevSupervisor {
|
|
|
2855
2615
|
return ports;
|
|
2856
2616
|
}
|
|
2857
2617
|
#makeChild(app, port) {
|
|
2858
|
-
|
|
2859
|
-
const ready = new Promise((resolve) => {
|
|
2860
|
-
markReady = resolve;
|
|
2861
|
-
});
|
|
2618
|
+
const { promise: ready, resolve: markReady } = Promise.withResolvers();
|
|
2862
2619
|
return {
|
|
2863
2620
|
status: {
|
|
2864
2621
|
app,
|
|
2865
2622
|
name: app.name,
|
|
2866
2623
|
port,
|
|
2867
2624
|
url: `http://localhost:${port}`,
|
|
2625
|
+
shareUrl: this.#options.shares?.get(app.name) ?? null,
|
|
2868
2626
|
state: "starting",
|
|
2869
2627
|
detail: "queued",
|
|
2870
2628
|
exitCode: null
|
|
@@ -2894,16 +2652,18 @@ class DevSupervisor {
|
|
|
2894
2652
|
}
|
|
2895
2653
|
}
|
|
2896
2654
|
async#waitForReady(child) {
|
|
2897
|
-
|
|
2655
|
+
if (await DevSupervisor.timesOut(child.ready, DevSupervisor.readyTimeoutMs))
|
|
2656
|
+
this.#note(`${child.status.name} has not reported ready after ${Math.round(DevSupervisor.readyTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
|
|
2657
|
+
}
|
|
2658
|
+
static async timesOut(work, ms) {
|
|
2659
|
+
let timer;
|
|
2898
2660
|
const expired = new Promise((resolve) => {
|
|
2899
|
-
timer = setTimeout(() => resolve(
|
|
2661
|
+
timer = setTimeout(() => resolve(true), ms);
|
|
2900
2662
|
});
|
|
2901
2663
|
try {
|
|
2902
|
-
|
|
2903
|
-
this.#note(`${child.status.name} has not reported ready after ${Math.round(DevSupervisor.readyTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
|
|
2664
|
+
return await Promise.race([work.then(() => false), expired]);
|
|
2904
2665
|
} finally {
|
|
2905
|
-
|
|
2906
|
-
clearTimeout(timer);
|
|
2666
|
+
clearTimeout(timer);
|
|
2907
2667
|
}
|
|
2908
2668
|
}
|
|
2909
2669
|
static childArgs(name, { write = true } = {}) {
|
|
@@ -3025,44 +2785,68 @@ class DevSupervisor {
|
|
|
3025
2785
|
for (const child of running)
|
|
3026
2786
|
child.proc.kill("SIGTERM");
|
|
3027
2787
|
const exited = Promise.all(running.map(async (child) => await child.proc.exited));
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
if (!child.proc.killed) {
|
|
3036
|
-
this.#note(`${child.status.name} did not exit in time; killing it`, "warn");
|
|
3037
|
-
child.proc.kill("SIGKILL");
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
} finally {
|
|
3042
|
-
if (timer)
|
|
3043
|
-
clearTimeout(timer);
|
|
2788
|
+
if (!await DevSupervisor.timesOut(exited, DevSupervisor.shutdownGraceMs))
|
|
2789
|
+
return;
|
|
2790
|
+
for (const child of running) {
|
|
2791
|
+
if (child.proc.killed)
|
|
2792
|
+
continue;
|
|
2793
|
+
this.#note(`${child.status.name} did not exit in time; killing it`, "warn");
|
|
2794
|
+
child.proc.kill("SIGKILL");
|
|
3044
2795
|
}
|
|
3045
2796
|
}
|
|
3046
2797
|
}
|
|
3047
2798
|
|
|
3048
|
-
// pkgs/@akanjs/cli/application/
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
2799
|
+
// pkgs/@akanjs/cli/application/interruptTeardown.ts
|
|
2800
|
+
import { Logger as Logger5 } from "akanjs/common";
|
|
2801
|
+
|
|
2802
|
+
class InterruptTeardown {
|
|
2803
|
+
#teardowns = [];
|
|
2804
|
+
#exit;
|
|
2805
|
+
#listen;
|
|
2806
|
+
#report;
|
|
2807
|
+
#interrupted = false;
|
|
2808
|
+
ownsExit = true;
|
|
2809
|
+
constructor({ exit, listen, report } = {}) {
|
|
2810
|
+
this.#exit = exit ?? ((code) => process.exit(code));
|
|
2811
|
+
this.#listen = listen ?? ((onSignal) => {
|
|
2812
|
+
process.on("SIGINT", onSignal);
|
|
2813
|
+
});
|
|
2814
|
+
this.#report = report ?? ((message) => Logger5.rawLog(message, undefined, "error"));
|
|
2815
|
+
}
|
|
2816
|
+
add(run, abandoned) {
|
|
2817
|
+
this.#teardowns.push({ run, abandoned, running: false });
|
|
2818
|
+
if (this.#teardowns.length === 1)
|
|
2819
|
+
this.#listen(() => this.#onSignal());
|
|
2820
|
+
}
|
|
2821
|
+
#onSignal() {
|
|
2822
|
+
if (this.#interrupted) {
|
|
2823
|
+
for (const one of this.#teardowns)
|
|
2824
|
+
if (one.running)
|
|
2825
|
+
this.#report(one.abandoned);
|
|
2826
|
+
this.#exit(130);
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
this.#interrupted = true;
|
|
2830
|
+
Promise.all(this.#teardowns.map(async (one) => {
|
|
2831
|
+
one.running = true;
|
|
2832
|
+
await one.run().catch(() => {
|
|
2833
|
+
return;
|
|
2834
|
+
});
|
|
2835
|
+
one.running = false;
|
|
2836
|
+
})).finally(() => {
|
|
2837
|
+
if (this.ownsExit)
|
|
2838
|
+
this.#exit(0);
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
3056
2842
|
|
|
3057
2843
|
// pkgs/@akanjs/cli/application/application.script.ts
|
|
3058
2844
|
class ApplicationScript extends script("application", [ApplicationRunner, LibraryScript]) {
|
|
3059
2845
|
static dbShutdownTimeoutMs = 20000;
|
|
2846
|
+
#interrupt = new InterruptTeardown;
|
|
3060
2847
|
async confirmDatabaseModeDependencyInstall(databaseMode, installSpecs) {
|
|
3061
2848
|
return await confirm({
|
|
3062
|
-
message:
|
|
3063
|
-
`Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}.`,
|
|
3064
|
-
"Install them now?"
|
|
3065
|
-
].join(" "),
|
|
2849
|
+
message: `Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}. Install them now?`,
|
|
3066
2850
|
default: true
|
|
3067
2851
|
});
|
|
3068
2852
|
}
|
|
@@ -3073,61 +2857,16 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3073
2857
|
const shouldInstall = await this.confirmDatabaseModeDependencyInstall(databaseMode, installSpecs);
|
|
3074
2858
|
if (!shouldInstall)
|
|
3075
2859
|
throw new Error(`Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}.`);
|
|
3076
|
-
|
|
3077
|
-
try {
|
|
3078
|
-
await app.workspace.spawn("bun", ["add", ...installSpecs], {
|
|
3079
|
-
stdio: "inherit"
|
|
3080
|
-
});
|
|
3081
|
-
await app.workspace.getPackageJson({ refresh: true });
|
|
3082
|
-
spinner.succeed(`Installed database dependencies for ${databaseMode} mode`);
|
|
3083
|
-
} catch (error) {
|
|
3084
|
-
spinner.fail(`Failed to install database dependencies for ${databaseMode} mode`);
|
|
3085
|
-
throw error;
|
|
3086
|
-
}
|
|
3087
|
-
}
|
|
3088
|
-
async confirmMobileDependencyInstall(installSpecs) {
|
|
3089
|
-
return await confirm({
|
|
3090
|
-
message: [`Mobile builds require missing dependencies: ${installSpecs.join(", ")}.`, "Install them now?"].join(" "),
|
|
3091
|
-
default: true
|
|
3092
|
-
});
|
|
2860
|
+
await this.#addDependencies(app, installSpecs, `database dependencies for ${databaseMode} mode`);
|
|
3093
2861
|
}
|
|
3094
|
-
async
|
|
3095
|
-
const
|
|
3096
|
-
if (installSpecs.length === 0)
|
|
3097
|
-
return;
|
|
3098
|
-
const shouldInstall = await this.confirmMobileDependencyInstall(installSpecs);
|
|
3099
|
-
if (!shouldInstall)
|
|
3100
|
-
throw new Error(`Mobile builds require missing dependencies: ${installSpecs.join(", ")}.`);
|
|
3101
|
-
const spinner = app.workspace.spinning("Installing mobile dependencies...");
|
|
2862
|
+
async#addDependencies(app, installSpecs, what) {
|
|
2863
|
+
const spinner = app.workspace.spinning(`Installing ${what}...`);
|
|
3102
2864
|
try {
|
|
3103
|
-
await app.workspace.spawn("bun", ["add", ...installSpecs], {
|
|
3104
|
-
stdio: "inherit"
|
|
3105
|
-
});
|
|
2865
|
+
await app.workspace.spawn("bun", ["add", ...installSpecs], { stdio: "inherit" });
|
|
3106
2866
|
await app.workspace.getPackageJson({ refresh: true });
|
|
3107
|
-
spinner.succeed(
|
|
3108
|
-
} catch (error) {
|
|
3109
|
-
spinner.fail("Failed to install mobile dependencies");
|
|
3110
|
-
throw error;
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
3113
|
-
async syncMobileAppCapacitorPlugins(app, akanConfig) {
|
|
3114
|
-
const plugins = akanConfig.getMobileAppCapacitorPlugins();
|
|
3115
|
-
if (plugins.length === 0)
|
|
3116
|
-
return;
|
|
3117
|
-
const packageJson = await app.getPackageJson({ refresh: true });
|
|
3118
|
-
const dependencies = packageJson.dependencies ?? {};
|
|
3119
|
-
const missing = plugins.filter((plugin) => !dependencies[plugin]);
|
|
3120
|
-
if (missing.length === 0)
|
|
3121
|
-
return;
|
|
3122
|
-
const spinner = app.workspace.spinning(`Adding default Capacitor plugins to ${app.name}...`);
|
|
3123
|
-
try {
|
|
3124
|
-
packageJson.dependencies = { ...dependencies, ...Object.fromEntries(missing.map((plugin) => [plugin, "*"])) };
|
|
3125
|
-
await app.setPackageJson(packageJson);
|
|
3126
|
-
await app.workspace.spawn("bun", ["install"], { stdio: "inherit" });
|
|
3127
|
-
await app.getPackageJson({ refresh: true });
|
|
3128
|
-
spinner.succeed(`Added default Capacitor plugins to ${app.name}: ${missing.join(", ")}`);
|
|
2867
|
+
spinner.succeed(`Installed ${what}`);
|
|
3129
2868
|
} catch (error) {
|
|
3130
|
-
spinner.fail(`Failed to
|
|
2869
|
+
spinner.fail(`Failed to install ${what}`);
|
|
3131
2870
|
throw error;
|
|
3132
2871
|
}
|
|
3133
2872
|
}
|
|
@@ -3156,17 +2895,6 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3156
2895
|
else
|
|
3157
2896
|
await this.libraryScript.syncLibrary(sys);
|
|
3158
2897
|
}
|
|
3159
|
-
async compact(sys, { module = null, minLines, interactive = false } = {}) {
|
|
3160
|
-
const { scanned, reports } = await this.applicationRunner.compact(sys, { module, minLines, interactive });
|
|
3161
|
-
if (!reports.length) {
|
|
3162
|
-
Logger6.rawLog(`No abstract file long enough to compact in ${sys.name} (${scanned} scanned)`);
|
|
3163
|
-
return;
|
|
3164
|
-
}
|
|
3165
|
-
for (const report of reports)
|
|
3166
|
-
Logger6.rawLog(`${report.status}: ${report.path} (${report.beforeLines} -> ${report.afterLines} lines)`);
|
|
3167
|
-
const compactedNum = reports.filter((report) => report.status === "compacted").length;
|
|
3168
|
-
Logger6.rawLog(`Compacted ${compactedNum}/${reports.length} abstract files in ${sys.name}`);
|
|
3169
|
-
}
|
|
3170
2898
|
async script(app, filename) {
|
|
3171
2899
|
const scriptFilename = filename ?? await this.applicationRunner.getScriptFilename(app);
|
|
3172
2900
|
await app.scanSync();
|
|
@@ -3184,10 +2912,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3184
2912
|
if (!quiet)
|
|
3185
2913
|
Logger6.rawLog(`Creating an optimized production build for ${app.name}...`);
|
|
3186
2914
|
try {
|
|
3187
|
-
const result = await this.applicationRunner.build(app, {
|
|
3188
|
-
fast,
|
|
3189
|
-
spinner: !quiet
|
|
3190
|
-
});
|
|
2915
|
+
const result = await this.applicationRunner.build(app, { fast, spinner: !quiet });
|
|
3191
2916
|
Logger6.rawLog(`${app.name} built in dist/apps/${app.name}`);
|
|
3192
2917
|
if (!quiet)
|
|
3193
2918
|
ApplicationBuildReporter.printSummary(result);
|
|
@@ -3212,8 +2937,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3212
2937
|
async test(exec, { write = true } = {}) {
|
|
3213
2938
|
if (exec instanceof LibExecutor) {
|
|
3214
2939
|
await this.libraryScript.syncLibrary(exec);
|
|
3215
|
-
|
|
3216
|
-
spinner.succeed(`${exec.name} prepared`);
|
|
2940
|
+
exec.spinning(`Preparing ${exec.name}...`).succeed(`${exec.name} prepared`);
|
|
3217
2941
|
await this.applicationRunner.test(exec);
|
|
3218
2942
|
return;
|
|
3219
2943
|
}
|
|
@@ -3230,23 +2954,33 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3230
2954
|
}
|
|
3231
2955
|
await this.applicationRunner.test(exec);
|
|
3232
2956
|
}
|
|
3233
|
-
async start(apps, {
|
|
2957
|
+
async start(apps, {
|
|
2958
|
+
open = false,
|
|
2959
|
+
dbup = true,
|
|
2960
|
+
write = true,
|
|
2961
|
+
plain = false,
|
|
2962
|
+
kill = false,
|
|
2963
|
+
share = false,
|
|
2964
|
+
concurrency = null
|
|
2965
|
+
} = {}) {
|
|
3234
2966
|
const first = apps[0];
|
|
3235
2967
|
if (!first)
|
|
3236
2968
|
throw new Error("No app selected to start");
|
|
3237
|
-
const { mode, downgraded } = resolveDevUi(plain);
|
|
2969
|
+
const { mode, downgraded } = DevSupervisor.resolveDevUi(plain);
|
|
3238
2970
|
if (downgraded)
|
|
3239
2971
|
first.workspace.log("no sized terminal to draw on; printing prefixed lines instead");
|
|
3240
2972
|
if (kill)
|
|
3241
2973
|
await this.reclaimDevPorts(apps);
|
|
2974
|
+
const shares = share ? await this.#shareOnInterrupt(apps) : null;
|
|
3242
2975
|
if (mode === "stream" && apps.length === 1)
|
|
3243
2976
|
return await this.startOne(first, { open, dbup, write, ...DevSupervisor.childHooks() });
|
|
3244
|
-
|
|
2977
|
+
this.#interrupt.ownsExit = false;
|
|
2978
|
+
await this.#startMany(apps, { open, dbup, write, mode, concurrency, shares });
|
|
3245
2979
|
}
|
|
3246
2980
|
async startOne(app, { open = false, dbup = true, stdio = "inherit", write = true, onDevEvent } = {}) {
|
|
3247
2981
|
await app.scanSync({ write });
|
|
3248
2982
|
const akanConfig = await app.getConfig();
|
|
3249
|
-
const databaseMode =
|
|
2983
|
+
const databaseMode = akanConfig.resolveDatabaseMode();
|
|
3250
2984
|
await this.syncDatabaseModeDependencies(app, akanConfig, databaseMode);
|
|
3251
2985
|
if (app.getEnv() === "local" && dbup && databaseMode !== "single") {
|
|
3252
2986
|
const wasDbAlreadyUp = await this.dbup(app.workspace, databaseMode);
|
|
@@ -3254,15 +2988,12 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3254
2988
|
this.#stopDatabaseOnInterrupt(app.workspace);
|
|
3255
2989
|
}
|
|
3256
2990
|
const spinner = app.spinning("Preparing backend...");
|
|
3257
|
-
|
|
2991
|
+
return await this.applicationRunner.start(app, {
|
|
3258
2992
|
open,
|
|
3259
|
-
onStart: () => {
|
|
3260
|
-
spinner.succeed(`${app.name} prepared, ready to start`);
|
|
3261
|
-
},
|
|
2993
|
+
onStart: () => spinner.succeed(`${app.name} prepared, ready to start`),
|
|
3262
2994
|
onDevEvent,
|
|
3263
2995
|
stdio
|
|
3264
2996
|
});
|
|
3265
|
-
return akanAppHost;
|
|
3266
2997
|
}
|
|
3267
2998
|
async reclaimDevPorts(apps) {
|
|
3268
2999
|
const workspace = apps[0]?.workspace;
|
|
@@ -3277,13 +3008,20 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3277
3008
|
if (killed.length === 0 && foreign.length === 0)
|
|
3278
3009
|
workspace.log("dev ports are free; nothing to kill");
|
|
3279
3010
|
}
|
|
3280
|
-
async#startMany(apps, {
|
|
3011
|
+
async#startMany(apps, {
|
|
3012
|
+
open,
|
|
3013
|
+
dbup,
|
|
3014
|
+
write,
|
|
3015
|
+
mode,
|
|
3016
|
+
concurrency,
|
|
3017
|
+
shares
|
|
3018
|
+
}) {
|
|
3281
3019
|
const workspace = apps[0]?.workspace;
|
|
3282
3020
|
if (!workspace)
|
|
3283
3021
|
throw new Error("No app selected to start");
|
|
3284
3022
|
const startedDatabase = dbup ? await this.#prepareSharedDatabase(apps) : false;
|
|
3285
|
-
const supervisor = new DevSupervisor({ apps, mode, concurrency, open, write });
|
|
3286
|
-
const view = mode === "tui" ?
|
|
3023
|
+
const supervisor = new DevSupervisor({ apps, mode, concurrency, open, write, shares });
|
|
3024
|
+
const view = mode === "tui" ? new (await import("./devTui-q7k7bakd.js")).DevTui(supervisor) : new DevStreamView(apps.map((app) => app.name));
|
|
3287
3025
|
await supervisor.run(view);
|
|
3288
3026
|
if (startedDatabase)
|
|
3289
3027
|
await this.#stopDatabase(workspace);
|
|
@@ -3297,7 +3035,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3297
3035
|
if (app.getEnv() !== "local")
|
|
3298
3036
|
continue;
|
|
3299
3037
|
const akanConfig = await app.getConfig();
|
|
3300
|
-
const mode =
|
|
3038
|
+
const mode = akanConfig.resolveDatabaseMode();
|
|
3301
3039
|
await this.syncDatabaseModeDependencies(app, akanConfig, mode);
|
|
3302
3040
|
if (mode !== "single")
|
|
3303
3041
|
modes.add(mode);
|
|
@@ -3308,82 +3046,60 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3308
3046
|
started = true;
|
|
3309
3047
|
return started;
|
|
3310
3048
|
}
|
|
3311
|
-
async buildIos(app, { write = true,
|
|
3049
|
+
async buildIos(app, { write = true, ...options } = {}) {
|
|
3312
3050
|
await app.scanSync({ write });
|
|
3313
|
-
await this.applicationRunner.
|
|
3051
|
+
await this.applicationRunner.buildMobile(app, "ios", options);
|
|
3314
3052
|
}
|
|
3315
|
-
async
|
|
3316
|
-
open = false,
|
|
3317
|
-
operation = "local",
|
|
3318
|
-
env = "local",
|
|
3319
|
-
write = true,
|
|
3320
|
-
target,
|
|
3321
|
-
device,
|
|
3322
|
-
regenerate = false,
|
|
3323
|
-
noAllowProvisioningUpdates = false
|
|
3324
|
-
} = {}) {
|
|
3053
|
+
async buildAndroid(app, { write = true, ...options } = {}) {
|
|
3325
3054
|
await app.scanSync({ write });
|
|
3326
|
-
|
|
3327
|
-
await this.syncMobileDependencies(app, akanConfig);
|
|
3328
|
-
await this.syncMobileAppCapacitorPlugins(app, akanConfig);
|
|
3329
|
-
await this.applicationRunner.startIos(app, {
|
|
3330
|
-
open,
|
|
3331
|
-
operation,
|
|
3332
|
-
env,
|
|
3333
|
-
target,
|
|
3334
|
-
device,
|
|
3335
|
-
regenerate,
|
|
3336
|
-
noAllowProvisioningUpdates
|
|
3337
|
-
});
|
|
3055
|
+
await this.applicationRunner.buildMobile(app, "android", options);
|
|
3338
3056
|
}
|
|
3339
|
-
async
|
|
3057
|
+
async startIos(app, { write = true, ...options } = {}) {
|
|
3340
3058
|
await app.scanSync({ write });
|
|
3341
|
-
|
|
3342
|
-
throw new Error("releaseIos --env local is blocked. Pass allowLocalRelease only for explicit local release testing.");
|
|
3343
|
-
await this.applicationRunner.releaseIos(app, { target, env, regenerate });
|
|
3059
|
+
await this.applicationRunner.startMobile(app, "ios", options);
|
|
3344
3060
|
}
|
|
3345
|
-
async
|
|
3061
|
+
async startAndroid(app, { write = true, ...options } = {}) {
|
|
3346
3062
|
await app.scanSync({ write });
|
|
3347
|
-
await this.applicationRunner.
|
|
3063
|
+
await this.applicationRunner.startMobile(app, "android", options);
|
|
3348
3064
|
}
|
|
3349
|
-
async
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3065
|
+
async startDesktop(app, { write = true, ...options } = {}) {
|
|
3066
|
+
await app.scanSync({ write });
|
|
3067
|
+
await this.applicationRunner.startDesktop(app, options);
|
|
3068
|
+
}
|
|
3069
|
+
async releaseIos(app, {
|
|
3353
3070
|
write = true,
|
|
3354
|
-
|
|
3355
|
-
|
|
3071
|
+
allowLocalRelease = false,
|
|
3072
|
+
...options
|
|
3356
3073
|
} = {}) {
|
|
3357
3074
|
await app.scanSync({ write });
|
|
3358
|
-
|
|
3359
|
-
await this.
|
|
3360
|
-
await this.syncMobileAppCapacitorPlugins(app, akanConfig);
|
|
3361
|
-
await this.applicationRunner.startAndroid(app, {
|
|
3362
|
-
open,
|
|
3363
|
-
operation,
|
|
3364
|
-
env,
|
|
3365
|
-
target,
|
|
3366
|
-
regenerate
|
|
3367
|
-
});
|
|
3075
|
+
ApplicationScript.#assertReleaseEnv("releaseIos", options.env ?? "main", allowLocalRelease);
|
|
3076
|
+
await this.applicationRunner.releaseIos(app, options);
|
|
3368
3077
|
}
|
|
3369
|
-
async releaseAndroid(app,
|
|
3078
|
+
async releaseAndroid(app, format, {
|
|
3079
|
+
write = true,
|
|
3080
|
+
allowLocalRelease = false,
|
|
3081
|
+
...options
|
|
3082
|
+
} = {}) {
|
|
3370
3083
|
await app.scanSync({ write });
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
await this.applicationRunner.releaseAndroid(app, assembleType, {
|
|
3374
|
-
target,
|
|
3375
|
-
env,
|
|
3376
|
-
regenerate
|
|
3377
|
-
});
|
|
3084
|
+
ApplicationScript.#assertReleaseEnv("releaseAndroid", options.env ?? "main", allowLocalRelease);
|
|
3085
|
+
await this.applicationRunner.releaseAndroid(app, format, options);
|
|
3378
3086
|
}
|
|
3379
|
-
|
|
3380
|
-
|
|
3087
|
+
static #assertReleaseEnv(command, env, allowLocalRelease) {
|
|
3088
|
+
if (env === "local" && !allowLocalRelease)
|
|
3089
|
+
throw new Error(`${command} --env local is blocked. Pass allowLocalRelease only for explicit local release testing.`);
|
|
3381
3090
|
}
|
|
3382
|
-
async
|
|
3383
|
-
await this.applicationRunner.
|
|
3091
|
+
async transferDatabase(app, direction, dir) {
|
|
3092
|
+
await this.applicationRunner.transferDatabase(app, direction, dir);
|
|
3384
3093
|
}
|
|
3385
|
-
async
|
|
3386
|
-
await
|
|
3094
|
+
async dbupDeclared(workspace, mode) {
|
|
3095
|
+
const declared = mode ? [mode] : await Promise.all((await workspace.getApps()).map(async (appName) => (await AppExecutor.from(workspace, appName).getConfig()).database.modes)).then((modes) => [...new Set(modes.flat())]);
|
|
3096
|
+
const needing = declared.filter((each) => each !== "single");
|
|
3097
|
+
if (!needing.length) {
|
|
3098
|
+
workspace.log("No app here declares a database mode that runs on local services; single needs none.");
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
for (const each of needing)
|
|
3102
|
+
await this.dbup(workspace, each);
|
|
3387
3103
|
}
|
|
3388
3104
|
async dbup(workspace, mode = "multiple") {
|
|
3389
3105
|
const spinner = workspace.spinning(`Starting local database (${mode})...`);
|
|
@@ -3401,34 +3117,36 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3401
3117
|
}
|
|
3402
3118
|
spinner.succeed("Local database (/local/docker-compose.yaml) is down");
|
|
3403
3119
|
}
|
|
3404
|
-
#
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3120
|
+
async#shareOnInterrupt(apps) {
|
|
3121
|
+
const shares = [];
|
|
3122
|
+
const urls = new Map;
|
|
3123
|
+
this.#interrupt.add(async () => {
|
|
3124
|
+
await Promise.all(shares.map(async (share) => await share.close()));
|
|
3125
|
+
}, "Abandoning the tunnel release; the shares expire on their own.");
|
|
3126
|
+
await Promise.all(apps.map(async (app) => {
|
|
3127
|
+
const { TunnelShare } = await import("./TunnelShare-scf4e4pd.js");
|
|
3128
|
+
const share = await TunnelShare.open(app, { workspace: app.workspace });
|
|
3129
|
+
shares.push(share);
|
|
3130
|
+
urls.set(app.name, share.url);
|
|
3131
|
+
Logger6.rawLog(`${app.name} is shared at ${share.url}`);
|
|
3132
|
+
})).catch((error) => {
|
|
3133
|
+
Logger6.rawLog(`Could not open the tunnel: ${error instanceof Error ? error.message : String(error)}`, undefined, "error");
|
|
3413
3134
|
});
|
|
3135
|
+
return urls;
|
|
3136
|
+
}
|
|
3137
|
+
#stopDatabaseOnInterrupt(workspace) {
|
|
3138
|
+
this.#interrupt.add(async () => {
|
|
3139
|
+
await this.#stopDatabase(workspace);
|
|
3140
|
+
}, "Abandoning the local database teardown; containers are left running.");
|
|
3414
3141
|
}
|
|
3415
3142
|
async#stopDatabase(workspace) {
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
timer = setTimeout(() => resolve("timeout"), ApplicationScript.dbShutdownTimeoutMs);
|
|
3143
|
+
const stopped = this.dbdown(workspace).catch(() => {
|
|
3144
|
+
return;
|
|
3419
3145
|
});
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
}), timeout]);
|
|
3424
|
-
if (result !== "timeout")
|
|
3425
|
-
return;
|
|
3426
|
-
Logger6.rawLog(`Local database did not stop within ${ApplicationScript.dbShutdownTimeoutMs}ms; run \`akan dbdown\` once Docker responds.`, undefined, "error");
|
|
3427
|
-
} finally {
|
|
3428
|
-
if (timer)
|
|
3429
|
-
clearTimeout(timer);
|
|
3430
|
-
}
|
|
3146
|
+
if (!await DevSupervisor.timesOut(stopped, ApplicationScript.dbShutdownTimeoutMs))
|
|
3147
|
+
return;
|
|
3148
|
+
Logger6.rawLog(`Local database did not stop within ${ApplicationScript.dbShutdownTimeoutMs}ms; run \`akan dbdown\` once Docker responds.`, undefined, "error");
|
|
3431
3149
|
}
|
|
3432
3150
|
}
|
|
3433
3151
|
|
|
3434
|
-
export {
|
|
3152
|
+
export { ApplicationScript };
|