@akanjs/cli 3.0.0-beta.2 → 3.0.0-beta.20
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-awv258wb.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-1nqh35ez.js +1457 -0
- package/README.md +3 -0
- package/TunnelShare-scf4e4pd.js +9 -0
- package/{agent.command-d2gfxfck.js → agent.command-1s9ag7sb.js} +8 -9
- package/akanCode.js +102 -0
- package/{application.command-pj2m7584.js → application.command-49hwy6va.js} +51 -78
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-ybyp97pv.js} +63 -64
- package/{applicationReleasePackager-4yasxwv9.js → applicationReleasePackager-esnrphw8.js} +40 -60
- package/buildBatch.proc.js +19 -24
- package/{capacitorApp-36rbr858.js → capacitorApp-1synz4en.js} +3 -4
- package/cloud.command-d7rqtzt9.js +94 -0
- package/cloud.script-bkbpcr96.js +19 -0
- package/code.command-nn48xzej.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-s3mcmvpa.js +48 -0
- package/context.runner-k99nb5pk.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-sct4qnt5.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +206 -25
- package/guidelines/conventions/conventions.instruction.md +79 -24
- 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 +74 -0
- 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-13ffe9wp.js +69 -0
- package/index-22d57v4c.js +266 -0
- package/{index-fz5q3gd7.js → index-31yr441d.js} +10 -17
- package/index-3hx1z96c.js +87 -0
- package/index-3sq321ak.js +53 -0
- package/{index-kcxmv48c.js → index-3t7g9e4r.js} +45 -79
- package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
- package/{index-47pj3aee.js → index-5e0n4hqx.js} +11 -12
- package/{index-259dsnz3.js → index-5pc1qrbv.js} +5 -8
- package/index-7md2g9vw.js +6 -0
- package/index-8mna0947.js +187 -0
- package/{index-zry5nz81.js → index-8sf7wmdr.js} +51 -30
- package/{index-2tv3fdx4.js → index-9d9bgcx4.js} +342 -765
- package/index-9dp031by.js +12 -0
- package/{index-ne2qy0nk.js → index-ahemdr5n.js} +12 -12
- package/{index-ws5e0j51.js → index-as7xmyz8.js} +40 -24
- package/index-axzjaqve.js +1118 -0
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/index-c6eggbee.js +123 -0
- package/index-d2242ksy.js +99 -0
- package/{index-a64esmtb.js → index-ddxvdd1p.js} +169 -305
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-pnqbkf5p.js → index-dzf3r49a.js} +6 -2
- package/{index-w8kmx1b6.js → index-fgq75haa.js} +198 -290
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
- package/{index-wt7qrtd4.js → index-mxav6j3c.js} +93 -232
- package/{index-gyd220p2.js → index-myhc3c1j.js} +554 -577
- package/{index-600zhnve.js → index-nv5976x3.js} +56 -197
- package/index-p6pbz9n5.js +25 -0
- package/{index-4edzf1gr.js → index-przybvy5.js} +77 -150
- package/index-smme4r47.js +49 -0
- package/{index-mak3cd28.js → index-w2zdcw3k.js} +137 -255
- package/index-wa5jbxj1.js +760 -0
- package/{index-v0qh106m.js → index-ww4z397j.js} +527 -723
- package/{index-j9yhg404.js → index-xqjwd384.js} +4 -7
- package/{index-7737gxcp.js → index-xqpzbab5.js} +107 -241
- package/index-xsjh6nzh.js +2729 -0
- package/{index-ktb5njgy.js → index-ygn77a5w.js} +47 -99
- package/index-ztp3e73t.js +700 -0
- package/index.js +23 -22
- package/{library.command-nbsky672.js → library.command-1gx04jgn.js} +4 -5
- package/{localRegistry.command-yeq57wjr.js → localRegistry.command-zndxj1q6.js} +22 -42
- package/{module.command-29vqzrh8.js → module.command-3waddk95.js} +8 -11
- package/{package.command-axv0abmn.js → package.command-zrc7br1y.js} +6 -7
- package/package.json +14 -5
- package/{page.command-vs9s3zrh.js → page.command-vpjgardh.js} +4 -5
- package/{primitive.command-cbxmqbtc.js → primitive.command-sz84tk4t.js} +9 -22
- package/prompter-ptaa3nk0.js +8 -0
- package/{quality.command-gmbs7nws.js → quality.command-g869hafs.js} +203 -376
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-bqsjptsj.js → repair.command-chsnjvvv.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-6k1vhe9s.js → scalar.command-2v21newx.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-ckcbwfp1.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/tunnel.command-kd2yr4fx.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-44252cqv.js +79 -0
- package/{workspace.command-8s050m0h.js → workspace.command-fqcztt5p.js} +77 -109
- package/abstractCompactor-mjh5ppsq.js +0 -101
- 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-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/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,74 @@
|
|
|
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-w2zdcw3k.js";
|
|
6
40
|
import {
|
|
7
41
|
WorkspaceExecutor,
|
|
8
42
|
AppExecutor,
|
|
9
43
|
LibExecutor,
|
|
10
44
|
PkgExecutor
|
|
11
|
-
} from "./index-
|
|
45
|
+
} from "./index-9d9bgcx4.js";
|
|
12
46
|
import {
|
|
13
47
|
LibraryScript
|
|
14
|
-
} from "./index-
|
|
48
|
+
} from "./index-5pc1qrbv.js";
|
|
15
49
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "./index-hwkygn9s.js";
|
|
50
|
+
stripAnsi
|
|
51
|
+
} from "./index-7md2g9vw.js";
|
|
19
52
|
import {
|
|
20
53
|
SlicePlanner,
|
|
21
54
|
formatSlicePlan
|
|
22
|
-
} from "./index-
|
|
55
|
+
} from "./index-xqjwd384.js";
|
|
23
56
|
import {
|
|
24
57
|
resolveMobileTargets
|
|
25
|
-
} from "./index-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
}
|
|
58
|
+
} from "./index-5e0n4hqx.js";
|
|
59
|
+
import {
|
|
60
|
+
openBrowser
|
|
61
|
+
} from "./index-9dp031by.js";
|
|
166
62
|
|
|
167
63
|
// pkgs/@akanjs/cli/application/application.script.ts
|
|
168
64
|
import { confirm } from "@inquirer/prompts";
|
|
169
65
|
import { Logger as Logger6 } from "akanjs/common";
|
|
170
66
|
|
|
171
67
|
// pkgs/@akanjs/cli/application/application.runner.ts
|
|
172
|
-
import
|
|
68
|
+
import path4 from "path";
|
|
173
69
|
|
|
174
70
|
// pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
|
|
175
|
-
import { Logger as
|
|
71
|
+
import { Logger as Logger2 } from "akanjs/common";
|
|
176
72
|
|
|
177
73
|
// pkgs/@akanjs/devkit/sshTunnel.ts
|
|
178
74
|
import { Client } from "ssh2";
|
|
@@ -261,7 +157,7 @@ var createSshTunnel = async (options) => {
|
|
|
261
157
|
for (const socket of sockets)
|
|
262
158
|
closeSocket(socket, error);
|
|
263
159
|
closeQuietly(() => client.end());
|
|
264
|
-
|
|
160
|
+
server.stop();
|
|
265
161
|
};
|
|
266
162
|
client.once("error", (error) => closeTunnel(error));
|
|
267
163
|
client.once("close", () => closeTunnel());
|
|
@@ -281,22 +177,10 @@ var closeBunSocket = (socket, error) => {
|
|
|
281
177
|
socket.data.stream?.end();
|
|
282
178
|
closeQuietly(() => socket.end());
|
|
283
179
|
};
|
|
284
|
-
var closeBunServer = (server) => {
|
|
285
|
-
server.stop();
|
|
286
|
-
};
|
|
287
180
|
|
|
288
181
|
// 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
182
|
var createTunnel = async (service, { app, environment, port = service === "postgres" ? 5432 : 6379 }) => {
|
|
299
|
-
const
|
|
183
|
+
const { serveDomain, repoName } = WorkspaceExecutor.getBaseDevEnv();
|
|
300
184
|
await createSshTunnel({
|
|
301
185
|
localHost: "0.0.0.0",
|
|
302
186
|
localPort: port,
|
|
@@ -304,13 +188,18 @@ var createTunnel = async (service, { app, environment, port = service === "postg
|
|
|
304
188
|
srcPort: port,
|
|
305
189
|
dstHost: `${service}-0.${service}-svc.${app.name}-${environment}.svc.cluster.local`,
|
|
306
190
|
dstPort: service === "postgres" ? 5432 : 6379,
|
|
307
|
-
sshOptions
|
|
191
|
+
sshOptions: {
|
|
192
|
+
host: `${app.name}-${environment}.${serveDomain}`,
|
|
193
|
+
port: process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767,
|
|
194
|
+
username: process.env.SSH_TUNNEL_USERNAME ?? "root",
|
|
195
|
+
password: process.env.SSH_TUNNEL_PASSWORD ?? repoName
|
|
196
|
+
}
|
|
308
197
|
});
|
|
309
198
|
return `localhost:${port}`;
|
|
310
199
|
};
|
|
311
200
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.host.ts
|
|
312
201
|
import path from "path";
|
|
313
|
-
import { Logger
|
|
202
|
+
import { Logger } from "akanjs/common";
|
|
314
203
|
import { MemoryLimit } from "akanjs/server/memoryLimit";
|
|
315
204
|
var builderMsgTypeSet = new Set([
|
|
316
205
|
"build-route-res",
|
|
@@ -328,7 +217,7 @@ class IncrementalBuilderHost {
|
|
|
328
217
|
static #restartMaxDelayMs = 30000;
|
|
329
218
|
static #recycleDrainTimeoutMs = 30000;
|
|
330
219
|
static #devMaxRssBytes = 1200 * 1024 * 1024;
|
|
331
|
-
logger = new
|
|
220
|
+
logger = new Logger("IncrementalBuilderHost");
|
|
332
221
|
entry;
|
|
333
222
|
env;
|
|
334
223
|
app;
|
|
@@ -518,12 +407,8 @@ class IncrementalBuilderHost {
|
|
|
518
407
|
const lost = [...this.#inFlight];
|
|
519
408
|
this.#inFlight.clear();
|
|
520
409
|
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
|
-
}
|
|
410
|
+
for (const [id, type] of lost)
|
|
411
|
+
this.#onMessage({ type: `${type}-res`, id, ok: false, error: reason });
|
|
527
412
|
}
|
|
528
413
|
stop() {
|
|
529
414
|
this.#manualStop = true;
|
|
@@ -592,211 +477,7 @@ class BuilderRequestRouter {
|
|
|
592
477
|
|
|
593
478
|
// pkgs/@akanjs/devkit/akanApp/BackendImportGraph.ts
|
|
594
479
|
import { stat } from "fs/promises";
|
|
595
|
-
import path3 from "path";
|
|
596
|
-
|
|
597
|
-
// pkgs/@akanjs/devkit/akanApp/devHostPolicy.ts
|
|
598
480
|
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
481
|
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
482
|
var GRAPH_IMPORT_KINDS = new Set([
|
|
802
483
|
"import-statement",
|
|
@@ -827,7 +508,7 @@ class BackendImportGraph {
|
|
|
827
508
|
return this.#lastRefreshSucceeded;
|
|
828
509
|
}
|
|
829
510
|
has(file) {
|
|
830
|
-
return this.#files.has(
|
|
511
|
+
return this.#files.has(path2.resolve(file));
|
|
831
512
|
}
|
|
832
513
|
async fingerprint() {
|
|
833
514
|
const stamps = await Promise.all([...this.#files].map(async (file) => {
|
|
@@ -855,9 +536,9 @@ class BackendImportGraph {
|
|
|
855
536
|
const roots = await this.#entrypoints();
|
|
856
537
|
const files = new Set;
|
|
857
538
|
const queue = [...roots];
|
|
858
|
-
const workspaceRoot =
|
|
539
|
+
const workspaceRoot = path2.resolve(this.#app.workspace.workspaceRoot);
|
|
859
540
|
while (queue.length > 0) {
|
|
860
|
-
const current =
|
|
541
|
+
const current = path2.resolve(queue.pop());
|
|
861
542
|
if (files.has(current))
|
|
862
543
|
continue;
|
|
863
544
|
if (!this.#isWorkspaceSource(current, workspaceRoot))
|
|
@@ -866,7 +547,7 @@ class BackendImportGraph {
|
|
|
866
547
|
if (!imports)
|
|
867
548
|
continue;
|
|
868
549
|
files.add(current);
|
|
869
|
-
const importerDir =
|
|
550
|
+
const importerDir = path2.dirname(current);
|
|
870
551
|
for (const imp of imports) {
|
|
871
552
|
if (!GRAPH_IMPORT_KINDS.has(imp.kind) || !imp.path || NON_SOURCE_EXT_RE.test(imp.path))
|
|
872
553
|
continue;
|
|
@@ -897,7 +578,7 @@ class BackendImportGraph {
|
|
|
897
578
|
const roots = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
|
|
898
579
|
const existing = [];
|
|
899
580
|
for (const root of roots) {
|
|
900
|
-
const abs =
|
|
581
|
+
const abs = path2.resolve(root);
|
|
901
582
|
if (await Bun.file(abs).exists())
|
|
902
583
|
existing.push(abs);
|
|
903
584
|
}
|
|
@@ -906,25 +587,25 @@ class BackendImportGraph {
|
|
|
906
587
|
#resolve(specifier, importerDir) {
|
|
907
588
|
try {
|
|
908
589
|
const resolved = Bun.resolveSync(specifier, importerDir);
|
|
909
|
-
if (!
|
|
590
|
+
if (!path2.isAbsolute(resolved))
|
|
910
591
|
return null;
|
|
911
|
-
if (!SOURCE_EXTS.has(
|
|
592
|
+
if (!SOURCE_EXTS.has(path2.extname(resolved).toLowerCase()))
|
|
912
593
|
return null;
|
|
913
|
-
return
|
|
594
|
+
return path2.resolve(resolved);
|
|
914
595
|
} catch {
|
|
915
596
|
return null;
|
|
916
597
|
}
|
|
917
598
|
}
|
|
918
599
|
#isWorkspaceSource(file, workspaceRoot) {
|
|
919
|
-
const rel =
|
|
920
|
-
if (rel.startsWith("..") ||
|
|
600
|
+
const rel = path2.relative(workspaceRoot, file);
|
|
601
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel))
|
|
921
602
|
return false;
|
|
922
|
-
if (rel.includes(`${
|
|
603
|
+
if (rel.includes(`${path2.sep}node_modules${path2.sep}`) || rel.includes(`${path2.sep}.akan${path2.sep}`))
|
|
923
604
|
return false;
|
|
924
|
-
return SOURCE_EXTS.has(
|
|
605
|
+
return SOURCE_EXTS.has(path2.extname(file).toLowerCase());
|
|
925
606
|
}
|
|
926
607
|
#scanImports(file, source) {
|
|
927
|
-
const ext =
|
|
608
|
+
const ext = path2.extname(file).toLowerCase();
|
|
928
609
|
if (ext === ".tsx")
|
|
929
610
|
return this.#tsxTranspiler.scanImports(source);
|
|
930
611
|
if (ext === ".jsx")
|
|
@@ -937,6 +618,7 @@ class BackendImportGraph {
|
|
|
937
618
|
|
|
938
619
|
// pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
|
|
939
620
|
var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
|
|
621
|
+
var asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
940
622
|
var BACKEND_RESTART_DEBOUNCE_MS = 120;
|
|
941
623
|
var BACKEND_GRACEFUL_TIMEOUT_MS = 8000;
|
|
942
624
|
var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
|
|
@@ -952,7 +634,7 @@ var BUILDER_RECOVERY_MAX_DELAY_MS = 60000;
|
|
|
952
634
|
|
|
953
635
|
class AkanAppHost {
|
|
954
636
|
app;
|
|
955
|
-
logger = new
|
|
637
|
+
logger = new Logger2("AkanAppHost");
|
|
956
638
|
stdio;
|
|
957
639
|
env;
|
|
958
640
|
#onDevEvent;
|
|
@@ -1030,23 +712,18 @@ class AkanAppHost {
|
|
|
1030
712
|
async stop() {
|
|
1031
713
|
this.#cancelIdleSuspend();
|
|
1032
714
|
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
|
-
}
|
|
715
|
+
this.#clearRestartTimers();
|
|
1045
716
|
this.#failPendingBuilderMessages("dev server is shutting down");
|
|
1046
717
|
await this.#stopBackend();
|
|
1047
718
|
this.#stopBuilder();
|
|
1048
719
|
return this;
|
|
1049
720
|
}
|
|
721
|
+
#clearRestartTimers() {
|
|
722
|
+
for (const timer of [this.#restartTimer, this.#backendRecoveryTimer, this.#builderRecoveryTimer])
|
|
723
|
+
if (timer)
|
|
724
|
+
clearTimeout(timer);
|
|
725
|
+
this.#restartTimer = this.#backendRecoveryTimer = this.#builderRecoveryTimer = null;
|
|
726
|
+
}
|
|
1050
727
|
kill() {
|
|
1051
728
|
this.stop();
|
|
1052
729
|
}
|
|
@@ -1342,7 +1019,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1342
1019
|
if (this.#rssCeilingAbandoned)
|
|
1343
1020
|
return;
|
|
1344
1021
|
const ceilingBytes = IncrementalBuilderHost.maxRssBytes();
|
|
1345
|
-
const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
1346
1022
|
const decision = decideBuilderRssRecycle({
|
|
1347
1023
|
rssBytes: metrics.rssBytes,
|
|
1348
1024
|
ceilingBytes,
|
|
@@ -1380,7 +1056,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1380
1056
|
if (!isRssCeilingUnreachable(freshRssBytes, ceilingBytes))
|
|
1381
1057
|
return;
|
|
1382
1058
|
this.#rssCeilingAbandoned = true;
|
|
1383
|
-
const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
1384
1059
|
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
1060
|
}
|
|
1386
1061
|
#armRssRecycle(reason, over) {
|
|
@@ -1407,7 +1082,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1407
1082
|
}, BUILDER_RSS_RECYCLE_QUIET_MS);
|
|
1408
1083
|
}
|
|
1409
1084
|
async#recycleBuilderForRssWhenStillOver(reason, { rssBytes, ceilingBytes }) {
|
|
1410
|
-
const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
1411
1085
|
if (decideBuilderRssSettle({ rssBytes, ceilingBytes }) === "recycle-now") {
|
|
1412
1086
|
this.#recycleBuilderForRss(reason);
|
|
1413
1087
|
return;
|
|
@@ -1433,16 +1107,18 @@ ${this.#backendStderrTail.join(`
|
|
|
1433
1107
|
this.#recycleBuilderForRss(settledBytes === null ? reason : `${reason}; still ${asMib(settledBytes)}MiB after settling`);
|
|
1434
1108
|
}
|
|
1435
1109
|
static async readProcessRssBytes(pid) {
|
|
1110
|
+
if (process.platform === "win32")
|
|
1111
|
+
return await AkanAppHost.#readRssKbVia(["tasklist", "/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], AkanAppHost.#tasklistRssKb);
|
|
1436
1112
|
const status = await Bun.file(`/proc/${pid}/status`).text().catch(() => null);
|
|
1437
1113
|
const vmRssKb = status === null ? null : /VmRSS:\s+(\d+) kB/.exec(status)?.[1];
|
|
1438
1114
|
if (vmRssKb)
|
|
1439
1115
|
return Number(vmRssKb) * 1024;
|
|
1440
|
-
return await AkanAppHost.#
|
|
1116
|
+
return await AkanAppHost.#readRssKbVia(["ps", "-o", "rss=", "-p", String(pid)], (output) => Number(output.trim()));
|
|
1441
1117
|
}
|
|
1442
|
-
static async#
|
|
1118
|
+
static async#readRssKbVia(command, parseKb, timeoutMs = PS_RSS_TIMEOUT_MS) {
|
|
1443
1119
|
let proc;
|
|
1444
1120
|
try {
|
|
1445
|
-
proc = Bun.spawn(
|
|
1121
|
+
proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"] });
|
|
1446
1122
|
} catch {
|
|
1447
1123
|
return null;
|
|
1448
1124
|
}
|
|
@@ -1450,7 +1126,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1450
1126
|
try {
|
|
1451
1127
|
const output = await new Response(proc.stdout).text();
|
|
1452
1128
|
await proc.exited;
|
|
1453
|
-
const rssKb =
|
|
1129
|
+
const rssKb = parseKb(output);
|
|
1454
1130
|
return Number.isFinite(rssKb) && rssKb > 0 ? rssKb * 1024 : null;
|
|
1455
1131
|
} catch {
|
|
1456
1132
|
return null;
|
|
@@ -1458,6 +1134,10 @@ ${this.#backendStderrTail.join(`
|
|
|
1458
1134
|
clearTimeout(killer);
|
|
1459
1135
|
}
|
|
1460
1136
|
}
|
|
1137
|
+
static #tasklistRssKb(output) {
|
|
1138
|
+
const memUsage = /"([^"]*)"\s*$/m.exec(output)?.[1];
|
|
1139
|
+
return memUsage ? Number(memUsage.replace(/\D/g, "")) : Number.NaN;
|
|
1140
|
+
}
|
|
1461
1141
|
#cancelRssRecycle() {
|
|
1462
1142
|
this.#rssRecycleReason = null;
|
|
1463
1143
|
this.#rssRecycleOver = null;
|
|
@@ -1593,7 +1273,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1593
1273
|
await this.#backendGraph.refresh();
|
|
1594
1274
|
await this.#startBuilder({ announceBootState: true });
|
|
1595
1275
|
const missed = await this.#takeBuilderGapChanges();
|
|
1596
|
-
const backendFiles = [...new Set([...files.filter((file) => this.#
|
|
1276
|
+
const backendFiles = [...new Set([...files.filter((file) => this.#backendGraph.has(file)), ...missed])];
|
|
1597
1277
|
if (backendFiles.length === 0)
|
|
1598
1278
|
return;
|
|
1599
1279
|
this.logger.verbose(`[idle-suspend] ${backendFiles.length} backend file(s) changed while suspended`);
|
|
@@ -1648,10 +1328,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1648
1328
|
for (const message of pending)
|
|
1649
1329
|
this.#sendToBuilder(message);
|
|
1650
1330
|
}
|
|
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
1331
|
#discardPendingBuilderMessages(reason) {
|
|
1656
1332
|
const held = this.#pendingBuilderMessages.splice(0);
|
|
1657
1333
|
if (held.length === 0)
|
|
@@ -1663,12 +1339,17 @@ ${this.#backendStderrTail.join(`
|
|
|
1663
1339
|
if (held.length === 0)
|
|
1664
1340
|
return;
|
|
1665
1341
|
this.logger.warn(`failing ${held.length} held builder request(s): ${reason}`);
|
|
1666
|
-
for (const message of held)
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1342
|
+
for (const message of held)
|
|
1343
|
+
this.#failBuilderRequest(message, reason);
|
|
1344
|
+
}
|
|
1345
|
+
#failBuilderRequest(message, error) {
|
|
1346
|
+
if (message.type === "build-route")
|
|
1347
|
+
this.#sendToBackend({ type: "build-route-res", id: message.id, ok: false, error });
|
|
1348
|
+
else if (message.type === "build-csr")
|
|
1349
|
+
this.#sendToBackend({ type: "build-csr-res", id: message.id, ok: false, error });
|
|
1350
|
+
else
|
|
1351
|
+
return false;
|
|
1352
|
+
return true;
|
|
1672
1353
|
}
|
|
1673
1354
|
#recycleBuilderForRss(reason) {
|
|
1674
1355
|
if (this.#pendingRecycle || this.#restartTimer) {
|
|
@@ -1789,18 +1470,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1789
1470
|
}
|
|
1790
1471
|
async#recycleDevChildren(message, { refreshConfig = false } = {}) {
|
|
1791
1472
|
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
|
-
}
|
|
1473
|
+
this.#clearRestartTimers();
|
|
1804
1474
|
this.#builderRecoveryAttempts = 0;
|
|
1805
1475
|
this.#pendingRestartReason = null;
|
|
1806
1476
|
this.#lastGoodFrontend = {};
|
|
@@ -1890,7 +1560,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1890
1560
|
}
|
|
1891
1561
|
if (message.kinds.includes("code"))
|
|
1892
1562
|
await this.#backendGraph.refresh();
|
|
1893
|
-
if (message.files.some((file) => this.#
|
|
1563
|
+
if (message.files.some((file) => this.#backendGraph.has(file)))
|
|
1894
1564
|
return true;
|
|
1895
1565
|
if (!this.#backendGraph.lastRefreshSucceeded) {
|
|
1896
1566
|
const fallbackFiles = message.files.filter((file) => isLegacyBackendFallbackFile(file, this.app.workspace.workspaceRoot));
|
|
@@ -1901,9 +1571,6 @@ ${this.#backendStderrTail.join(`
|
|
|
1901
1571
|
}
|
|
1902
1572
|
return false;
|
|
1903
1573
|
}
|
|
1904
|
-
#isBackendFile(file) {
|
|
1905
|
-
return this.#backendGraph.has(file);
|
|
1906
|
-
}
|
|
1907
1574
|
async#startBuilder({
|
|
1908
1575
|
announceBootState = false
|
|
1909
1576
|
} = {}) {
|
|
@@ -1989,27 +1656,12 @@ ${this.#backendStderrTail.join(`
|
|
|
1989
1656
|
return;
|
|
1990
1657
|
const status = this.#builder?.status ?? "stopped";
|
|
1991
1658
|
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
|
-
});
|
|
1659
|
+
this.#pendingBuilderMessages.push(message);
|
|
1660
|
+
this.logger.verbose(`[builder] holding ${message.type} until the builder is ready (${this.#pendingBuilderMessages.length} waiting)`);
|
|
2002
1661
|
return;
|
|
2003
1662
|
}
|
|
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
|
-
});
|
|
1663
|
+
if (this.#failBuilderRequest(message, `builder is ${status}; reload after the builder is ready`))
|
|
2011
1664
|
return;
|
|
2012
|
-
}
|
|
2013
1665
|
this.logger.warn("akanAppHost builder is not running");
|
|
2014
1666
|
}
|
|
2015
1667
|
#stopBuilder() {
|
|
@@ -2021,25 +1673,20 @@ ${this.#backendStderrTail.join(`
|
|
|
2021
1673
|
}
|
|
2022
1674
|
}
|
|
2023
1675
|
// pkgs/@akanjs/devkit/applicationTestPreload.ts
|
|
2024
|
-
import
|
|
1676
|
+
import path3 from "path";
|
|
2025
1677
|
var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
|
|
2026
1678
|
async function resolveSignalTestPreloadPath(target) {
|
|
2027
1679
|
const candidates = [];
|
|
2028
|
-
const
|
|
1680
|
+
for (const basePath of [target.cwdPath, process.cwd(), path3.dirname(Bun.main), import.meta.dir]) {
|
|
2029
1681
|
try {
|
|
2030
|
-
candidates.push(
|
|
1682
|
+
candidates.push(path3.join(path3.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
|
|
2031
1683
|
} 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));
|
|
1684
|
+
}
|
|
1685
|
+
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
1686
|
const uniqueCandidates = [...new Set(candidates)];
|
|
2039
|
-
for (const candidate of uniqueCandidates)
|
|
1687
|
+
for (const candidate of uniqueCandidates)
|
|
2040
1688
|
if (await Bun.file(candidate).exists())
|
|
2041
1689
|
return candidate;
|
|
2042
|
-
}
|
|
2043
1690
|
throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
|
|
2044
1691
|
Probed paths:
|
|
2045
1692
|
${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
|
|
@@ -2047,12 +1694,10 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
|
|
|
2047
1694
|
}
|
|
2048
1695
|
|
|
2049
1696
|
// pkgs/@akanjs/cli/application/application.runner.ts
|
|
2050
|
-
import { Logger as
|
|
2051
|
-
|
|
2052
|
-
var
|
|
2053
|
-
var
|
|
2054
|
-
var loadCapacitorApp = async () => (await import("./capacitorApp-36rbr858.js")).CapacitorApp;
|
|
2055
|
-
var loadAbstractCompactor = async () => (await import("./abstractCompactor-mjh5ppsq.js")).AbstractCompactor;
|
|
1697
|
+
import { Logger as Logger3 } from "akanjs/common";
|
|
1698
|
+
var loadBuildRunner = async () => (await import("./applicationBuildRunner-ybyp97pv.js")).ApplicationBuildRunner;
|
|
1699
|
+
var loadReleasePackager = async () => (await import("./applicationReleasePackager-esnrphw8.js")).ApplicationReleasePackager;
|
|
1700
|
+
var loadCapacitorApp = async () => (await import("./capacitorApp-1synz4en.js")).CapacitorApp;
|
|
2056
1701
|
var loadPrompts = async () => await import("@inquirer/prompts");
|
|
2057
1702
|
|
|
2058
1703
|
class ApplicationRunner extends runner("application") {
|
|
@@ -2071,9 +1716,6 @@ class ApplicationRunner extends runner("application") {
|
|
|
2071
1716
|
async removeApplication(app) {
|
|
2072
1717
|
await app.workspace.removeDir(`apps/${app.name}`);
|
|
2073
1718
|
}
|
|
2074
|
-
async getConfig(app) {
|
|
2075
|
-
return await app.getConfig();
|
|
2076
|
-
}
|
|
2077
1719
|
async getScriptFilename(app) {
|
|
2078
1720
|
if (!await app.exists("script")) {
|
|
2079
1721
|
await app.mkdir("script");
|
|
@@ -2095,12 +1737,12 @@ class ApplicationRunner extends runner("application") {
|
|
|
2095
1737
|
if (!await app.exists(scriptPath))
|
|
2096
1738
|
throw new Error(`Script file not found: apps/${app.name}/${scriptPath}`);
|
|
2097
1739
|
await app.spawn("bun", [scriptPath], {
|
|
2098
|
-
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script" }),
|
|
1740
|
+
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script", ...await app.getDatabaseModeEnv() }),
|
|
2099
1741
|
stdio: "inherit"
|
|
2100
1742
|
});
|
|
2101
1743
|
}
|
|
2102
1744
|
#runtimeDirOf(app, override) {
|
|
2103
|
-
return
|
|
1745
|
+
return path4.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path4.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
|
|
2104
1746
|
}
|
|
2105
1747
|
async runLogs(app, options) {
|
|
2106
1748
|
const { LogControlUnavailableError, LogTailClient } = await import("akanjs/server/logging/logTailClient");
|
|
@@ -2110,13 +1752,10 @@ class ApplicationRunner extends runner("application") {
|
|
|
2110
1752
|
const { level, grep, endpoint, trace, child, role, origin, since } = options;
|
|
2111
1753
|
const query = Object.fromEntries(Object.entries({ level, grep, endpoint, trace, child, role, origin, since }).filter(([, value]) => value !== undefined && value !== null && value !== ""));
|
|
2112
1754
|
const print = (record) => process.stdout.write(options.json ? `${JSON.stringify(record)}
|
|
2113
|
-
` :
|
|
1755
|
+
` : Logger3.render(record));
|
|
2114
1756
|
const note = (text) => process.stderr.write(`[akan logs] ${text}
|
|
2115
1757
|
`);
|
|
2116
|
-
|
|
2117
|
-
const closed = new Promise((resolve) => {
|
|
2118
|
-
finish = resolve;
|
|
2119
|
-
});
|
|
1758
|
+
const { promise: closed, resolve: finish } = Promise.withResolvers();
|
|
2120
1759
|
let client;
|
|
2121
1760
|
try {
|
|
2122
1761
|
client = await LogTailClient.connect(socketPath, {
|
|
@@ -2125,11 +1764,11 @@ class ApplicationRunner extends runner("application") {
|
|
|
2125
1764
|
if (response.type === "dropped")
|
|
2126
1765
|
note(`${response.count} records dropped (reader too slow)`);
|
|
2127
1766
|
},
|
|
2128
|
-
onClose: () => finish
|
|
1767
|
+
onClose: () => finish()
|
|
2129
1768
|
});
|
|
2130
1769
|
} catch (error) {
|
|
2131
1770
|
if (error instanceof LogControlUnavailableError)
|
|
2132
|
-
throw new Error(`${app.name} is not running (no ${
|
|
1771
|
+
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
1772
|
throw error;
|
|
2134
1773
|
}
|
|
2135
1774
|
const sinceMs = since ? LogQueryMatcher.parseSince(since) : undefined;
|
|
@@ -2167,7 +1806,7 @@ const serverModule = await import(${JSON.stringify(serverPath)});
|
|
|
2167
1806
|
const { assertAkanConsoleAllowed, startAkanConsole } = await import("akanjs/server");
|
|
2168
1807
|
const server = serverModule.server;
|
|
2169
1808
|
if (!server?.start) throw new Error("server.ts must export server with start()");
|
|
2170
|
-
assertAkanConsoleAllowed(
|
|
1809
|
+
assertAkanConsoleAllowed();
|
|
2171
1810
|
await server.start({ listen: false, web: false });
|
|
2172
1811
|
try {
|
|
2173
1812
|
await startAkanConsole(server, {
|
|
@@ -2186,17 +1825,35 @@ try {
|
|
|
2186
1825
|
}
|
|
2187
1826
|
`;
|
|
2188
1827
|
await app.spawn("bun", ["-e", code], {
|
|
2189
|
-
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "console" }),
|
|
1828
|
+
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "console", ...await app.getDatabaseModeEnv() }),
|
|
1829
|
+
stdio: "inherit"
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
async transferDatabase(app, direction, dir) {
|
|
1833
|
+
const serverPath = `${app.cwdPath}/server.ts`;
|
|
1834
|
+
if (!await app.exists("server.ts"))
|
|
1835
|
+
throw new Error(`Server file not found: apps/${app.name}/server.ts`);
|
|
1836
|
+
const target = path4.resolve(app.workspace.workspaceRoot, dir);
|
|
1837
|
+
const code = `
|
|
1838
|
+
const { server } = await import(${JSON.stringify(serverPath)});
|
|
1839
|
+
const { DatabaseAdaptorRole, DocumentTransfer } = await import("akanjs/service");
|
|
1840
|
+
await server.start({ listen: false, web: false });
|
|
1841
|
+
try {
|
|
1842
|
+
const transfer = new DocumentTransfer(server.get(DatabaseAdaptorRole));
|
|
1843
|
+
const reports = await transfer.${direction === "export" ? "exportTo" : "importFrom"}(${JSON.stringify(target)});
|
|
1844
|
+
for (const { table, rows } of reports) console.info(\`${direction === "export" ? "exported" : "imported"} \${table}: \${rows} rows\`);
|
|
1845
|
+
} finally {
|
|
1846
|
+
await server.stop();
|
|
1847
|
+
}
|
|
1848
|
+
`;
|
|
1849
|
+
await app.spawn("bun", ["-e", code], {
|
|
1850
|
+
env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script", ...await app.getDatabaseModeEnv() }),
|
|
2190
1851
|
stdio: "inherit"
|
|
2191
1852
|
});
|
|
2192
1853
|
}
|
|
2193
1854
|
async typecheck(app, options = {}) {
|
|
2194
1855
|
await new (await loadBuildRunner())(app).typecheck(options);
|
|
2195
1856
|
}
|
|
2196
|
-
async compact(sys, { module, minLines, interactive } = {}) {
|
|
2197
|
-
const compactor = new (await loadAbstractCompactor())(sys, { minLines, interactive });
|
|
2198
|
-
return await compactor.compactAll({ module });
|
|
2199
|
-
}
|
|
2200
1857
|
async test(exec) {
|
|
2201
1858
|
const isSignalTarget = exec instanceof AppExecutor || exec instanceof LibExecutor;
|
|
2202
1859
|
const preloadPath = isSignalTarget ? await resolveSignalTestPreloadPath(exec) : null;
|
|
@@ -2272,13 +1929,7 @@ try {
|
|
|
2272
1929
|
await new (await loadCapacitorApp())(app, mobileTarget.config).buildAndroid("apk", { env, regenerate });
|
|
2273
1930
|
});
|
|
2274
1931
|
}
|
|
2275
|
-
async startAndroid(app, {
|
|
2276
|
-
open = false,
|
|
2277
|
-
operation = "local",
|
|
2278
|
-
env = "local",
|
|
2279
|
-
target,
|
|
2280
|
-
regenerate = false
|
|
2281
|
-
} = {}) {
|
|
1932
|
+
async startAndroid(app, { open = false, operation = "local", env = "local", target, regenerate = false } = {}) {
|
|
2282
1933
|
const targets = await resolveMobileTargets(app, target);
|
|
2283
1934
|
if (operation === "release")
|
|
2284
1935
|
await this.#buildMobileCsr(app, env);
|
|
@@ -2335,24 +1986,34 @@ try {
|
|
|
2335
1986
|
return;
|
|
2336
1987
|
for (const failure of failures) {
|
|
2337
1988
|
const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
|
|
2338
|
-
|
|
1989
|
+
Logger3.rawLog(`Mobile target ${failure.target} failed: ${message}`, undefined, "error");
|
|
2339
1990
|
}
|
|
2340
1991
|
throw new Error(`${failures.length}/${results.length} mobile targets failed`);
|
|
2341
1992
|
}
|
|
2342
|
-
async codepush(app
|
|
1993
|
+
async codepush(app) {
|
|
1994
|
+
throw new Error(`akan codepush is still in development and deploys nothing yet, so ${app.name} was not updated. To release the app's source with OTA update support, run \`akan release-source ${app.name}\`.`);
|
|
1995
|
+
}
|
|
1996
|
+
async#initCapacitorApp(app) {
|
|
2343
1997
|
const [target] = await resolveMobileTargets(app, undefined);
|
|
2344
1998
|
if (!target)
|
|
2345
1999
|
throw new Error(`No mobile target configured for ${app.name}`);
|
|
2346
2000
|
const capacitorApp = new (await loadCapacitorApp())(app, target.config);
|
|
2347
2001
|
await capacitorApp.init();
|
|
2002
|
+
return capacitorApp;
|
|
2348
2003
|
}
|
|
2349
2004
|
#getLocalDatabaseServices(mode) {
|
|
2350
2005
|
if (mode === "single")
|
|
2351
2006
|
return [];
|
|
2352
2007
|
if (mode === "multiple")
|
|
2353
|
-
return ["redis"
|
|
2008
|
+
return ["redis"];
|
|
2354
2009
|
return ["redis", "postgres"];
|
|
2355
2010
|
}
|
|
2011
|
+
async#assertComposeHas(workspace, services) {
|
|
2012
|
+
const compose = Bun.YAML.parse(await Bun.file(`${workspace.workspaceRoot}/local/docker-compose.yaml`).text());
|
|
2013
|
+
const missing = services.filter((service) => !compose?.services?.[service]);
|
|
2014
|
+
if (missing.length)
|
|
2015
|
+
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.`);
|
|
2016
|
+
}
|
|
2356
2017
|
async#isLocalDatabaseUp(workspace, mode) {
|
|
2357
2018
|
const requiredServices = this.#getLocalDatabaseServices(mode);
|
|
2358
2019
|
if (!requiredServices.length)
|
|
@@ -2366,16 +2027,18 @@ try {
|
|
|
2366
2027
|
async dbup(workspace, mode = "multiple") {
|
|
2367
2028
|
if (mode === "single")
|
|
2368
2029
|
return true;
|
|
2030
|
+
const services = this.#getLocalDatabaseServices(mode);
|
|
2031
|
+
await workspace.applyTemplate({
|
|
2032
|
+
basePath: "local",
|
|
2033
|
+
template: "localDev",
|
|
2034
|
+
dict: { repoName: workspace.repoName },
|
|
2035
|
+
overwrite: false
|
|
2036
|
+
});
|
|
2037
|
+
await this.#assertComposeHas(workspace, services);
|
|
2369
2038
|
try {
|
|
2370
|
-
await workspace.applyTemplate({
|
|
2371
|
-
basePath: "local",
|
|
2372
|
-
template: "localDev",
|
|
2373
|
-
dict: { repoName: workspace.repoName },
|
|
2374
|
-
overwrite: false
|
|
2375
|
-
});
|
|
2376
2039
|
const wasAlreadyUp = await this.#isLocalDatabaseUp(workspace, mode);
|
|
2377
2040
|
if (!wasAlreadyUp)
|
|
2378
|
-
await workspace.spawn(`docker`, ["compose", "up", "-d", ...
|
|
2041
|
+
await workspace.spawn(`docker`, ["compose", "up", "-d", ...services], {
|
|
2379
2042
|
cwd: `${workspace.workspaceRoot}/local`
|
|
2380
2043
|
});
|
|
2381
2044
|
return wasAlreadyUp;
|
|
@@ -2394,11 +2057,7 @@ ${detail}`
|
|
|
2394
2057
|
await workspace.spawn(`docker`, ["compose", "down"], { cwd: `${workspace.workspaceRoot}/local` });
|
|
2395
2058
|
}
|
|
2396
2059
|
async configureApp(app) {
|
|
2397
|
-
const
|
|
2398
|
-
if (!target)
|
|
2399
|
-
throw new Error(`No mobile target configured for ${app.name}`);
|
|
2400
|
-
const capacitorApp = new (await loadCapacitorApp())(app, target.config);
|
|
2401
|
-
await capacitorApp.init();
|
|
2060
|
+
const capacitorApp = await this.#initCapacitorApp(app);
|
|
2402
2061
|
if (await (await loadPrompts()).confirm({ message: "want to add camera permission?" }))
|
|
2403
2062
|
await capacitorApp.addCamera();
|
|
2404
2063
|
if (await (await loadPrompts()).confirm({ message: "want to add contact permission?" }))
|
|
@@ -2408,59 +2067,187 @@ ${detail}`
|
|
|
2408
2067
|
await capacitorApp.save();
|
|
2409
2068
|
}
|
|
2410
2069
|
async releaseSource(app, { rebuild, buildNum = 0, environment = "debug", local = true } = {}) {
|
|
2411
|
-
|
|
2070
|
+
const packager = new (await loadReleasePackager())(app, { build: () => this.build(app).then(() => {
|
|
2412
2071
|
return;
|
|
2413
|
-
}) })
|
|
2414
|
-
|
|
2415
|
-
buildNum,
|
|
2416
|
-
environment,
|
|
2417
|
-
local
|
|
2418
|
-
});
|
|
2419
|
-
return;
|
|
2072
|
+
}) });
|
|
2073
|
+
await packager.releaseSource({ rebuild, buildNum, environment, local });
|
|
2420
2074
|
}
|
|
2421
|
-
|
|
2422
|
-
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// pkgs/@akanjs/cli/application/hostProcessProbe.ts
|
|
2078
|
+
import { readdirSync, readFileSync, readlinkSync } from "fs";
|
|
2079
|
+
|
|
2080
|
+
class HostProcessProbe {
|
|
2081
|
+
static timeoutMs = 3000;
|
|
2082
|
+
static win32TimeoutMs = 20000;
|
|
2083
|
+
static #procTcpListen = "0A";
|
|
2084
|
+
static #win32ProcessQuery = "[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine,CreationDate | ConvertTo-Json -Compress";
|
|
2085
|
+
platform;
|
|
2086
|
+
#win32Table = null;
|
|
2087
|
+
constructor(platform = process.platform) {
|
|
2088
|
+
this.platform = platform;
|
|
2089
|
+
}
|
|
2090
|
+
async listenersOn(port) {
|
|
2091
|
+
if (this.platform === "win32")
|
|
2092
|
+
return HostProcessProbe.parseNetstat(await HostProcessProbe.run(["netstat", "-ano"]), port);
|
|
2093
|
+
if (this.platform === "linux")
|
|
2094
|
+
return HostProcessProbe.#procListenersOn(port);
|
|
2095
|
+
const output = await HostProcessProbe.run(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
|
|
2096
|
+
return [
|
|
2097
|
+
...new Set(output.split(`
|
|
2098
|
+
`).map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0))
|
|
2099
|
+
];
|
|
2423
2100
|
}
|
|
2424
|
-
async
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
return;
|
|
2101
|
+
async parentOf(pid) {
|
|
2102
|
+
if (this.platform === "win32") {
|
|
2103
|
+
const child = await this.#win32Process(pid);
|
|
2104
|
+
const parent = child ? await this.#win32Process(child.ppid) : null;
|
|
2105
|
+
return child && parent ? HostProcessProbe.parentPidOf(child, parent) : null;
|
|
2106
|
+
}
|
|
2107
|
+
if (this.platform === "linux") {
|
|
2108
|
+
const stat = HostProcessProbe.#readProc(pid, "stat");
|
|
2109
|
+
return stat === null ? null : HostProcessProbe.parseProcStatPpid(stat);
|
|
2110
|
+
}
|
|
2111
|
+
const parsed = Number((await HostProcessProbe.run(["ps", "-p", String(pid), "-o", "ppid="])).trim());
|
|
2112
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
2436
2113
|
}
|
|
2437
|
-
async
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
const
|
|
2455
|
-
const
|
|
2456
|
-
|
|
2457
|
-
|
|
2114
|
+
async commandOf(pid) {
|
|
2115
|
+
if (this.platform === "win32")
|
|
2116
|
+
return (await this.#win32Process(pid))?.command ?? null;
|
|
2117
|
+
if (this.platform === "linux") {
|
|
2118
|
+
const cmdline = HostProcessProbe.#readProc(pid, "cmdline");
|
|
2119
|
+
return cmdline?.split("\x00").filter(Boolean).join(" ") || null;
|
|
2120
|
+
}
|
|
2121
|
+
return (await HostProcessProbe.run(["ps", "-ww", "-p", String(pid), "-o", "command="])).trim() || null;
|
|
2122
|
+
}
|
|
2123
|
+
static parentPidOf(child, parent) {
|
|
2124
|
+
if (child.ppid !== parent.pid)
|
|
2125
|
+
return null;
|
|
2126
|
+
if (parent.startedAt !== null && child.startedAt !== null && parent.startedAt > child.startedAt)
|
|
2127
|
+
return null;
|
|
2128
|
+
return parent.pid;
|
|
2129
|
+
}
|
|
2130
|
+
static parseNetstat(output, port) {
|
|
2131
|
+
const pids = new Set;
|
|
2132
|
+
for (const line of output.split(/\r?\n/)) {
|
|
2133
|
+
const fields = line.trim().split(/\s+/);
|
|
2134
|
+
if (fields[0] !== "TCP" || fields.length < 5)
|
|
2135
|
+
continue;
|
|
2136
|
+
const [, local, remote] = fields;
|
|
2137
|
+
if (!local?.endsWith(`:${port}`) || !remote?.endsWith(":0"))
|
|
2138
|
+
continue;
|
|
2139
|
+
const pid = Number(fields.at(-1));
|
|
2140
|
+
if (Number.isInteger(pid) && pid > 0)
|
|
2141
|
+
pids.add(pid);
|
|
2142
|
+
}
|
|
2143
|
+
return [...pids];
|
|
2144
|
+
}
|
|
2145
|
+
static parseProcNetTcp(text, port) {
|
|
2146
|
+
const inodes = [];
|
|
2147
|
+
for (const line of text.split(`
|
|
2148
|
+
`).slice(1)) {
|
|
2149
|
+
const fields = line.trim().split(/\s+/);
|
|
2150
|
+
const [, local, , state, , , , , , inode] = fields;
|
|
2151
|
+
if (!local || !inode || state !== HostProcessProbe.#procTcpListen || inode === "0")
|
|
2152
|
+
continue;
|
|
2153
|
+
if (Number.parseInt(local.slice(local.lastIndexOf(":") + 1), 16) === port)
|
|
2154
|
+
inodes.push(inode);
|
|
2155
|
+
}
|
|
2156
|
+
return inodes;
|
|
2157
|
+
}
|
|
2158
|
+
static parseProcStatPpid(stat) {
|
|
2159
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
2160
|
+
return Number.isInteger(ppid) && ppid > 0 ? ppid : null;
|
|
2161
|
+
}
|
|
2162
|
+
static parseWin32Processes(json) {
|
|
2163
|
+
const table = new Map;
|
|
2164
|
+
let parsed;
|
|
2165
|
+
try {
|
|
2166
|
+
parsed = JSON.parse(json.trim() || "[]");
|
|
2167
|
+
} catch {
|
|
2168
|
+
return table;
|
|
2169
|
+
}
|
|
2170
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
2171
|
+
for (const row of rows) {
|
|
2172
|
+
if (typeof row?.ProcessId !== "number")
|
|
2173
|
+
continue;
|
|
2174
|
+
table.set(row.ProcessId, {
|
|
2175
|
+
pid: row.ProcessId,
|
|
2176
|
+
ppid: row.ParentProcessId ?? 0,
|
|
2177
|
+
command: row.CommandLine?.trim() || null,
|
|
2178
|
+
startedAt: HostProcessProbe.#parseCimDate(row.CreationDate)
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
return table;
|
|
2182
|
+
}
|
|
2183
|
+
static async run(command, timeoutMs = HostProcessProbe.timeoutMs) {
|
|
2184
|
+
let proc;
|
|
2185
|
+
try {
|
|
2186
|
+
proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
|
|
2187
|
+
} catch {
|
|
2188
|
+
return "";
|
|
2189
|
+
}
|
|
2190
|
+
const timer = setTimeout(() => proc.kill("SIGKILL"), timeoutMs);
|
|
2191
|
+
try {
|
|
2192
|
+
return await new Response(proc.stdout).text();
|
|
2193
|
+
} catch {
|
|
2194
|
+
return "";
|
|
2195
|
+
} finally {
|
|
2196
|
+
clearTimeout(timer);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
async#win32Process(pid) {
|
|
2200
|
+
this.#win32Table ??= await HostProcessProbe.#queryWin32Processes();
|
|
2201
|
+
if (!this.#win32Table.has(pid))
|
|
2202
|
+
this.#win32Table = await HostProcessProbe.#queryWin32Processes();
|
|
2203
|
+
return this.#win32Table.get(pid) ?? null;
|
|
2204
|
+
}
|
|
2205
|
+
static async#queryWin32Processes() {
|
|
2206
|
+
const output = await HostProcessProbe.run(["powershell", "-NoProfile", "-NonInteractive", "-Command", HostProcessProbe.#win32ProcessQuery], HostProcessProbe.win32TimeoutMs);
|
|
2207
|
+
return HostProcessProbe.parseWin32Processes(output);
|
|
2208
|
+
}
|
|
2209
|
+
static #parseCimDate(value) {
|
|
2210
|
+
if (!value)
|
|
2211
|
+
return null;
|
|
2212
|
+
const legacy = /\/Date\((\d+)/.exec(value)?.[1];
|
|
2213
|
+
const ms = legacy ? Number(legacy) : Date.parse(value);
|
|
2214
|
+
return Number.isFinite(ms) ? ms : null;
|
|
2215
|
+
}
|
|
2216
|
+
static #procListenersOn(port) {
|
|
2217
|
+
const inodes = new Set(["tcp", "tcp6"].flatMap((table) => HostProcessProbe.parseProcNetTcp(HostProcessProbe.#readProc("net", table) ?? "", port)));
|
|
2218
|
+
if (!inodes.size)
|
|
2219
|
+
return [];
|
|
2220
|
+
const pids = [];
|
|
2221
|
+
for (const entry of HostProcessProbe.#listDir("/proc")) {
|
|
2222
|
+
if (!/^\d+$/.test(entry))
|
|
2223
|
+
continue;
|
|
2224
|
+
const holds = HostProcessProbe.#listDir(`/proc/${entry}/fd`).some((fd) => {
|
|
2225
|
+
const link = HostProcessProbe.#orElse(() => readlinkSync(`/proc/${entry}/fd/${fd}`), "");
|
|
2226
|
+
const inode = /^socket:\[(\d+)\]$/.exec(link)?.[1];
|
|
2227
|
+
return !!inode && inodes.has(inode);
|
|
2228
|
+
});
|
|
2229
|
+
if (holds)
|
|
2230
|
+
pids.push(Number(entry));
|
|
2231
|
+
}
|
|
2232
|
+
return pids;
|
|
2233
|
+
}
|
|
2234
|
+
static #readProc(pid, file) {
|
|
2235
|
+
return HostProcessProbe.#orElse(() => readFileSync(`/proc/${pid}/${file}`, "utf8"), null);
|
|
2236
|
+
}
|
|
2237
|
+
static #listDir(dir) {
|
|
2238
|
+
return HostProcessProbe.#orElse(() => readdirSync(dir), []);
|
|
2239
|
+
}
|
|
2240
|
+
static #orElse(read, fallback) {
|
|
2241
|
+
try {
|
|
2242
|
+
return read();
|
|
2243
|
+
} catch {
|
|
2244
|
+
return fallback;
|
|
2245
|
+
}
|
|
2458
2246
|
}
|
|
2459
2247
|
}
|
|
2460
2248
|
|
|
2461
2249
|
// pkgs/@akanjs/cli/application/devPortReclaimer.ts
|
|
2462
2250
|
class DevPortReclaimer {
|
|
2463
|
-
static lsofTimeoutMs = 3000;
|
|
2464
2251
|
static releaseTimeoutMs = 8000;
|
|
2465
2252
|
static maxAncestorDepth = 8;
|
|
2466
2253
|
static #releasePollMs = 100;
|
|
@@ -2469,7 +2256,12 @@ class DevPortReclaimer {
|
|
|
2469
2256
|
return [...new Set(ports.flatMap((port) => [port, port + DevPortReclaimer.wsPortOffset]))].sort((left, right) => left - right);
|
|
2470
2257
|
}
|
|
2471
2258
|
static isAkanCommand(command) {
|
|
2472
|
-
|
|
2259
|
+
const normalized = command.replaceAll("\\", "/");
|
|
2260
|
+
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");
|
|
2261
|
+
}
|
|
2262
|
+
#probe;
|
|
2263
|
+
constructor(probe = new HostProcessProbe) {
|
|
2264
|
+
this.#probe = probe;
|
|
2473
2265
|
}
|
|
2474
2266
|
async reclaim(ports) {
|
|
2475
2267
|
const killed = [];
|
|
@@ -2494,10 +2286,10 @@ class DevPortReclaimer {
|
|
|
2494
2286
|
}
|
|
2495
2287
|
async holdersOf(port) {
|
|
2496
2288
|
const holders = [];
|
|
2497
|
-
for (const listenerPid of await this.#
|
|
2289
|
+
for (const listenerPid of await this.#probe.listenersOn(port)) {
|
|
2498
2290
|
if (listenerPid === process.pid || listenerPid === process.ppid)
|
|
2499
2291
|
continue;
|
|
2500
|
-
const command = await this.#commandOf(listenerPid);
|
|
2292
|
+
const command = await this.#probe.commandOf(listenerPid);
|
|
2501
2293
|
if (!command)
|
|
2502
2294
|
continue;
|
|
2503
2295
|
if (!DevPortReclaimer.isAkanCommand(command)) {
|
|
@@ -2512,44 +2304,37 @@ class DevPortReclaimer {
|
|
|
2512
2304
|
async#akanRootOf(pid, command) {
|
|
2513
2305
|
let root = { pid, command };
|
|
2514
2306
|
for (let depth = 0;depth < DevPortReclaimer.maxAncestorDepth; depth += 1) {
|
|
2515
|
-
const parentPid = await this.#
|
|
2307
|
+
const parentPid = await this.#probe.parentOf(root.pid);
|
|
2516
2308
|
if (parentPid === null || parentPid <= 1 || parentPid === process.pid || parentPid === process.ppid)
|
|
2517
2309
|
break;
|
|
2518
|
-
const parentCommand = await this.#commandOf(parentPid);
|
|
2310
|
+
const parentCommand = await this.#probe.commandOf(parentPid);
|
|
2519
2311
|
if (!parentCommand || !DevPortReclaimer.isAkanCommand(parentCommand))
|
|
2520
2312
|
break;
|
|
2521
2313
|
root = { pid: parentPid, command: parentCommand };
|
|
2522
2314
|
}
|
|
2523
2315
|
return root;
|
|
2524
2316
|
}
|
|
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
2317
|
async#terminate(pid) {
|
|
2318
|
+
if (this.#probe.platform === "win32") {
|
|
2319
|
+
await HostProcessProbe.run(["taskkill", "/PID", String(pid), "/T", "/F"]);
|
|
2320
|
+
return await this.#released(pid);
|
|
2321
|
+
}
|
|
2542
2322
|
if (!this.#signal(pid, "SIGTERM"))
|
|
2543
2323
|
return !this.#alive(pid);
|
|
2324
|
+
if (await this.#released(pid))
|
|
2325
|
+
return true;
|
|
2326
|
+
this.#signal(pid, "SIGKILL");
|
|
2327
|
+
await Bun.sleep(DevPortReclaimer.#releasePollMs);
|
|
2328
|
+
return !this.#alive(pid);
|
|
2329
|
+
}
|
|
2330
|
+
async#released(pid) {
|
|
2544
2331
|
const deadline = Date.now() + DevPortReclaimer.releaseTimeoutMs;
|
|
2545
2332
|
while (Date.now() < deadline) {
|
|
2546
2333
|
if (!this.#alive(pid))
|
|
2547
2334
|
return true;
|
|
2548
2335
|
await Bun.sleep(DevPortReclaimer.#releasePollMs);
|
|
2549
2336
|
}
|
|
2550
|
-
|
|
2551
|
-
await Bun.sleep(DevPortReclaimer.#releasePollMs);
|
|
2552
|
-
return !this.#alive(pid);
|
|
2337
|
+
return false;
|
|
2553
2338
|
}
|
|
2554
2339
|
#signal(pid, signal) {
|
|
2555
2340
|
try {
|
|
@@ -2567,26 +2352,15 @@ class DevPortReclaimer {
|
|
|
2567
2352
|
return error.code === "EPERM";
|
|
2568
2353
|
}
|
|
2569
2354
|
}
|
|
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
2355
|
}
|
|
2582
2356
|
|
|
2583
2357
|
// pkgs/@akanjs/cli/application/devStreamView.ts
|
|
2584
|
-
import { Logger as
|
|
2358
|
+
import { Logger as Logger4 } from "akanjs/common";
|
|
2585
2359
|
import chalk from "chalk";
|
|
2586
2360
|
var appColors = ["cyan", "magenta", "yellow", "green", "blue", "red"];
|
|
2587
2361
|
|
|
2588
2362
|
class DevStreamView {
|
|
2589
|
-
#logger = new
|
|
2363
|
+
#logger = new Logger4("akan start");
|
|
2590
2364
|
#partial = new Map;
|
|
2591
2365
|
#lastState = new Map;
|
|
2592
2366
|
#color = new Map;
|
|
@@ -2690,8 +2464,9 @@ class DevBootConcurrency {
|
|
|
2690
2464
|
|
|
2691
2465
|
// pkgs/@akanjs/cli/application/devSessionLog.ts
|
|
2692
2466
|
import { mkdir, rename } from "fs/promises";
|
|
2693
|
-
import
|
|
2467
|
+
import path5 from "path";
|
|
2694
2468
|
import dayjs from "dayjs";
|
|
2469
|
+
|
|
2695
2470
|
class DevSessionLog {
|
|
2696
2471
|
static fileName = "dev.log";
|
|
2697
2472
|
static previousFileName = "dev.prev.log";
|
|
@@ -2713,19 +2488,19 @@ class DevSessionLog {
|
|
|
2713
2488
|
return [...this.#appNames];
|
|
2714
2489
|
}
|
|
2715
2490
|
pathOf(app) {
|
|
2716
|
-
return
|
|
2491
|
+
return path5.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
|
|
2717
2492
|
}
|
|
2718
2493
|
previousPathOf(app) {
|
|
2719
|
-
return
|
|
2494
|
+
return path5.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
|
|
2720
2495
|
}
|
|
2721
2496
|
relativePathOf(app) {
|
|
2722
|
-
return
|
|
2497
|
+
return path5.relative(this.#workspaceRoot, this.pathOf(app));
|
|
2723
2498
|
}
|
|
2724
2499
|
async open() {
|
|
2725
2500
|
const stamp = dayjs(this.#now()).format("YYYY-MM-DD HH:mm:ss");
|
|
2726
2501
|
for (const app of this.#appNames) {
|
|
2727
2502
|
const file = this.pathOf(app);
|
|
2728
|
-
await mkdir(
|
|
2503
|
+
await mkdir(path5.dirname(file), { recursive: true });
|
|
2729
2504
|
await rename(file, this.previousPathOf(app)).catch(() => {
|
|
2730
2505
|
return;
|
|
2731
2506
|
});
|
|
@@ -2769,7 +2544,7 @@ class DevSessionLog {
|
|
|
2769
2544
|
this.#writers.clear();
|
|
2770
2545
|
}
|
|
2771
2546
|
#runtimeDirOf(app) {
|
|
2772
|
-
return
|
|
2547
|
+
return path5.join(this.#workspaceRoot, "local", "apps", app, "runtime");
|
|
2773
2548
|
}
|
|
2774
2549
|
#writeLine(app, text) {
|
|
2775
2550
|
const writer = this.#writers.get(app);
|
|
@@ -2792,6 +2567,16 @@ class DevSessionLog {
|
|
|
2792
2567
|
|
|
2793
2568
|
// pkgs/@akanjs/cli/application/devSupervisor.ts
|
|
2794
2569
|
class DevSupervisor {
|
|
2570
|
+
static resolveDevUi(plain, {
|
|
2571
|
+
isTty = !!process.stdout.isTTY,
|
|
2572
|
+
columns = process.stdout.columns ?? 0
|
|
2573
|
+
} = {}) {
|
|
2574
|
+
if (plain)
|
|
2575
|
+
return { mode: "stream", downgraded: false };
|
|
2576
|
+
if (!isTty || columns <= 0)
|
|
2577
|
+
return { mode: "stream", downgraded: true };
|
|
2578
|
+
return { mode: "tui", downgraded: false };
|
|
2579
|
+
}
|
|
2795
2580
|
static supervisedEnvKey = "AKAN_DEV_SUPERVISED";
|
|
2796
2581
|
static childHooks() {
|
|
2797
2582
|
if (process.env[DevSupervisor.supervisedEnvKey] !== "1" || !process.send)
|
|
@@ -2855,16 +2640,14 @@ class DevSupervisor {
|
|
|
2855
2640
|
return ports;
|
|
2856
2641
|
}
|
|
2857
2642
|
#makeChild(app, port) {
|
|
2858
|
-
|
|
2859
|
-
const ready = new Promise((resolve) => {
|
|
2860
|
-
markReady = resolve;
|
|
2861
|
-
});
|
|
2643
|
+
const { promise: ready, resolve: markReady } = Promise.withResolvers();
|
|
2862
2644
|
return {
|
|
2863
2645
|
status: {
|
|
2864
2646
|
app,
|
|
2865
2647
|
name: app.name,
|
|
2866
2648
|
port,
|
|
2867
2649
|
url: `http://localhost:${port}`,
|
|
2650
|
+
shareUrl: this.#options.shares?.get(app.name) ?? null,
|
|
2868
2651
|
state: "starting",
|
|
2869
2652
|
detail: "queued",
|
|
2870
2653
|
exitCode: null
|
|
@@ -2894,16 +2677,18 @@ class DevSupervisor {
|
|
|
2894
2677
|
}
|
|
2895
2678
|
}
|
|
2896
2679
|
async#waitForReady(child) {
|
|
2897
|
-
|
|
2680
|
+
if (await DevSupervisor.timesOut(child.ready, DevSupervisor.readyTimeoutMs))
|
|
2681
|
+
this.#note(`${child.status.name} has not reported ready after ${Math.round(DevSupervisor.readyTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
|
|
2682
|
+
}
|
|
2683
|
+
static async timesOut(work, ms) {
|
|
2684
|
+
let timer;
|
|
2898
2685
|
const expired = new Promise((resolve) => {
|
|
2899
|
-
timer = setTimeout(() => resolve(
|
|
2686
|
+
timer = setTimeout(() => resolve(true), ms);
|
|
2900
2687
|
});
|
|
2901
2688
|
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");
|
|
2689
|
+
return await Promise.race([work.then(() => false), expired]);
|
|
2904
2690
|
} finally {
|
|
2905
|
-
|
|
2906
|
-
clearTimeout(timer);
|
|
2691
|
+
clearTimeout(timer);
|
|
2907
2692
|
}
|
|
2908
2693
|
}
|
|
2909
2694
|
static childArgs(name, { write = true } = {}) {
|
|
@@ -3025,44 +2810,68 @@ class DevSupervisor {
|
|
|
3025
2810
|
for (const child of running)
|
|
3026
2811
|
child.proc.kill("SIGTERM");
|
|
3027
2812
|
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);
|
|
2813
|
+
if (!await DevSupervisor.timesOut(exited, DevSupervisor.shutdownGraceMs))
|
|
2814
|
+
return;
|
|
2815
|
+
for (const child of running) {
|
|
2816
|
+
if (child.proc.killed)
|
|
2817
|
+
continue;
|
|
2818
|
+
this.#note(`${child.status.name} did not exit in time; killing it`, "warn");
|
|
2819
|
+
child.proc.kill("SIGKILL");
|
|
3044
2820
|
}
|
|
3045
2821
|
}
|
|
3046
2822
|
}
|
|
3047
2823
|
|
|
3048
|
-
// pkgs/@akanjs/cli/application/
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
2824
|
+
// pkgs/@akanjs/cli/application/interruptTeardown.ts
|
|
2825
|
+
import { Logger as Logger5 } from "akanjs/common";
|
|
2826
|
+
|
|
2827
|
+
class InterruptTeardown {
|
|
2828
|
+
#teardowns = [];
|
|
2829
|
+
#exit;
|
|
2830
|
+
#listen;
|
|
2831
|
+
#report;
|
|
2832
|
+
#interrupted = false;
|
|
2833
|
+
ownsExit = true;
|
|
2834
|
+
constructor({ exit, listen, report } = {}) {
|
|
2835
|
+
this.#exit = exit ?? ((code) => process.exit(code));
|
|
2836
|
+
this.#listen = listen ?? ((onSignal) => {
|
|
2837
|
+
process.on("SIGINT", onSignal);
|
|
2838
|
+
});
|
|
2839
|
+
this.#report = report ?? ((message) => Logger5.rawLog(message, undefined, "error"));
|
|
2840
|
+
}
|
|
2841
|
+
add(run, abandoned) {
|
|
2842
|
+
this.#teardowns.push({ run, abandoned, running: false });
|
|
2843
|
+
if (this.#teardowns.length === 1)
|
|
2844
|
+
this.#listen(() => this.#onSignal());
|
|
2845
|
+
}
|
|
2846
|
+
#onSignal() {
|
|
2847
|
+
if (this.#interrupted) {
|
|
2848
|
+
for (const one of this.#teardowns)
|
|
2849
|
+
if (one.running)
|
|
2850
|
+
this.#report(one.abandoned);
|
|
2851
|
+
this.#exit(130);
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2854
|
+
this.#interrupted = true;
|
|
2855
|
+
Promise.all(this.#teardowns.map(async (one) => {
|
|
2856
|
+
one.running = true;
|
|
2857
|
+
await one.run().catch(() => {
|
|
2858
|
+
return;
|
|
2859
|
+
});
|
|
2860
|
+
one.running = false;
|
|
2861
|
+
})).finally(() => {
|
|
2862
|
+
if (this.ownsExit)
|
|
2863
|
+
this.#exit(0);
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
3056
2867
|
|
|
3057
2868
|
// pkgs/@akanjs/cli/application/application.script.ts
|
|
3058
2869
|
class ApplicationScript extends script("application", [ApplicationRunner, LibraryScript]) {
|
|
3059
2870
|
static dbShutdownTimeoutMs = 20000;
|
|
2871
|
+
#interrupt = new InterruptTeardown;
|
|
3060
2872
|
async confirmDatabaseModeDependencyInstall(databaseMode, installSpecs) {
|
|
3061
2873
|
return await confirm({
|
|
3062
|
-
message:
|
|
3063
|
-
`Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}.`,
|
|
3064
|
-
"Install them now?"
|
|
3065
|
-
].join(" "),
|
|
2874
|
+
message: `Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}. Install them now?`,
|
|
3066
2875
|
default: true
|
|
3067
2876
|
});
|
|
3068
2877
|
}
|
|
@@ -3073,21 +2882,11 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3073
2882
|
const shouldInstall = await this.confirmDatabaseModeDependencyInstall(databaseMode, installSpecs);
|
|
3074
2883
|
if (!shouldInstall)
|
|
3075
2884
|
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
|
-
}
|
|
2885
|
+
await this.#addDependencies(app, installSpecs, `database dependencies for ${databaseMode} mode`);
|
|
3087
2886
|
}
|
|
3088
2887
|
async confirmMobileDependencyInstall(installSpecs) {
|
|
3089
2888
|
return await confirm({
|
|
3090
|
-
message:
|
|
2889
|
+
message: `Mobile builds require missing dependencies: ${installSpecs.join(", ")}. Install them now?`,
|
|
3091
2890
|
default: true
|
|
3092
2891
|
});
|
|
3093
2892
|
}
|
|
@@ -3098,15 +2897,16 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3098
2897
|
const shouldInstall = await this.confirmMobileDependencyInstall(installSpecs);
|
|
3099
2898
|
if (!shouldInstall)
|
|
3100
2899
|
throw new Error(`Mobile builds require missing dependencies: ${installSpecs.join(", ")}.`);
|
|
3101
|
-
|
|
2900
|
+
await this.#addDependencies(app, installSpecs, "mobile dependencies");
|
|
2901
|
+
}
|
|
2902
|
+
async#addDependencies(app, installSpecs, what) {
|
|
2903
|
+
const spinner = app.workspace.spinning(`Installing ${what}...`);
|
|
3102
2904
|
try {
|
|
3103
|
-
await app.workspace.spawn("bun", ["add", ...installSpecs], {
|
|
3104
|
-
stdio: "inherit"
|
|
3105
|
-
});
|
|
2905
|
+
await app.workspace.spawn("bun", ["add", ...installSpecs], { stdio: "inherit" });
|
|
3106
2906
|
await app.workspace.getPackageJson({ refresh: true });
|
|
3107
|
-
spinner.succeed(
|
|
2907
|
+
spinner.succeed(`Installed ${what}`);
|
|
3108
2908
|
} catch (error) {
|
|
3109
|
-
spinner.fail(
|
|
2909
|
+
spinner.fail(`Failed to install ${what}`);
|
|
3110
2910
|
throw error;
|
|
3111
2911
|
}
|
|
3112
2912
|
}
|
|
@@ -3156,17 +2956,6 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3156
2956
|
else
|
|
3157
2957
|
await this.libraryScript.syncLibrary(sys);
|
|
3158
2958
|
}
|
|
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
2959
|
async script(app, filename) {
|
|
3171
2960
|
const scriptFilename = filename ?? await this.applicationRunner.getScriptFilename(app);
|
|
3172
2961
|
await app.scanSync();
|
|
@@ -3184,10 +2973,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3184
2973
|
if (!quiet)
|
|
3185
2974
|
Logger6.rawLog(`Creating an optimized production build for ${app.name}...`);
|
|
3186
2975
|
try {
|
|
3187
|
-
const result = await this.applicationRunner.build(app, {
|
|
3188
|
-
fast,
|
|
3189
|
-
spinner: !quiet
|
|
3190
|
-
});
|
|
2976
|
+
const result = await this.applicationRunner.build(app, { fast, spinner: !quiet });
|
|
3191
2977
|
Logger6.rawLog(`${app.name} built in dist/apps/${app.name}`);
|
|
3192
2978
|
if (!quiet)
|
|
3193
2979
|
ApplicationBuildReporter.printSummary(result);
|
|
@@ -3212,8 +2998,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3212
2998
|
async test(exec, { write = true } = {}) {
|
|
3213
2999
|
if (exec instanceof LibExecutor) {
|
|
3214
3000
|
await this.libraryScript.syncLibrary(exec);
|
|
3215
|
-
|
|
3216
|
-
spinner.succeed(`${exec.name} prepared`);
|
|
3001
|
+
exec.spinning(`Preparing ${exec.name}...`).succeed(`${exec.name} prepared`);
|
|
3217
3002
|
await this.applicationRunner.test(exec);
|
|
3218
3003
|
return;
|
|
3219
3004
|
}
|
|
@@ -3230,23 +3015,33 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3230
3015
|
}
|
|
3231
3016
|
await this.applicationRunner.test(exec);
|
|
3232
3017
|
}
|
|
3233
|
-
async start(apps, {
|
|
3018
|
+
async start(apps, {
|
|
3019
|
+
open = false,
|
|
3020
|
+
dbup = true,
|
|
3021
|
+
write = true,
|
|
3022
|
+
plain = false,
|
|
3023
|
+
kill = false,
|
|
3024
|
+
share = false,
|
|
3025
|
+
concurrency = null
|
|
3026
|
+
} = {}) {
|
|
3234
3027
|
const first = apps[0];
|
|
3235
3028
|
if (!first)
|
|
3236
3029
|
throw new Error("No app selected to start");
|
|
3237
|
-
const { mode, downgraded } = resolveDevUi(plain);
|
|
3030
|
+
const { mode, downgraded } = DevSupervisor.resolveDevUi(plain);
|
|
3238
3031
|
if (downgraded)
|
|
3239
3032
|
first.workspace.log("no sized terminal to draw on; printing prefixed lines instead");
|
|
3240
3033
|
if (kill)
|
|
3241
3034
|
await this.reclaimDevPorts(apps);
|
|
3035
|
+
const shares = share ? await this.#shareOnInterrupt(apps) : null;
|
|
3242
3036
|
if (mode === "stream" && apps.length === 1)
|
|
3243
3037
|
return await this.startOne(first, { open, dbup, write, ...DevSupervisor.childHooks() });
|
|
3244
|
-
|
|
3038
|
+
this.#interrupt.ownsExit = false;
|
|
3039
|
+
await this.#startMany(apps, { open, dbup, write, mode, concurrency, shares });
|
|
3245
3040
|
}
|
|
3246
3041
|
async startOne(app, { open = false, dbup = true, stdio = "inherit", write = true, onDevEvent } = {}) {
|
|
3247
3042
|
await app.scanSync({ write });
|
|
3248
3043
|
const akanConfig = await app.getConfig();
|
|
3249
|
-
const databaseMode =
|
|
3044
|
+
const databaseMode = akanConfig.resolveDatabaseMode();
|
|
3250
3045
|
await this.syncDatabaseModeDependencies(app, akanConfig, databaseMode);
|
|
3251
3046
|
if (app.getEnv() === "local" && dbup && databaseMode !== "single") {
|
|
3252
3047
|
const wasDbAlreadyUp = await this.dbup(app.workspace, databaseMode);
|
|
@@ -3254,15 +3049,12 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3254
3049
|
this.#stopDatabaseOnInterrupt(app.workspace);
|
|
3255
3050
|
}
|
|
3256
3051
|
const spinner = app.spinning("Preparing backend...");
|
|
3257
|
-
|
|
3052
|
+
return await this.applicationRunner.start(app, {
|
|
3258
3053
|
open,
|
|
3259
|
-
onStart: () => {
|
|
3260
|
-
spinner.succeed(`${app.name} prepared, ready to start`);
|
|
3261
|
-
},
|
|
3054
|
+
onStart: () => spinner.succeed(`${app.name} prepared, ready to start`),
|
|
3262
3055
|
onDevEvent,
|
|
3263
3056
|
stdio
|
|
3264
3057
|
});
|
|
3265
|
-
return akanAppHost;
|
|
3266
3058
|
}
|
|
3267
3059
|
async reclaimDevPorts(apps) {
|
|
3268
3060
|
const workspace = apps[0]?.workspace;
|
|
@@ -3277,13 +3069,20 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3277
3069
|
if (killed.length === 0 && foreign.length === 0)
|
|
3278
3070
|
workspace.log("dev ports are free; nothing to kill");
|
|
3279
3071
|
}
|
|
3280
|
-
async#startMany(apps, {
|
|
3072
|
+
async#startMany(apps, {
|
|
3073
|
+
open,
|
|
3074
|
+
dbup,
|
|
3075
|
+
write,
|
|
3076
|
+
mode,
|
|
3077
|
+
concurrency,
|
|
3078
|
+
shares
|
|
3079
|
+
}) {
|
|
3281
3080
|
const workspace = apps[0]?.workspace;
|
|
3282
3081
|
if (!workspace)
|
|
3283
3082
|
throw new Error("No app selected to start");
|
|
3284
3083
|
const startedDatabase = dbup ? await this.#prepareSharedDatabase(apps) : false;
|
|
3285
|
-
const supervisor = new DevSupervisor({ apps, mode, concurrency, open, write });
|
|
3286
|
-
const view = mode === "tui" ?
|
|
3084
|
+
const supervisor = new DevSupervisor({ apps, mode, concurrency, open, write, shares });
|
|
3085
|
+
const view = mode === "tui" ? new (await import("./devTui-q7k7bakd.js")).DevTui(supervisor) : new DevStreamView(apps.map((app) => app.name));
|
|
3287
3086
|
await supervisor.run(view);
|
|
3288
3087
|
if (startedDatabase)
|
|
3289
3088
|
await this.#stopDatabase(workspace);
|
|
@@ -3297,7 +3096,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3297
3096
|
if (app.getEnv() !== "local")
|
|
3298
3097
|
continue;
|
|
3299
3098
|
const akanConfig = await app.getConfig();
|
|
3300
|
-
const mode =
|
|
3099
|
+
const mode = akanConfig.resolveDatabaseMode();
|
|
3301
3100
|
await this.syncDatabaseModeDependencies(app, akanConfig, mode);
|
|
3302
3101
|
if (mode !== "single")
|
|
3303
3102
|
modes.add(mode);
|
|
@@ -3358,23 +3157,13 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3358
3157
|
const akanConfig = await app.getConfig();
|
|
3359
3158
|
await this.syncMobileDependencies(app, akanConfig);
|
|
3360
3159
|
await this.syncMobileAppCapacitorPlugins(app, akanConfig);
|
|
3361
|
-
await this.applicationRunner.startAndroid(app, {
|
|
3362
|
-
open,
|
|
3363
|
-
operation,
|
|
3364
|
-
env,
|
|
3365
|
-
target,
|
|
3366
|
-
regenerate
|
|
3367
|
-
});
|
|
3160
|
+
await this.applicationRunner.startAndroid(app, { open, operation, env, target, regenerate });
|
|
3368
3161
|
}
|
|
3369
3162
|
async releaseAndroid(app, assembleType, { write = true, target, env = "main", regenerate = false, allowLocalRelease = false } = {}) {
|
|
3370
3163
|
await app.scanSync({ write });
|
|
3371
3164
|
if (env === "local" && !allowLocalRelease)
|
|
3372
3165
|
throw new Error("releaseAndroid --env local is blocked. Pass allowLocalRelease only for explicit local release testing.");
|
|
3373
|
-
await this.applicationRunner.releaseAndroid(app, assembleType, {
|
|
3374
|
-
target,
|
|
3375
|
-
env,
|
|
3376
|
-
regenerate
|
|
3377
|
-
});
|
|
3166
|
+
await this.applicationRunner.releaseAndroid(app, assembleType, { target, env, regenerate });
|
|
3378
3167
|
}
|
|
3379
3168
|
async configureApp(app) {
|
|
3380
3169
|
await this.applicationRunner.configureApp(app);
|
|
@@ -3382,8 +3171,21 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3382
3171
|
async releaseSource(app, options) {
|
|
3383
3172
|
await this.applicationRunner.releaseSource(app, options);
|
|
3384
3173
|
}
|
|
3385
|
-
async codepush(app
|
|
3386
|
-
await this.applicationRunner.codepush(app
|
|
3174
|
+
async codepush(app) {
|
|
3175
|
+
await this.applicationRunner.codepush(app);
|
|
3176
|
+
}
|
|
3177
|
+
async transferDatabase(app, direction, dir) {
|
|
3178
|
+
await this.applicationRunner.transferDatabase(app, direction, dir);
|
|
3179
|
+
}
|
|
3180
|
+
async dbupDeclared(workspace, mode) {
|
|
3181
|
+
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())]);
|
|
3182
|
+
const needing = declared.filter((each) => each !== "single");
|
|
3183
|
+
if (!needing.length) {
|
|
3184
|
+
workspace.log("No app here declares a database mode that runs on local services; single needs none.");
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3187
|
+
for (const each of needing)
|
|
3188
|
+
await this.dbup(workspace, each);
|
|
3387
3189
|
}
|
|
3388
3190
|
async dbup(workspace, mode = "multiple") {
|
|
3389
3191
|
const spinner = workspace.spinning(`Starting local database (${mode})...`);
|
|
@@ -3401,34 +3203,36 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
|
|
|
3401
3203
|
}
|
|
3402
3204
|
spinner.succeed("Local database (/local/docker-compose.yaml) is down");
|
|
3403
3205
|
}
|
|
3404
|
-
#
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3206
|
+
async#shareOnInterrupt(apps) {
|
|
3207
|
+
const shares = [];
|
|
3208
|
+
const urls = new Map;
|
|
3209
|
+
this.#interrupt.add(async () => {
|
|
3210
|
+
await Promise.all(shares.map(async (share) => await share.close()));
|
|
3211
|
+
}, "Abandoning the tunnel release; the shares expire on their own.");
|
|
3212
|
+
await Promise.all(apps.map(async (app) => {
|
|
3213
|
+
const { TunnelShare } = await import("./TunnelShare-scf4e4pd.js");
|
|
3214
|
+
const share = await TunnelShare.open(app, { workspace: app.workspace });
|
|
3215
|
+
shares.push(share);
|
|
3216
|
+
urls.set(app.name, share.url);
|
|
3217
|
+
Logger6.rawLog(`${app.name} is shared at ${share.url}`);
|
|
3218
|
+
})).catch((error) => {
|
|
3219
|
+
Logger6.rawLog(`Could not open the tunnel: ${error instanceof Error ? error.message : String(error)}`, undefined, "error");
|
|
3413
3220
|
});
|
|
3221
|
+
return urls;
|
|
3222
|
+
}
|
|
3223
|
+
#stopDatabaseOnInterrupt(workspace) {
|
|
3224
|
+
this.#interrupt.add(async () => {
|
|
3225
|
+
await this.#stopDatabase(workspace);
|
|
3226
|
+
}, "Abandoning the local database teardown; containers are left running.");
|
|
3414
3227
|
}
|
|
3415
3228
|
async#stopDatabase(workspace) {
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
timer = setTimeout(() => resolve("timeout"), ApplicationScript.dbShutdownTimeoutMs);
|
|
3229
|
+
const stopped = this.dbdown(workspace).catch(() => {
|
|
3230
|
+
return;
|
|
3419
3231
|
});
|
|
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
|
-
}
|
|
3232
|
+
if (!await DevSupervisor.timesOut(stopped, ApplicationScript.dbShutdownTimeoutMs))
|
|
3233
|
+
return;
|
|
3234
|
+
Logger6.rawLog(`Local database did not stop within ${ApplicationScript.dbShutdownTimeoutMs}ms; run \`akan dbdown\` once Docker responds.`, undefined, "error");
|
|
3431
3235
|
}
|
|
3432
3236
|
}
|
|
3433
3237
|
|
|
3434
|
-
export {
|
|
3238
|
+
export { ApplicationScript };
|