@akanjs/cli 3.1.0-rc.20 → 3.1.0-rc.22
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-n3tzjm91.js → CodeAgent-fee0pnwp.js} +2 -2
- package/{CodeTui-fnm9ft4d.js → CodeTui-xh71b2k8.js} +1 -1
- package/TunnelShare-s7bpe55e.js +14 -0
- package/{agent.command-3qxf4xcj.js → agent.command-tge9rvmt.js} +5 -5
- package/akanCode.js +2 -2
- package/{application.command-zfja7x4r.js → application.command-j5b8yrcf.js} +35 -9
- package/{applicationBuildRunner-vh4j6vdv.js → applicationBuildRunner-s41mgz21.js} +21 -7
- package/buildBatch.proc.js +9 -9
- package/{cloud.command-fcc8e8tp.js → cloud.command-q9srng6e.js} +13 -13
- package/cloud.script-vfsd4gxs.js +24 -0
- package/{code.command-130bdmhe.js → code.command-n6r6zt97.js} +6 -6
- package/commandManifest.json +1 -1
- package/{context.command-0qx447mb.js → context.command-1je84w1b.js} +16 -16
- package/context.runner-xg0ngz93.js +25 -0
- package/{guideline.command-cv5bmrx2.js → guideline.command-0e0sx2vg.js} +2 -2
- package/guidelines/conventions/conventions.instruction.md +42 -13
- package/guidelines/mcpRule/mcpRule.instruction.md +88 -47
- package/guidelines/pageRule/pageRule.instruction.md +53 -8
- package/guidelines/runtimeRule/runtimeRule.instruction.md +8 -1
- package/guidelines/ssrRule/ssrRule.instruction.md +8 -5
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +1 -1
- package/incrementalBuilder.proc.js +3 -3
- package/{index-fmzcs8v6.js → index-1bhv9dh4.js} +4 -4
- package/{index-qtfmtcrw.js → index-1xqq3va9.js} +71 -18
- package/{index-86jt2a0e.js → index-3xhak9e0.js} +1 -1
- package/{index-tamvyvj3.js → index-4fe4qqgn.js} +3 -3
- package/{index-g1mbppa9.js → index-551gh8h2.js} +5 -5
- package/{index-trpasv9q.js → index-5m0s0gdv.js} +1 -1
- package/index-6mprvdt8.js +566 -0
- package/{index-192ha4xh.js → index-6nqnqzv1.js} +1 -1
- package/{index-dye8pnxx.js → index-73esn3y2.js} +1 -1
- package/{index-4mhy6qme.js → index-7xy30jev.js} +4 -4
- package/{index-1xj2t7cb.js → index-85h3dhe0.js} +7 -7
- package/{index-axs61rna.js → index-8abyrfw8.js} +5 -4
- package/{index-1p98exm2.js → index-94jq9cbz.js} +2 -2
- package/{index-cpb3nvma.js → index-9h0z0tjr.js} +2 -2
- package/{index-hsadfb6c.js → index-c9rfak6r.js} +7 -7
- package/{index-w44cy4vs.js → index-cgyqx6q1.js} +2 -2
- package/{index-d72hfmdm.js → index-cq5x7a9k.js} +8 -8
- package/{index-j237xtew.js → index-deckcwbg.js} +2 -2
- package/{index-vk0pa0mn.js → index-ex35dg39.js} +2 -1
- package/{index-35hc95h5.js → index-f9118j70.js} +1 -1
- package/{index-jw1zm273.js → index-g5px5pxd.js} +53 -9
- package/{index-va06nr10.js → index-gxqvev8w.js} +52 -12
- package/{index-fyq1z6kt.js → index-hagw9c94.js} +3 -3
- package/{index-pydbnfez.js → index-p44jnjfr.js} +2 -2
- package/{index-awbdfcf2.js → index-rp9anyeb.js} +4 -4
- package/{index-j4mkhz93.js → index-t8npm331.js} +3 -3
- package/{index-5edzv8fn.js → index-tbmtmkhx.js} +2 -2
- package/{index-1djcjcre.js → index-vyh8kemm.js} +3 -3
- package/{index-qwa5k1j8.js → index-x7bny3wv.js} +1 -1
- package/index.js +22 -22
- package/{library.command-erp2f56n.js → library.command-ys9s0mtc.js} +3 -3
- package/{localRegistry.command-hj3p16p5.js → localRegistry.command-kmp3sd47.js} +12 -12
- package/{module.command-gs2r615e.js → module.command-5sx9453y.js} +5 -5
- package/{package.command-j10r76ax.js → package.command-s0d3417k.js} +3 -3
- package/package.json +2 -2
- package/{page.command-4h4a8pnh.js → page.command-zsa03pmc.js} +3 -3
- package/{pipeline.command-a2nrk26m.js → pipeline.command-j8h6qsha.js} +2 -2
- package/{primitive.command-w6t0asqz.js → primitive.command-n2978nmw.js} +6 -6
- package/{projectTypecheck-f534enqj.js → projectTypecheck-qg2jf80j.js} +1 -1
- package/{quality.command-77x7vbbx.js → quality.command-7kgz46st.js} +2 -2
- package/{repair.command-y6sstev7.js → repair.command-2sszcckw.js} +5 -5
- package/{routeSourceValidator-7ycnfz0e.js → routeSourceValidator-6jd0q6r6.js} +47 -14
- package/{scalar.command-20b8cy0k.js → scalar.command-230jrhzf.js} +4 -4
- package/{subspace.command-baz73jpk.js → subspace.command-y5v4ypd3.js} +8 -8
- package/templates/migration/__migrationId__.ts +23 -0
- package/templates/server.ts +8 -1
- package/{tunnel.command-2kw8vt9g.js → tunnel.command-ja29zghd.js} +7 -7
- package/{workflow.command-2djwmpqf.js → workflow.command-0eg8czt5.js} +10 -10
- package/{workspace.command-jqs7vjhe.js → workspace.command-zcym1dk5.js} +24 -24
- package/TunnelShare-661cxz5e.js +0 -14
- package/cloud.script-1xt3tnts.js +0 -24
- package/context.runner-1vsjztjg.js +0 -25
- package/index-665zp9rs.js +0 -271
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
runner,
|
|
4
4
|
script
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-g5px5pxd.js";
|
|
6
6
|
import {
|
|
7
7
|
AGENT_BLOCK_START,
|
|
8
8
|
AGENT_BLOCK_END,
|
|
@@ -13,14 +13,14 @@ import {
|
|
|
13
13
|
CLAUDE_COMMENT_RULE,
|
|
14
14
|
AppExecutor,
|
|
15
15
|
LibExecutor
|
|
16
|
-
} from "./index-
|
|
16
|
+
} from "./index-gxqvev8w.js";
|
|
17
17
|
import {
|
|
18
18
|
collectRecipeSources2,
|
|
19
19
|
scanRecipes2
|
|
20
20
|
} from "./index-hj6w4ka1.js";
|
|
21
21
|
import {
|
|
22
22
|
AkanContextAnalyzer
|
|
23
|
-
} from "./index-
|
|
23
|
+
} from "./index-tbmtmkhx.js";
|
|
24
24
|
import {
|
|
25
25
|
Prompter2
|
|
26
26
|
} from "./index-zzvvc845.js";
|
|
@@ -4,17 +4,17 @@ import {
|
|
|
4
4
|
ServerEnvProfile,
|
|
5
5
|
Executor,
|
|
6
6
|
WorkspaceExecutor
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-gxqvev8w.js";
|
|
8
8
|
import {
|
|
9
9
|
resolveAppId,
|
|
10
10
|
targetHtmlFilename
|
|
11
11
|
} from "./index-m9jj544v.js";
|
|
12
|
-
import {
|
|
13
|
-
ExitCodeError
|
|
14
|
-
} from "./index-jw1zm273.js";
|
|
15
12
|
import {
|
|
16
13
|
CsrArtifactBuilder
|
|
17
|
-
} from "./index-
|
|
14
|
+
} from "./index-6mprvdt8.js";
|
|
15
|
+
import {
|
|
16
|
+
ExitCodeError
|
|
17
|
+
} from "./index-g5px5pxd.js";
|
|
18
18
|
|
|
19
19
|
// pkgs/@akanjs/devkit/mobile/desktopBin.ts
|
|
20
20
|
import { chmod, mkdir, rename, rm, writeFile } from "fs/promises";
|
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
resolveSsrPageEntriesForApp
|
|
4
|
+
} from "./index-gxqvev8w.js";
|
|
5
|
+
import {
|
|
6
|
+
bundleDefine,
|
|
7
|
+
CsrEntryFiles,
|
|
8
|
+
PagesEntrySourceGenerator,
|
|
9
|
+
PagesBundleBuilder
|
|
10
|
+
} from "./index-6nqnqzv1.js";
|
|
11
|
+
|
|
12
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
|
|
13
|
+
import { mkdir as mkdir2, rm as rm2, unlink as unlink2 } from "fs/promises";
|
|
14
|
+
import path4 from "path";
|
|
15
|
+
|
|
16
|
+
// pkgs/@akanjs/devkit/transforms/browserDirnamePlugin.ts
|
|
17
|
+
import path from "path";
|
|
18
|
+
var vendorScript = /[\\/]node_modules[\\/].+\.[cm]?js$/;
|
|
19
|
+
var createBrowserDirnamePlugin = () => ({
|
|
20
|
+
name: "akan-browser-dirname",
|
|
21
|
+
setup(build) {
|
|
22
|
+
build.onLoad({ filter: vendorScript }, async (args) => {
|
|
23
|
+
const source = await Bun.file(args.path).text();
|
|
24
|
+
if (!source.includes("__dirname") && !source.includes("__filename"))
|
|
25
|
+
return;
|
|
26
|
+
const posixPath = args.path.replaceAll("\\", "/");
|
|
27
|
+
const filename = posixPath.slice(posixPath.indexOf("/node_modules/"));
|
|
28
|
+
const define = { __dirname: JSON.stringify(path.posix.dirname(filename)), __filename: JSON.stringify(filename) };
|
|
29
|
+
return {
|
|
30
|
+
loader: "js",
|
|
31
|
+
contents: new Bun.Transpiler({ loader: "js", target: "browser", define }).transformSync(source)
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// pkgs/@akanjs/devkit/frontendBuild/mcpAppViewBuilder.ts
|
|
38
|
+
import { mkdir, readdir, rename, rm, unlink } from "fs/promises";
|
|
39
|
+
import path3 from "path";
|
|
40
|
+
import { parseRouteModuleKey } from "akanjs/common";
|
|
41
|
+
|
|
42
|
+
// pkgs/@akanjs/devkit/transforms/mcpAppPageTrimmer.ts
|
|
43
|
+
import { realpathSync } from "fs";
|
|
44
|
+
import path2 from "path";
|
|
45
|
+
import ts2 from "typescript";
|
|
46
|
+
|
|
47
|
+
// pkgs/@akanjs/devkit/transforms/pureLazyMarker.ts
|
|
48
|
+
import ts from "typescript";
|
|
49
|
+
|
|
50
|
+
class PureLazyMarker {
|
|
51
|
+
static plugin() {
|
|
52
|
+
return {
|
|
53
|
+
name: "akan-pure-lazy",
|
|
54
|
+
setup(build) {
|
|
55
|
+
build.onLoad({ filter: /\.tsx?$/ }, async (args) => {
|
|
56
|
+
const file = Bun.file(args.path);
|
|
57
|
+
if (!await file.exists())
|
|
58
|
+
return;
|
|
59
|
+
const source = await file.text();
|
|
60
|
+
const marked = PureLazyMarker.mark(source, args.path);
|
|
61
|
+
return marked === source ? undefined : { contents: marked, loader: args.path.endsWith(".tsx") ? "tsx" : "ts" };
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
static mark(source, filePath) {
|
|
67
|
+
if (!source.includes("lazy("))
|
|
68
|
+
return source;
|
|
69
|
+
const kind = filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
70
|
+
const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, kind);
|
|
71
|
+
const starts = [];
|
|
72
|
+
const visit = (node) => {
|
|
73
|
+
const call = ts.isVariableDeclaration(node) ? node.initializer : undefined;
|
|
74
|
+
if (call && ts.isCallExpression(call) && ts.isIdentifier(call.expression) && call.expression.text === "lazy")
|
|
75
|
+
starts.push(call.getStart(sourceFile));
|
|
76
|
+
ts.forEachChild(node, visit);
|
|
77
|
+
};
|
|
78
|
+
visit(sourceFile);
|
|
79
|
+
return starts.sort((a, b) => b - a).reduce((marked, start) => `${marked.slice(0, start)}/* @__PURE__ */ ${marked.slice(start)}`, source);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// pkgs/@akanjs/devkit/transforms/mcpAppPageTrimmer.ts
|
|
84
|
+
class McpAppPageTrimmer {
|
|
85
|
+
static #unused = new Set(["render", "head", "loading", "guard"]);
|
|
86
|
+
static plugin(pagePaths) {
|
|
87
|
+
return {
|
|
88
|
+
name: "akan-mcp-app-page",
|
|
89
|
+
setup(build) {
|
|
90
|
+
if (!pagePaths.length)
|
|
91
|
+
return;
|
|
92
|
+
const loadedPaths = new Set(pagePaths.flatMap((pagePath) => [pagePath, McpAppPageTrimmer.#realPathOf(pagePath)]));
|
|
93
|
+
const filter = new RegExp(`^(?:${[...loadedPaths].map((pagePath) => RegExp.escape(pagePath)).join("|")})$`);
|
|
94
|
+
build.onLoad({ filter }, async (args) => ({
|
|
95
|
+
contents: PureLazyMarker.mark(McpAppPageTrimmer.trim(await Bun.file(args.path).text(), args.path), args.path),
|
|
96
|
+
loader: path2.extname(args.path).slice(1)
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
static trim(source, filePath) {
|
|
102
|
+
const sourceFile = ts2.createSourceFile(filePath, source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
103
|
+
const spans = McpAppPageTrimmer.#stagesOf(sourceFile).flatMap((stage) => {
|
|
104
|
+
const [first] = stage.arguments;
|
|
105
|
+
if (!first || !McpAppPageTrimmer.#unused.has(stage.expression.name.text))
|
|
106
|
+
return [];
|
|
107
|
+
return [{ start: first.getStart(sourceFile), end: first.getEnd() }];
|
|
108
|
+
}).sort((a, b) => b.start - a.start);
|
|
109
|
+
let trimmed = source;
|
|
110
|
+
for (const { start, end } of spans)
|
|
111
|
+
trimmed = `${trimmed.slice(0, start)}() => null${trimmed.slice(end)}`;
|
|
112
|
+
return trimmed;
|
|
113
|
+
}
|
|
114
|
+
static #realPathOf(filePath) {
|
|
115
|
+
try {
|
|
116
|
+
return realpathSync(filePath);
|
|
117
|
+
} catch {
|
|
118
|
+
return filePath;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
static #stagesOf(sourceFile) {
|
|
122
|
+
const stages = [];
|
|
123
|
+
for (const statement of sourceFile.statements) {
|
|
124
|
+
if (!ts2.isExportAssignment(statement) || statement.isExportEquals)
|
|
125
|
+
continue;
|
|
126
|
+
let node = statement.expression;
|
|
127
|
+
while (ts2.isParenthesizedExpression(node) || ts2.isAsExpression(node) || ts2.isSatisfiesExpression(node))
|
|
128
|
+
node = node.expression;
|
|
129
|
+
while (ts2.isCallExpression(node) && ts2.isPropertyAccessExpression(node.expression)) {
|
|
130
|
+
stages.push(node);
|
|
131
|
+
node = node.expression.expression;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return stages;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// pkgs/@akanjs/devkit/frontendBuild/mcpAppViewBuilder.ts
|
|
139
|
+
class McpAppViewBuilder {
|
|
140
|
+
static dirName = "mcp-apps";
|
|
141
|
+
static #appName = /^[A-Za-z0-9_-]{1,64}$/;
|
|
142
|
+
#app;
|
|
143
|
+
#command;
|
|
144
|
+
constructor(app, command) {
|
|
145
|
+
this.#app = app;
|
|
146
|
+
this.#command = command;
|
|
147
|
+
}
|
|
148
|
+
get outputDir() {
|
|
149
|
+
return this.#command === "build" ? path3.join(this.#app.dist.cwdPath, McpAppViewBuilder.dirName) : path3.join(this.#app.cwdPath, ".akan", "artifact", McpAppViewBuilder.dirName);
|
|
150
|
+
}
|
|
151
|
+
async build(pageEntries, { basePaths, cssAssets, options }) {
|
|
152
|
+
const views = await McpAppViewBuilder.discover(pageEntries, basePaths);
|
|
153
|
+
if (!views.length) {
|
|
154
|
+
await rm(this.outputDir, { recursive: true, force: true });
|
|
155
|
+
return views;
|
|
156
|
+
}
|
|
157
|
+
const stagingDir = `${this.outputDir}.next`;
|
|
158
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
159
|
+
const generatedDir = path3.join(this.#app.cwdPath, ".akan", "generated", McpAppViewBuilder.dirName);
|
|
160
|
+
await rm(generatedDir, { recursive: true, force: true });
|
|
161
|
+
await mkdir(generatedDir, { recursive: true });
|
|
162
|
+
const files = Object.fromEntries(views.flatMap((view) => [
|
|
163
|
+
[path3.join(generatedDir, `${view.name}.html`), McpAppViewBuilder.htmlOf(view)],
|
|
164
|
+
[path3.join(generatedDir, `${view.name}.view.tsx`), McpAppViewBuilder.entryOf(view, generatedDir)]
|
|
165
|
+
]));
|
|
166
|
+
const pagePaths = [...new Set(views.map((view) => view.page.moduleAbsPath))];
|
|
167
|
+
const result = await Bun.build({
|
|
168
|
+
...options,
|
|
169
|
+
plugins: [...options.plugins, McpAppPageTrimmer.plugin(pagePaths), PureLazyMarker.plugin()],
|
|
170
|
+
entrypoints: views.map((view) => path3.join(generatedDir, `${view.name}.html`)),
|
|
171
|
+
files,
|
|
172
|
+
root: generatedDir,
|
|
173
|
+
outdir: stagingDir
|
|
174
|
+
});
|
|
175
|
+
if (!result.success) {
|
|
176
|
+
const logs = result.logs.map((log) => log.message).join(`
|
|
177
|
+
`);
|
|
178
|
+
throw new Error(`[mcp-app-build] failed${logs ? `
|
|
179
|
+
${logs}` : ""}`);
|
|
180
|
+
}
|
|
181
|
+
for (const view of views)
|
|
182
|
+
await this.#inline(view, stagingDir, cssAssets[view.basePath]);
|
|
183
|
+
await this.#publish(views, stagingDir);
|
|
184
|
+
return views;
|
|
185
|
+
}
|
|
186
|
+
async#publish(views, stagingDir) {
|
|
187
|
+
const names = new Set(views.map((view) => `${view.name}.html`));
|
|
188
|
+
await mkdir(this.outputDir, { recursive: true });
|
|
189
|
+
for (const name of names)
|
|
190
|
+
await rename(path3.join(stagingDir, name), path3.join(this.outputDir, name));
|
|
191
|
+
for (const name of await readdir(this.outputDir))
|
|
192
|
+
if (!names.has(name))
|
|
193
|
+
await rm(path3.join(this.outputDir, name), { recursive: true, force: true });
|
|
194
|
+
const strays = await readdir(stagingDir);
|
|
195
|
+
if (strays.length)
|
|
196
|
+
this.#app.logger.warn(`[mcp-app-build] ${strays.join(", ")} reached no view; a view loads nothing beside its own file`);
|
|
197
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
198
|
+
}
|
|
199
|
+
static async discover(pageEntries, basePaths) {
|
|
200
|
+
const { RouteSourceValidator } = await import("./routeSourceValidator-6jd0q6r6.js");
|
|
201
|
+
const roots = pageEntries.filter((entry) => parseRouteModuleKey(entry.key).isInternalRootLayout);
|
|
202
|
+
const overrides = pageEntries.filter((entry) => parseRouteModuleKey(entry.key).kind === "overrides");
|
|
203
|
+
const layoutNodes = new Set(pageEntries.flatMap((entry) => {
|
|
204
|
+
const parsed = parseRouteModuleKey(entry.key);
|
|
205
|
+
return parsed.kind === "layout" && !parsed.isInternalRootLayout ? [parsed.routeSegments.join("/")] : [];
|
|
206
|
+
}));
|
|
207
|
+
const owners = new Map;
|
|
208
|
+
const views = [];
|
|
209
|
+
for (const page of pageEntries) {
|
|
210
|
+
if (parseRouteModuleKey(page.key).kind !== "page")
|
|
211
|
+
continue;
|
|
212
|
+
const source = await Bun.file(page.moduleAbsPath).text();
|
|
213
|
+
const apps = RouteSourceValidator.validateRouteSourceExports(source, page.moduleAbsPath, "page").chain?.apps ?? [];
|
|
214
|
+
for (const name of apps) {
|
|
215
|
+
if (!McpAppViewBuilder.#appName.test(name))
|
|
216
|
+
throw new Error(`[route-convention] app name "${name}" in ${page.key} must match ${McpAppViewBuilder.#appName}`);
|
|
217
|
+
const owner = owners.get(name.toLowerCase());
|
|
218
|
+
if (owner?.name === name)
|
|
219
|
+
throw new Error(`[route-convention] app "${name}" is declared by both ${owner.key} and ${page.key}`);
|
|
220
|
+
if (owner)
|
|
221
|
+
throw new Error(`[route-convention] apps "${owner.name}" (${owner.key}) and "${name}" (${page.key}) differ only in case; their views would share one file`);
|
|
222
|
+
owners.set(name.toLowerCase(), { name, key: page.key });
|
|
223
|
+
const root = await McpAppViewBuilder.#rootOf(page.key, roots);
|
|
224
|
+
views.push({
|
|
225
|
+
name,
|
|
226
|
+
page,
|
|
227
|
+
root,
|
|
228
|
+
...McpAppViewBuilder.#overridesOf(page.key, root, overrides, layoutNodes),
|
|
229
|
+
basePath: CsrEntryFiles.basePathOfPageKey(page.key, basePaths) ?? ""
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return views;
|
|
234
|
+
}
|
|
235
|
+
static htmlOf(view) {
|
|
236
|
+
return `<!doctype html>
|
|
237
|
+
<html lang="en">
|
|
238
|
+
<head>
|
|
239
|
+
<meta charset="utf-8" />
|
|
240
|
+
<title>${view.name}</title>
|
|
241
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
242
|
+
</head>
|
|
243
|
+
<body>
|
|
244
|
+
<div id="root"></div>
|
|
245
|
+
<script type="module" src="./${view.name}.view.tsx"></script>
|
|
246
|
+
</body>
|
|
247
|
+
</html>
|
|
248
|
+
`;
|
|
249
|
+
}
|
|
250
|
+
static entryOf(view, fromDir) {
|
|
251
|
+
const specifier = (absPath) => {
|
|
252
|
+
const relative = path3.relative(fromDir, absPath).split(path3.sep).join("/");
|
|
253
|
+
return JSON.stringify(relative.startsWith(".") ? relative : `./${relative}`);
|
|
254
|
+
};
|
|
255
|
+
const importsOf = (entries, name) => entries.map((entry, idx) => `import ${name}${idx} from ${specifier(entry.moduleAbsPath)};
|
|
256
|
+
`).join("");
|
|
257
|
+
const namesOf = (entries, name) => entries.map((_, idx) => `${name}${idx}`).join(", ");
|
|
258
|
+
return `import { McpAppBoot } from "akanjs/webkit";
|
|
259
|
+
import * as page from ${specifier(view.page.moduleAbsPath)};
|
|
260
|
+
${view.root ? `import * as root from ${specifier(view.root.moduleAbsPath)};` : "const root = {};"}
|
|
261
|
+
${importsOf(view.rootOverrides, "rootOverride")}${importsOf(view.overrides, "override")}void McpAppBoot.run({
|
|
262
|
+
app: ${JSON.stringify(view.name)},
|
|
263
|
+
page,
|
|
264
|
+
root,
|
|
265
|
+
rootOverrides: [${namesOf(view.rootOverrides, "rootOverride")}],
|
|
266
|
+
overrides: [${namesOf(view.overrides, "override")}],
|
|
267
|
+
basePath: ${JSON.stringify(view.basePath)},
|
|
268
|
+
});
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
static #overridesOf(pageKey, root, overrides, layoutNodes) {
|
|
272
|
+
const pageSegments = parseRouteModuleKey(pageKey).routeSegments;
|
|
273
|
+
const rootDepth = root ? parseRouteModuleKey(root.key).routeSegments.length : 0;
|
|
274
|
+
const over = overrides.map((entry) => ({ entry, segments: parseRouteModuleKey(entry.key).routeSegments })).filter(({ segments }) => segments.every((segment, idx) => pageSegments[idx] === segment)).sort((a, b) => a.segments.length - b.segments.length);
|
|
275
|
+
const onRootNode = (segments) => segments.length === rootDepth || segments.length < rootDepth && !layoutNodes.has(segments.join("/"));
|
|
276
|
+
return {
|
|
277
|
+
rootOverrides: over.filter(({ segments }) => onRootNode(segments)).map(({ entry }) => entry),
|
|
278
|
+
overrides: over.filter(({ segments }) => !onRootNode(segments)).map(({ entry }) => entry)
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
static async#rootOf(pageKey, roots) {
|
|
282
|
+
const dirOf = (key) => key.slice(0, key.lastIndexOf("/") + 1);
|
|
283
|
+
const pageDir = dirOf(pageKey);
|
|
284
|
+
const over = roots.filter((root) => pageDir.startsWith(dirOf(root.key))).sort((a, b) => dirOf(a.key).length - dirOf(b.key).length);
|
|
285
|
+
for (const root of over)
|
|
286
|
+
if ((await Bun.file(root.moduleAbsPath).text()).includes("export function McpAppRoot"))
|
|
287
|
+
return root;
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
async#inline(view, dir, cssAsset) {
|
|
291
|
+
const htmlPath = path3.join(dir, `${view.name}.html`);
|
|
292
|
+
let html = await Bun.file(htmlPath).text();
|
|
293
|
+
if (cssAsset) {
|
|
294
|
+
const artifactDir = path3.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan", "artifact");
|
|
295
|
+
const css = await Bun.file(path3.join(artifactDir, cssAsset.cssRelPath)).text();
|
|
296
|
+
html = CsrArtifactBuilder.injectBeforeHeadEnd(html, CsrArtifactBuilder.createInlineStyle(css));
|
|
297
|
+
} else
|
|
298
|
+
this.#app.logger.warn(`[mcp-app-build] no compiled stylesheet for ${view.name}; its view ships without CSS`);
|
|
299
|
+
const scripts = [];
|
|
300
|
+
html = await CsrArtifactBuilder.replaceModuleScriptSrc(html, async (src) => {
|
|
301
|
+
const jsPath = CsrArtifactBuilder.resolveHtmlAssetPath(htmlPath, src);
|
|
302
|
+
scripts.push(jsPath);
|
|
303
|
+
return await Bun.file(jsPath).text();
|
|
304
|
+
});
|
|
305
|
+
await Bun.write(htmlPath, html);
|
|
306
|
+
for (const script of scripts)
|
|
307
|
+
await unlink(script).catch(() => {
|
|
308
|
+
return;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
|
|
314
|
+
class CsrArtifactBuilder {
|
|
315
|
+
#app;
|
|
316
|
+
#command;
|
|
317
|
+
#lang;
|
|
318
|
+
#cssAssets;
|
|
319
|
+
constructor(app, command = "start", lang = "en", { cssAssets = null } = {}) {
|
|
320
|
+
this.#app = app;
|
|
321
|
+
this.#command = command;
|
|
322
|
+
this.#lang = lang;
|
|
323
|
+
this.#cssAssets = cssAssets;
|
|
324
|
+
}
|
|
325
|
+
async build() {
|
|
326
|
+
const pageKeys = await this.#app.getPageKeys();
|
|
327
|
+
if (pageKeys.length === 0) {
|
|
328
|
+
this.#app.log(`[cli] no route files under ${this.#app.cwdPath}/page \u2014 skipping CSR build`);
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
|
|
332
|
+
const akanConfig = await this.#app.getConfig();
|
|
333
|
+
const cssAssets = await this.#loadCssAssets();
|
|
334
|
+
const basePaths = [...akanConfig.basePaths];
|
|
335
|
+
const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
|
|
336
|
+
await rm2(this.#outputDir, { recursive: true, force: true });
|
|
337
|
+
await mkdir2(this.#generatedDir, { recursive: true });
|
|
338
|
+
const generatedFiles = Object.fromEntries((await Promise.all(htmlBasePaths.map(async (basePath) => [
|
|
339
|
+
this.#createHtmlFile(basePath),
|
|
340
|
+
await this.#createEntryFile(basePath, CsrArtifactBuilder.pageEntriesForBasePath(pageEntries, basePath, basePaths))
|
|
341
|
+
]))).flat());
|
|
342
|
+
const options = CsrArtifactBuilder.bundleOptions(this.#app, this.#command, akanConfig.optimizeImports);
|
|
343
|
+
const result = await Bun.build({
|
|
344
|
+
...options,
|
|
345
|
+
entrypoints: htmlBasePaths.map((basePath) => this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath))),
|
|
346
|
+
files: generatedFiles,
|
|
347
|
+
root: this.#generatedDir,
|
|
348
|
+
outdir: this.#outputDir
|
|
349
|
+
});
|
|
350
|
+
if (!result.success) {
|
|
351
|
+
const logs = result.logs.map((log) => log.message).join(`
|
|
352
|
+
`);
|
|
353
|
+
throw new Error(`[csr-build] failed${logs ? `
|
|
354
|
+
${logs}` : ""}`);
|
|
355
|
+
}
|
|
356
|
+
await this.#inlineCsrArtifacts(cssAssets);
|
|
357
|
+
await this.#buildMcpAppViews(pageEntries, { basePaths, cssAssets, options });
|
|
358
|
+
if (this.#command === "build")
|
|
359
|
+
await CsrArtifactBuilder.writePublicEnv(this.#app, options.define);
|
|
360
|
+
this.#app.verbose(`[csr-build] output -> ${this.#outputDir}`);
|
|
361
|
+
return { outputDir: this.#outputDir };
|
|
362
|
+
}
|
|
363
|
+
async buildMcpAppViews() {
|
|
364
|
+
const pageKeys = await this.#app.getPageKeys();
|
|
365
|
+
if (pageKeys.length === 0)
|
|
366
|
+
return [];
|
|
367
|
+
const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
|
|
368
|
+
const akanConfig = await this.#app.getConfig();
|
|
369
|
+
return await this.#buildMcpAppViews(pageEntries, {
|
|
370
|
+
basePaths: [...akanConfig.basePaths],
|
|
371
|
+
cssAssets: await this.#loadCssAssets(),
|
|
372
|
+
options: CsrArtifactBuilder.bundleOptions(this.#app, this.#command, akanConfig.optimizeImports)
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
async#buildMcpAppViews(pageEntries, option) {
|
|
376
|
+
const views = await new McpAppViewBuilder(this.#app, this.#command).build(pageEntries, option);
|
|
377
|
+
if (views.length)
|
|
378
|
+
this.#app.verbose(`[csr-build] ${views.length} MCP App view(s): ${views.map((view) => view.name).join(", ")}`);
|
|
379
|
+
return views;
|
|
380
|
+
}
|
|
381
|
+
static bundleOptions(app, command, optimizeImports = []) {
|
|
382
|
+
return {
|
|
383
|
+
target: "browser",
|
|
384
|
+
splitting: false,
|
|
385
|
+
minify: true,
|
|
386
|
+
define: bundleDefine(app, command, "csr"),
|
|
387
|
+
optimizeImports,
|
|
388
|
+
plugins: [PagesBundleBuilder.createCssStubPlugin(), createBrowserDirnamePlugin()]
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
static publicEnvPath(app) {
|
|
392
|
+
return path4.join(app.dist.cwdPath, ".akan", "artifact", "csr-public-env.json");
|
|
393
|
+
}
|
|
394
|
+
static async writePublicEnv(app, define) {
|
|
395
|
+
const prefix = "process.env.";
|
|
396
|
+
const publicEnv = Object.fromEntries(Object.entries(define).filter(([key]) => key.startsWith(`${prefix}AKAN_PUBLIC_`)).map(([key, value]) => [key.slice(prefix.length), JSON.parse(value)]));
|
|
397
|
+
await Bun.write(CsrArtifactBuilder.publicEnvPath(app), `${JSON.stringify(publicEnv, null, 2)}
|
|
398
|
+
`);
|
|
399
|
+
}
|
|
400
|
+
static pageEntriesForBasePath(pageEntries, basePath, basePaths) {
|
|
401
|
+
return CsrEntryFiles.pageEntriesForBasePath(pageEntries, basePath, basePaths);
|
|
402
|
+
}
|
|
403
|
+
static htmlFilename(basePath) {
|
|
404
|
+
return `${basePath || "index"}.html`;
|
|
405
|
+
}
|
|
406
|
+
static entryFilename(basePath) {
|
|
407
|
+
return CsrEntryFiles.entryFilename(basePath);
|
|
408
|
+
}
|
|
409
|
+
static basePathOfHtml(htmlPath) {
|
|
410
|
+
const name = path4.basename(htmlPath, ".html");
|
|
411
|
+
return name === "index" ? "" : name;
|
|
412
|
+
}
|
|
413
|
+
get #outputDir() {
|
|
414
|
+
return path4.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
|
|
415
|
+
}
|
|
416
|
+
get #generatedDir() {
|
|
417
|
+
return path4.join(this.#app.cwdPath, ".akan/generated/csr");
|
|
418
|
+
}
|
|
419
|
+
get #artifactDir() {
|
|
420
|
+
return path4.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
|
|
421
|
+
}
|
|
422
|
+
#generatedPath(filename) {
|
|
423
|
+
return path4.join(this.#generatedDir, filename);
|
|
424
|
+
}
|
|
425
|
+
async#createEntryFile(basePath, pageEntries) {
|
|
426
|
+
return [
|
|
427
|
+
this.#generatedPath(CsrArtifactBuilder.entryFilename(basePath)),
|
|
428
|
+
`import { bootCsr } from "akanjs/webkit";
|
|
429
|
+
${await PagesEntrySourceGenerator.generateStatic(pageEntries)}
|
|
430
|
+
void bootCsr(pages);
|
|
431
|
+
`
|
|
432
|
+
];
|
|
433
|
+
}
|
|
434
|
+
#createHtmlFile(basePath) {
|
|
435
|
+
return [
|
|
436
|
+
this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath)),
|
|
437
|
+
`<!doctype html>
|
|
438
|
+
<html lang="${this.#lang}">
|
|
439
|
+
<head>
|
|
440
|
+
<meta charset="utf-8" />
|
|
441
|
+
<title>${this.#app.name}</title>
|
|
442
|
+
<base href="/" />
|
|
443
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
444
|
+
</head>
|
|
445
|
+
<body>
|
|
446
|
+
<div id="root"></div>
|
|
447
|
+
<script type="module" src="./${CsrArtifactBuilder.entryFilename(basePath)}"></script>
|
|
448
|
+
</body>
|
|
449
|
+
</html>
|
|
450
|
+
`
|
|
451
|
+
];
|
|
452
|
+
}
|
|
453
|
+
async#loadCssAssets() {
|
|
454
|
+
if (this.#cssAssets)
|
|
455
|
+
return this.#cssAssets;
|
|
456
|
+
const artifactFile = Bun.file(path4.join(this.#artifactDir, "base-artifact.json"));
|
|
457
|
+
if (!await artifactFile.exists())
|
|
458
|
+
return {};
|
|
459
|
+
const artifact = await artifactFile.json();
|
|
460
|
+
return artifact.cssAssets ?? {};
|
|
461
|
+
}
|
|
462
|
+
async#inlineCsrArtifacts(cssAssets) {
|
|
463
|
+
const jsFiles = new Set;
|
|
464
|
+
for (const htmlPath of await this.#listOutputFiles((filePath) => filePath.endsWith(".html"))) {
|
|
465
|
+
const htmlFile = Bun.file(htmlPath);
|
|
466
|
+
if (!await htmlFile.exists())
|
|
467
|
+
continue;
|
|
468
|
+
const basePath = CsrArtifactBuilder.basePathOfHtml(htmlPath);
|
|
469
|
+
const cssAsset = cssAssets[basePath];
|
|
470
|
+
if (!cssAsset) {
|
|
471
|
+
this.#app.logger.warn(`[csr-build] base-artifact.json has no compiled stylesheet for ${basePath || "root"}; ${path4.basename(htmlPath)} ships without CSS`);
|
|
472
|
+
}
|
|
473
|
+
const inlined = await this.#inlineHtmlAssets(await htmlFile.text(), htmlPath, cssAsset);
|
|
474
|
+
for (const filePath of inlined.jsFiles)
|
|
475
|
+
jsFiles.add(filePath);
|
|
476
|
+
await Bun.write(htmlPath, inlined.html);
|
|
477
|
+
}
|
|
478
|
+
for (const filePath of jsFiles)
|
|
479
|
+
await unlink2(filePath).catch(() => {
|
|
480
|
+
return;
|
|
481
|
+
});
|
|
482
|
+
const remainingAssets = await this.#listOutputFiles((filePath) => filePath.endsWith(".js") || filePath.endsWith(".css"));
|
|
483
|
+
if (remainingAssets.length > 0) {
|
|
484
|
+
throw new Error(`[csr-build] expected single-file HTML, but CSR assets remain:
|
|
485
|
+
${remainingAssets.join(`
|
|
486
|
+
`)}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async#inlineHtmlAssets(html, htmlPath, cssAsset) {
|
|
490
|
+
let next = html;
|
|
491
|
+
if (cssAsset) {
|
|
492
|
+
const css = await Bun.file(path4.join(this.#artifactDir, cssAsset.cssRelPath)).text();
|
|
493
|
+
next = CsrArtifactBuilder.injectBeforeHeadEnd(next, CsrArtifactBuilder.createInlineStyle(css));
|
|
494
|
+
}
|
|
495
|
+
const jsFiles = [];
|
|
496
|
+
next = await CsrArtifactBuilder.replaceModuleScriptSrc(next, async (src) => {
|
|
497
|
+
const jsPath = CsrArtifactBuilder.resolveHtmlAssetPath(htmlPath, src);
|
|
498
|
+
jsFiles.push(jsPath);
|
|
499
|
+
return await Bun.file(jsPath).text();
|
|
500
|
+
});
|
|
501
|
+
return { html: next, jsFiles };
|
|
502
|
+
}
|
|
503
|
+
async#listOutputFiles(predicate) {
|
|
504
|
+
const glob = new Bun.Glob("**/*");
|
|
505
|
+
const files = [];
|
|
506
|
+
for await (const filePath of glob.scan({ cwd: this.#outputDir, absolute: true })) {
|
|
507
|
+
if (predicate(filePath))
|
|
508
|
+
files.push(filePath);
|
|
509
|
+
}
|
|
510
|
+
return files.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
511
|
+
}
|
|
512
|
+
static injectBeforeHeadEnd(html, snippet) {
|
|
513
|
+
const scannable = CsrArtifactBuilder.blankEmbeddedContent(html);
|
|
514
|
+
const headEnd = scannable.search(/<\/head\s*>/i);
|
|
515
|
+
if (headEnd !== -1)
|
|
516
|
+
return `${html.slice(0, headEnd)}${snippet}
|
|
517
|
+
${html.slice(headEnd)}`;
|
|
518
|
+
const bodyStart = scannable.search(/<body(?:\s|>)/i);
|
|
519
|
+
if (bodyStart !== -1)
|
|
520
|
+
return `${html.slice(0, bodyStart)}${snippet}
|
|
521
|
+
${html.slice(bodyStart)}`;
|
|
522
|
+
return `${html}
|
|
523
|
+
${snippet}`;
|
|
524
|
+
}
|
|
525
|
+
static blankEmbeddedContent(html) {
|
|
526
|
+
return html.replace(/<script\b[^>]*>[\s\S]*?(?:<\/script(?:[\s/][^<>]*)?>|$)|<style\b[^>]*>[\s\S]*?(?:<\/style(?:[\s/][^<>]*)?>|$)|<!--[\s\S]*?(?:--!?>|$)/gi, (match) => " ".repeat(match.length));
|
|
527
|
+
}
|
|
528
|
+
static createInlineStyle(css) {
|
|
529
|
+
return `<style data-akan-css="active">
|
|
530
|
+
${css.replace(/<\/style/gi, "<\\/style")}
|
|
531
|
+
</style>`;
|
|
532
|
+
}
|
|
533
|
+
static async replaceModuleScriptSrc(html, loadScript) {
|
|
534
|
+
const scriptRe = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>\s*<\/script>/gi;
|
|
535
|
+
let result = "";
|
|
536
|
+
let lastIndex = 0;
|
|
537
|
+
let matched = false;
|
|
538
|
+
for (const match of html.matchAll(scriptRe)) {
|
|
539
|
+
const full = match[0];
|
|
540
|
+
const src = match[1];
|
|
541
|
+
if (match.index === undefined || !src)
|
|
542
|
+
continue;
|
|
543
|
+
matched = true;
|
|
544
|
+
result += html.slice(lastIndex, match.index);
|
|
545
|
+
result += `<script type="module">
|
|
546
|
+
${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
547
|
+
</script>`;
|
|
548
|
+
lastIndex = match.index + full.length;
|
|
549
|
+
}
|
|
550
|
+
if (!matched)
|
|
551
|
+
return html;
|
|
552
|
+
return result + html.slice(lastIndex);
|
|
553
|
+
}
|
|
554
|
+
static escapeInlineScript(source) {
|
|
555
|
+
return source.replace(/<\/script/gi, "<\\/script");
|
|
556
|
+
}
|
|
557
|
+
static resolveHtmlAssetPath(htmlPath, src) {
|
|
558
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) {
|
|
559
|
+
throw new Error(`[csr-build] cannot inline external script: ${src}`);
|
|
560
|
+
}
|
|
561
|
+
const normalized = src.startsWith("/") ? src.slice(1) : src;
|
|
562
|
+
return path4.resolve(path4.dirname(htmlPath), normalized);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export { CsrArtifactBuilder };
|
|
@@ -179,7 +179,7 @@ class SubagentPool2 {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
async#run(id, kind, prompt, signal) {
|
|
182
|
-
const { CodeAgent } = await import("./CodeAgent-
|
|
182
|
+
const { CodeAgent } = await import("./CodeAgent-fee0pnwp.js");
|
|
183
183
|
const budget = this.#options.parent.tools.subagent;
|
|
184
184
|
const child = SubagentPool2.childOf(this.#options.parent, kind);
|
|
185
185
|
const agent = await CodeAgent.create({
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
jsonText
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-5m0s0gdv.js";
|
|
5
5
|
import {
|
|
6
6
|
akanMcpInstallTargets
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-tbmtmkhx.js";
|
|
8
8
|
import {
|
|
9
9
|
script
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-g5px5pxd.js";
|
|
11
11
|
import {
|
|
12
12
|
ContextRunner2
|
|
13
|
-
} from "./index-
|
|
13
|
+
} from "./index-cq5x7a9k.js";
|
|
14
14
|
|
|
15
15
|
// pkgs/@akanjs/cli/context/context.script.ts
|
|
16
16
|
import { Logger } from "akanjs/common";
|