@akanjs/cli 3.0.0-alpha.11 → 3.0.0-alpha.111
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/README.ko.md +1 -1
- package/README.md +1 -1
- package/{abstractCompactor-e5yq60qz.js → abstractCompactor-mjh5ppsq.js} +3 -5
- package/{agent.command-a3n15c6t.js → agent.command-d2gfxfck.js} +11 -13
- package/{application.command-rdm3et0g.js → application.command-pj2m7584.js} +65 -38
- package/{applicationBuildRunner-6m8n71w4.js → applicationBuildRunner-kxwbssgn.js} +27 -23
- package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-4yasxwv9.js} +8 -9
- package/buildBatch.proc.js +12 -14
- package/capacitorApp-36rbr858.js +12 -0
- package/{cloud.command-56aeekqn.js → cloud.command-gxqp8xdv.js} +15 -18
- package/commandManifest.json +1 -1
- package/{context.command-ysxawe7h.js → context.command-ez8a0zyz.js} +19 -23
- package/dependencyScanner-shs82ynp.js +8 -0
- package/devTui-40ggmd99.js +553 -0
- package/devTuiView-c08ccbat.js +9 -0
- package/getDirname-dr3jz1xg.js +7 -0
- package/{guideline.command-yqf9a372.js → guideline.command-xmqwx8zm.js} +24 -22
- package/guidelines/agentRule/agentRule.generate.json +31 -0
- package/guidelines/agentRule/agentRule.instruction.md +531 -0
- package/guidelines/conventions/conventions.instruction.md +493 -369
- package/guidelines/framework/framework.generate.json +3 -2
- package/guidelines/framework/framework.instruction.md +0 -9
- package/guidelines/mcpRule/mcpRule.generate.json +35 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +460 -0
- package/guidelines/modelStore/modelStore.instruction.md +12 -0
- package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
- package/guidelines/queryRule/queryRule.generate.json +31 -0
- package/guidelines/queryRule/queryRule.instruction.md +178 -0
- package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
- package/guidelines/runtimeRule/runtimeRule.instruction.md +220 -0
- package/guidelines/ssrRule/ssrRule.instruction.md +51 -5
- package/guidelines/transportRule/transportRule.generate.json +31 -0
- package/guidelines/transportRule/transportRule.instruction.md +60 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +54 -764
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +467 -0
- package/incrementalBuilder.proc.js +26 -25
- package/{index-8pd02prk.js → index-259dsnz3.js} +58 -25
- package/{index-yzsxc30b.js → index-2tv3fdx4.js} +975 -234
- package/index-3d9y066c.js +76 -0
- package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
- package/{index-m90fn8xe.js → index-4edzf1gr.js} +32 -32
- package/{index-3789fdpg.js → index-600zhnve.js} +94 -61
- package/{index-1577bej2.js → index-6wwzs1bc.js} +2 -2
- package/{index-cv9cxs5k.js → index-7737gxcp.js} +18 -18
- package/{index-a5cs7d8r.js → index-8few9nyh.js} +13 -13
- package/{index-mewacxye.js → index-8pm30m73.js} +57 -57
- package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
- package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
- package/{index-cav1azma.js → index-a64esmtb.js} +1106 -1369
- package/{index-nwqqssnb.js → index-aj7tyh5w.js} +26 -23
- package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
- package/{index-yzmqvg23.js → index-fz5q3gd7.js} +8 -8
- package/{index-kcsa1zff.js → index-gyd220p2.js} +4 -4
- package/{index-j37qq1f2.js → index-h7yw2z9n.js} +7 -7
- package/{index-bjpxzr6s.js → index-hwkygn9s.js} +51 -3
- package/index-j9yhg404.js +159 -0
- package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
- package/{index-0cj2zxbm.js → index-kn1h7p1x.js} +12 -11
- package/{index-e4b2ae6b.js → index-ktb5njgy.js} +41 -34
- package/{index-etwh7sd3.js → index-mak3cd28.js} +100 -32
- package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
- package/{index-z1s5f4e0.js → index-ne2qy0nk.js} +36 -37
- package/{index-65aeh789.js → index-pnqbkf5p.js} +1 -1
- package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
- package/{index-4mgkgba3.js → index-rwg2aa4f.js} +14 -14
- package/{index-bzznxgcx.js → index-v0qh106m.js} +1002 -128
- package/{index-j9cxndc0.js → index-w8kmx1b6.js} +20 -11
- package/{index-h5whaj26.js → index-ws5e0j51.js} +16 -12
- package/{index-9va8zayb.js → index-wt7qrtd4.js} +27 -27
- package/index.js +22 -25
- package/{library.command-x6t29a30.js → library.command-nbsky672.js} +11 -10
- package/{localRegistry.command-649g1n7b.js → localRegistry.command-ts6t5e5a.js} +20 -23
- package/{module.command-8w8ys5t3.js → module.command-29vqzrh8.js} +13 -16
- package/{package.command-zgy7f3fz.js → package.command-axv0abmn.js} +8 -10
- package/package.json +5 -7
- package/{page.command-yjmnkyk2.js → page.command-vs9s3zrh.js} +7 -9
- package/{primitive.command-gfqaagsg.js → primitive.command-cbxmqbtc.js} +13 -16
- package/{quality.command-ec8r1dn3.js → quality.command-gmbs7nws.js} +184 -334
- package/recipeScanner-fpagtgc1.js +11 -0
- package/{repair.command-p9ey6mkm.js → repair.command-bqsjptsj.js} +12 -14
- package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-apyqw7wk.js} +90 -19
- package/{scalar.command-9dhmqhtm.js → scalar.command-6k1vhe9s.js} +11 -14
- package/subspace.command-35a3t6vv.js +755 -0
- package/templates/app/env/env.client.debug.ts.template +1 -5
- package/templates/app/env/env.client.develop.ts.template +1 -5
- package/templates/app/env/env.client.local.ts.template +1 -5
- package/templates/app/env/env.client.main.ts.template +1 -5
- package/templates/app/env/env.client.testing.ts.template +1 -5
- package/templates/app/env/env.client.type.ts +2 -6
- package/templates/app/env/env.server.debug.ts.template +1 -5
- package/templates/app/env/env.server.develop.ts.template +1 -5
- package/templates/app/env/env.server.local.ts.template +1 -5
- package/templates/app/env/env.server.main.ts.template +1 -5
- package/templates/app/main.ts +3 -1
- package/templates/app/page/_index.tsx +3 -3
- package/templates/app/page/_layout.tsx +31 -32
- package/templates/appSample/lib/task/task.signal.ts +11 -6
- package/templates/appSample/page/task/[taskId]/_index.tsx +22 -27
- package/templates/appSample/page/task/[taskId]/edit.tsx +25 -30
- package/templates/appSample/page/task/_index.tsx +3 -7
- package/templates/appSample/page/task/_layout.tsx +4 -15
- package/templates/appSample/page/task/new.tsx +4 -10
- package/templates/appSample/srvkit/AuthGuard.ts +2 -2
- package/templates/crudPages/[__model__Id]/edit/page.tsx +27 -29
- package/templates/crudPages/[__model__Id]/page.tsx +24 -32
- package/templates/crudPages/new/page.tsx +26 -25
- package/templates/crudPages/page.tsx +27 -26
- package/templates/crudSinglePage/page.tsx +26 -21
- package/templates/env/_env.server.type.ts +7 -5
- package/templates/lib/db.ts +1 -1
- package/templates/lib/dict.ts +1 -1
- package/templates/lib/sig.ts +5 -1
- package/templates/lib/srv.ts +6 -2
- package/templates/module/__model__.dictionary.ts +4 -1
- package/templates/module/__model__.signal.ts +2 -3
- package/templates/module/__model__.store.ts +1 -2
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/.husky/pre-commit.template +16 -0
- package/templates/workspaceRoot/.vscode/settings.json.template +1 -1
- package/templates/workspaceRoot/AGENTS.md.template +4 -0
- package/templates/workspaceRoot/CLAUDE.md.template +19 -0
- package/templates/workspaceRoot/biome.json.template +1 -1
- package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
- package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
- package/templates/workspaceRoot/package.json.template +2 -4
- package/typeChecker-3xgrzc2v.js +7 -0
- package/typecheck.proc.js +4 -5
- package/{workflow.command-ycwc6zr6.js → workflow.command-nwpcdc8p.js} +22 -25
- package/{workspace.command-xhyhajxa.js → workspace.command-aczjxzbf.js} +64 -66
- package/capacitorApp-mr81qdxq.js +0 -61
- package/dependencyScanner-f6grrep3.js +0 -9
- package/getDirname-m9s0cmhq.js +0 -8
- package/index-mxvakhsm.js +0 -66
- package/index-r24hmh0q.js +0 -4
- package/recipeScanner-tghkzrrt.js +0 -12
- package/typeChecker-kravn7ns.js +0 -8
- /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
- /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
|
@@ -1,16 +1,189 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import {
|
|
3
|
-
Spinner
|
|
4
|
-
} from "./index-mxvakhsm.js";
|
|
5
|
-
import {
|
|
6
|
-
getDirname
|
|
7
|
-
} from "./index-1577bej2.js";
|
|
8
2
|
import {
|
|
9
3
|
FileSys
|
|
10
|
-
} from "./index-
|
|
4
|
+
} from "./index-q0y2jq9j.js";
|
|
11
5
|
import {
|
|
12
|
-
|
|
13
|
-
} from "./index-
|
|
6
|
+
getDirname2
|
|
7
|
+
} from "./index-6wwzs1bc.js";
|
|
8
|
+
|
|
9
|
+
// pkgs/@akanjs/devkit/codegenLock.ts
|
|
10
|
+
import { mkdir, open, readFile, rm, stat } from "fs/promises";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { Logger } from "akanjs/common";
|
|
13
|
+
|
|
14
|
+
class CodegenLock {
|
|
15
|
+
static fileName = "codegen.lock";
|
|
16
|
+
static waitTimeoutMs = 1e4;
|
|
17
|
+
static unknownHolderStaleMs = 60000;
|
|
18
|
+
static #pollMs = 25;
|
|
19
|
+
static #logger = new Logger("CodegenLock");
|
|
20
|
+
static #queue = Promise.resolve();
|
|
21
|
+
static pathIn(workspaceRoot) {
|
|
22
|
+
return path.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
|
|
23
|
+
}
|
|
24
|
+
static async run(workspaceRoot, label, fn) {
|
|
25
|
+
const ahead = CodegenLock.#queue;
|
|
26
|
+
let done;
|
|
27
|
+
CodegenLock.#queue = new Promise((resolve) => {
|
|
28
|
+
done = resolve;
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
await CodegenLock.#waitForQueue(ahead, label);
|
|
32
|
+
return await CodegenLock.#withFileLock(workspaceRoot, label, fn);
|
|
33
|
+
} finally {
|
|
34
|
+
done();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
static async#waitForQueue(ahead, label) {
|
|
38
|
+
let timer = null;
|
|
39
|
+
const expired = new Promise((resolve) => {
|
|
40
|
+
timer = setTimeout(() => resolve("expired"), CodegenLock.waitTimeoutMs);
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
if (await Promise.race([ahead.then(() => "done"), expired]) === "expired")
|
|
44
|
+
CodegenLock.#logger.warn(`codegen lock queued past ${CodegenLock.waitTimeoutMs}ms in this process; continuing without waiting (${label})`);
|
|
45
|
+
} finally {
|
|
46
|
+
if (timer)
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
static async#withFileLock(workspaceRoot, label, fn) {
|
|
51
|
+
const lockPath = CodegenLock.pathIn(workspaceRoot);
|
|
52
|
+
await mkdir(path.dirname(lockPath), { recursive: true }).catch(() => {
|
|
53
|
+
return;
|
|
54
|
+
});
|
|
55
|
+
const held = await CodegenLock.#acquire(lockPath, label);
|
|
56
|
+
try {
|
|
57
|
+
return await fn();
|
|
58
|
+
} finally {
|
|
59
|
+
if (held)
|
|
60
|
+
await rm(lockPath, { force: true }).catch(() => {
|
|
61
|
+
return;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
static async#acquire(lockPath, label) {
|
|
66
|
+
const deadline = Date.now() + CodegenLock.waitTimeoutMs;
|
|
67
|
+
for (;; ) {
|
|
68
|
+
const handle = await open(lockPath, "wx").catch(() => null);
|
|
69
|
+
if (handle) {
|
|
70
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, at: Date.now(), label })).catch(() => {
|
|
71
|
+
return;
|
|
72
|
+
});
|
|
73
|
+
await handle.close().catch(() => {
|
|
74
|
+
return;
|
|
75
|
+
});
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (await CodegenLock.#reclaimIfAbandoned(lockPath))
|
|
79
|
+
continue;
|
|
80
|
+
if (Date.now() >= deadline) {
|
|
81
|
+
CodegenLock.#logger.warn(`codegen lock at ${lockPath} held past ${CodegenLock.waitTimeoutMs}ms; continuing without it (${label})`);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
await Bun.sleep(CodegenLock.#pollMs);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
static async#reclaimIfAbandoned(lockPath) {
|
|
88
|
+
const info = await stat(lockPath).catch(() => null);
|
|
89
|
+
if (!info)
|
|
90
|
+
return true;
|
|
91
|
+
const holder = CodegenLock.#parseHolder(await readFile(lockPath, "utf8").catch(() => ""));
|
|
92
|
+
if (holder) {
|
|
93
|
+
if (CodegenLock.#isAlive(holder.pid))
|
|
94
|
+
return false;
|
|
95
|
+
} else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
|
|
96
|
+
return false;
|
|
97
|
+
await rm(lockPath, { force: true }).catch(() => {
|
|
98
|
+
return;
|
|
99
|
+
});
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
static #parseHolder(raw) {
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(raw);
|
|
105
|
+
if (typeof parsed.pid !== "number" || typeof parsed.at !== "number")
|
|
106
|
+
return null;
|
|
107
|
+
return { pid: parsed.pid, at: parsed.at, label: typeof parsed.label === "string" ? parsed.label : "" };
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
static #isAlive(pid) {
|
|
113
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
114
|
+
return false;
|
|
115
|
+
try {
|
|
116
|
+
process.kill(pid, 0);
|
|
117
|
+
return true;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return error.code === "EPERM";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// pkgs/@akanjs/devkit/spinner.ts
|
|
125
|
+
import ora from "ora";
|
|
126
|
+
|
|
127
|
+
class Spinner {
|
|
128
|
+
static padding = 12;
|
|
129
|
+
static oraOptions = { discardStdin: false };
|
|
130
|
+
static canAnimate(stream = process.stderr) {
|
|
131
|
+
return !stream.isTTY || stream.columns > 0;
|
|
132
|
+
}
|
|
133
|
+
spinner;
|
|
134
|
+
stopWatch = null;
|
|
135
|
+
startAt = new Date;
|
|
136
|
+
prefix;
|
|
137
|
+
message;
|
|
138
|
+
enableSpin;
|
|
139
|
+
constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
|
|
140
|
+
Spinner.padding = Math.max(Spinner.padding, prefix.length);
|
|
141
|
+
this.prefix = prefix;
|
|
142
|
+
this.message = message;
|
|
143
|
+
this.spinner = ora({ ...Spinner.oraOptions, text: message });
|
|
144
|
+
this.spinner.prefixText = prefix.padStart(Spinner.padding, " ");
|
|
145
|
+
this.spinner.indent = indent;
|
|
146
|
+
this.enableSpin = enableSpin && Spinner.canAnimate();
|
|
147
|
+
}
|
|
148
|
+
start() {
|
|
149
|
+
this.startAt = new Date;
|
|
150
|
+
if (this.enableSpin) {
|
|
151
|
+
this.spinner.start();
|
|
152
|
+
this.stopWatch = setInterval(() => {
|
|
153
|
+
this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
|
|
154
|
+
this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
|
|
155
|
+
}, 1000);
|
|
156
|
+
} else
|
|
157
|
+
this.spinner.info();
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
succeed(message) {
|
|
161
|
+
this.spinner.succeed(`${message} (${this.#getElapsedTimeStr()})`);
|
|
162
|
+
this.#reset();
|
|
163
|
+
}
|
|
164
|
+
fail(message) {
|
|
165
|
+
this.spinner.fail(`${message} (${this.#getElapsedTimeStr()})`);
|
|
166
|
+
this.#reset();
|
|
167
|
+
}
|
|
168
|
+
isSpinning() {
|
|
169
|
+
return this.spinner.isSpinning;
|
|
170
|
+
}
|
|
171
|
+
#reset() {
|
|
172
|
+
if (this.stopWatch)
|
|
173
|
+
clearInterval(this.stopWatch);
|
|
174
|
+
this.stopWatch = null;
|
|
175
|
+
}
|
|
176
|
+
#getElapsedTimeStr({ floor = false } = {}) {
|
|
177
|
+
const ms = Date.now() - this.startAt.getTime();
|
|
178
|
+
if (ms < 1000)
|
|
179
|
+
return `${ms}ms`;
|
|
180
|
+
const s = Math.floor(ms / 1000);
|
|
181
|
+
if (s < 60)
|
|
182
|
+
return `${floor ? Math.floor(ms / 1000) : Math.floor(ms / 100) / 10}s`;
|
|
183
|
+
const m = Math.floor(s / 60);
|
|
184
|
+
return `${m}m ${s % 60}s`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
14
187
|
|
|
15
188
|
// pkgs/@akanjs/devkit/executors.ts
|
|
16
189
|
import {
|
|
@@ -22,19 +195,20 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
22
195
|
import {
|
|
23
196
|
copyFile,
|
|
24
197
|
cp as cpEntry,
|
|
25
|
-
mkdir,
|
|
198
|
+
mkdir as mkdir3,
|
|
26
199
|
readdir as readDirEntries,
|
|
27
200
|
realpath,
|
|
28
|
-
rm as
|
|
29
|
-
stat,
|
|
201
|
+
rm as rm3,
|
|
202
|
+
stat as stat2,
|
|
30
203
|
symlink
|
|
31
204
|
} from "fs/promises";
|
|
32
|
-
import
|
|
205
|
+
import path8 from "path";
|
|
33
206
|
import { pathToFileURL } from "url";
|
|
34
207
|
import {
|
|
35
208
|
capitalize,
|
|
209
|
+
getPageSourceFileViolation,
|
|
36
210
|
isRouteSourceFile,
|
|
37
|
-
Logger,
|
|
211
|
+
Logger as Logger2,
|
|
38
212
|
parseRouteModuleKey,
|
|
39
213
|
validatePageSourceFile,
|
|
40
214
|
validateSubRoutePageKey
|
|
@@ -44,7 +218,7 @@ import chalk2 from "chalk";
|
|
|
44
218
|
|
|
45
219
|
// pkgs/@akanjs/devkit/agentsIndex.ts
|
|
46
220
|
var recipeScannerLoad = null;
|
|
47
|
-
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-
|
|
221
|
+
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-fpagtgc1.js");
|
|
48
222
|
var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
|
|
49
223
|
var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
|
|
50
224
|
var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
|
|
@@ -54,9 +228,9 @@ ${block}`;
|
|
|
54
228
|
var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
|
|
55
229
|
var readDevkitVersion = async () => {
|
|
56
230
|
const { readFile } = await import("fs/promises");
|
|
57
|
-
const { getDirname
|
|
231
|
+
const { getDirname } = await import("./getDirname-dr3jz1xg.js");
|
|
58
232
|
try {
|
|
59
|
-
const raw = await readFile(`${
|
|
233
|
+
const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
|
|
60
234
|
return JSON.parse(raw).version ?? null;
|
|
61
235
|
} catch {
|
|
62
236
|
return null;
|
|
@@ -155,15 +329,36 @@ ${AGENT_BLOCK_START}
|
|
|
155
329
|
${block}
|
|
156
330
|
${AGENT_BLOCK_END}
|
|
157
331
|
`;
|
|
332
|
+
var CLAUDE_COMMENT_RULE = `## Comments \u2014 Overrides Your Default
|
|
333
|
+
|
|
334
|
+
Write **no comments** unless the comment passes the test below. This is the rule agents break most often here, so it
|
|
335
|
+
is repeated outside the guide: a diff that adds a comment the test rejects is a diff to redo.
|
|
336
|
+
|
|
337
|
+
Before typing \`//\`, \`/*\`, or a doc block, ask \u2014 **does this sentence carry a fact that is nowhere in the code?**
|
|
338
|
+
|
|
339
|
+
- Restates the identifier, the signature, or the line under it \u2192 delete it.
|
|
340
|
+
- Labels a section (\`// helpers\`, \`// state\`) or narrates a step (\`// fetch the user\`, \`// then save\`) \u2192 delete it.
|
|
341
|
+
- JSDoc on an ordinary function, or a why/how preamble on ordinary logic \u2192 delete it.
|
|
342
|
+
- Explains the edit you just made, for whoever reads the diff \u2192 say it in your reply, not in the file.
|
|
343
|
+
- Names a vendor or protocol quirk, an infrastructure constraint, a library gotcha, security reasoning, a math
|
|
344
|
+
derivation, a domain field's business meaning, a state transition, or why an obvious alternative was rejected \u2192
|
|
345
|
+
keep it, one line.
|
|
346
|
+
|
|
347
|
+
That keep-list is exact \u2014 \`Comments\` in the guide is the full version. "It aids readability" and "this logic is
|
|
348
|
+
subtle" are not on it: rename or split the code instead. When you edit an existing file, match its density; if the
|
|
349
|
+
surrounding code carries none, your diff carries none.`;
|
|
158
350
|
var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
|
|
159
351
|
|
|
160
352
|
@AGENTS.md
|
|
353
|
+
|
|
354
|
+
${CLAUDE_COMMENT_RULE}
|
|
161
355
|
`;
|
|
162
356
|
|
|
163
357
|
// pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
|
|
164
358
|
import fs from "fs";
|
|
165
|
-
import
|
|
359
|
+
import path2 from "path";
|
|
166
360
|
import { fileURLToPath } from "url";
|
|
361
|
+
import { normalizeRoutePrefix } from "akanjs/base";
|
|
167
362
|
import { resolveAkanI18nConfig } from "akanjs/common";
|
|
168
363
|
|
|
169
364
|
// pkgs/@akanjs/devkit/akanConfig/types.ts
|
|
@@ -199,9 +394,8 @@ var DEFAULT_OPTIMIZE_IMPORTS = [
|
|
|
199
394
|
"react-icons/*"
|
|
200
395
|
];
|
|
201
396
|
var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
|
|
397
|
+
var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
|
|
202
398
|
var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
|
|
203
|
-
var NATIVE_RUNTIME_PACKAGES = ["sharp"];
|
|
204
|
-
var DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"];
|
|
205
399
|
var MOBILE_RUNTIME_PACKAGES = [
|
|
206
400
|
"@capacitor/cli",
|
|
207
401
|
"@capacitor/core",
|
|
@@ -230,8 +424,6 @@ var DATABASE_MODE_RUNTIME_PACKAGES = {
|
|
|
230
424
|
};
|
|
231
425
|
var AKAN_RUNTIME_PACKAGES = new Set([
|
|
232
426
|
...SSR_RUNTIME_PACKAGES,
|
|
233
|
-
...NATIVE_RUNTIME_PACKAGES,
|
|
234
|
-
...DEFAULT_BACKEND_RUNTIME_PACKAGES,
|
|
235
427
|
...MOBILE_RUNTIME_PACKAGES,
|
|
236
428
|
...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat()
|
|
237
429
|
]);
|
|
@@ -246,14 +438,15 @@ var DEFAULT_AKAN_IMAGE_CONFIG = {
|
|
|
246
438
|
dangerouslyAllowSVG: false,
|
|
247
439
|
maximumRedirects: 3,
|
|
248
440
|
fetchTimeoutMs: 7000,
|
|
249
|
-
maxRemoteBytes: 25 * 1024 * 1024
|
|
441
|
+
maxRemoteBytes: 25 * 1024 * 1024,
|
|
442
|
+
maxConcurrency: 0
|
|
250
443
|
};
|
|
251
444
|
var normalizeIndexPath = (indexPath) => {
|
|
252
445
|
const normalized = indexPath?.trim();
|
|
253
446
|
if (!normalized)
|
|
254
447
|
return;
|
|
255
|
-
const
|
|
256
|
-
return
|
|
448
|
+
const path = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
449
|
+
return path === "/" ? "/" : path;
|
|
257
450
|
};
|
|
258
451
|
var normalizeStringList = (values) => {
|
|
259
452
|
const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
@@ -293,21 +486,39 @@ var normalizeDeepLinks = (deepLinks) => {
|
|
|
293
486
|
...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
|
|
294
487
|
};
|
|
295
488
|
};
|
|
489
|
+
var emptyLibContributions = () => ({
|
|
490
|
+
externalLibs: [],
|
|
491
|
+
docker: { preRuns: [], postRuns: [] },
|
|
492
|
+
keepFonts: []
|
|
493
|
+
});
|
|
494
|
+
var normalizeKeepFonts = (keepFonts) => [
|
|
495
|
+
...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean))
|
|
496
|
+
];
|
|
497
|
+
var dedupeDockerRuns = (runs) => {
|
|
498
|
+
const byKey = new Map;
|
|
499
|
+
for (const run of runs)
|
|
500
|
+
byKey.set(typeof run === "string" ? run : JSON.stringify(run), run);
|
|
501
|
+
return [...byKey.values()];
|
|
502
|
+
};
|
|
296
503
|
|
|
297
504
|
class AkanAppConfig {
|
|
298
505
|
app;
|
|
299
506
|
rootPackageJson;
|
|
300
507
|
docker;
|
|
508
|
+
dockerfile;
|
|
301
509
|
defaultDatabaseMode;
|
|
510
|
+
web;
|
|
302
511
|
externalLibs;
|
|
303
512
|
barrelImports;
|
|
304
513
|
optimizeImports;
|
|
305
514
|
images;
|
|
306
515
|
i18n;
|
|
516
|
+
api;
|
|
307
517
|
publicEnv;
|
|
308
518
|
mobile;
|
|
309
519
|
hasMobileConfig;
|
|
310
520
|
secrets;
|
|
521
|
+
assets;
|
|
311
522
|
syncPageLibs;
|
|
312
523
|
baseDevEnv;
|
|
313
524
|
libs;
|
|
@@ -316,7 +527,7 @@ class AkanAppConfig {
|
|
|
316
527
|
subRoutes = new Map;
|
|
317
528
|
basePaths = new Set;
|
|
318
529
|
branches = new Set(["debug", "develop", "main"]);
|
|
319
|
-
constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = []) {
|
|
530
|
+
constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = [], libContributions = emptyLibContributions()) {
|
|
320
531
|
this.app = app;
|
|
321
532
|
this.rootPackageJson = rootPackageJson;
|
|
322
533
|
this.libs = libs;
|
|
@@ -324,7 +535,7 @@ class AkanAppConfig {
|
|
|
324
535
|
this.plugins = plugins;
|
|
325
536
|
this.#applyRoutes(config?.routes);
|
|
326
537
|
this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
|
|
327
|
-
this.externalLibs = config?.externalLibs ?? [];
|
|
538
|
+
this.externalLibs = [...new Set([...config?.externalLibs ?? [], ...libContributions.externalLibs])];
|
|
328
539
|
this.barrelImports = [
|
|
329
540
|
...DEFAULT_BARREL_IMPORTS,
|
|
330
541
|
...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
|
|
@@ -336,12 +547,33 @@ class AkanAppConfig {
|
|
|
336
547
|
this.i18n = resolveAkanI18nConfig(config?.i18n);
|
|
337
548
|
process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
|
|
338
549
|
process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
|
|
550
|
+
this.api = {
|
|
551
|
+
prefix: normalizeRoutePrefix(config?.api?.prefix) ?? "/api",
|
|
552
|
+
websocketPrefix: normalizeRoutePrefix(config?.api?.websocketPrefix) ?? "/ws"
|
|
553
|
+
};
|
|
554
|
+
process.env.AKAN_PUBLIC_API_PREFIX = this.api.prefix;
|
|
555
|
+
process.env.AKAN_PUBLIC_WS_PREFIX = this.api.websocketPrefix;
|
|
339
556
|
this.publicEnv = config?.publicEnv ?? [];
|
|
340
557
|
this.secrets = config?.secrets ?? [];
|
|
558
|
+
this.assets = {
|
|
559
|
+
pruneFonts: config?.assets?.pruneFonts ?? true,
|
|
560
|
+
keepFonts: [
|
|
561
|
+
...normalizeKeepFonts(config?.assets?.keepFonts),
|
|
562
|
+
...libContributions.keepFonts ?? []
|
|
563
|
+
]
|
|
564
|
+
};
|
|
341
565
|
this.syncPageLibs = config?.syncPageLibs ?? false;
|
|
342
566
|
this.hasMobileConfig = Boolean(config.mobile);
|
|
343
567
|
this.mobile = this.#resolveMobileConfig(config.mobile);
|
|
344
|
-
this.
|
|
568
|
+
this.web = this.#resolveWebConfig(config.web);
|
|
569
|
+
this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
|
|
570
|
+
this.dockerfile = this.#makeDockerfile();
|
|
571
|
+
}
|
|
572
|
+
#resolveWebConfig(web) {
|
|
573
|
+
const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
|
|
574
|
+
if (!resolved.csr && this.hasMobileConfig)
|
|
575
|
+
throw new Error(`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares mobile targets; the Capacitor build ships that bundle. Drop the mobile section or leave CSR on.`);
|
|
576
|
+
return resolved;
|
|
345
577
|
}
|
|
346
578
|
#resolveMobileConfig(mobile) {
|
|
347
579
|
const {
|
|
@@ -451,23 +683,31 @@ class AkanAppConfig {
|
|
|
451
683
|
return archs.map((arch) => `FROM ${image[arch] ?? defaultImage} AS ${arch}`).join(`
|
|
452
684
|
`);
|
|
453
685
|
}
|
|
454
|
-
#
|
|
455
|
-
if (docker
|
|
456
|
-
return
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const
|
|
686
|
+
static #resolveDocker(docker, libDocker) {
|
|
687
|
+
if (typeof docker === "string")
|
|
688
|
+
return docker;
|
|
689
|
+
return {
|
|
690
|
+
image: docker?.image ?? DEFAULT_DOCKER_IMAGE,
|
|
691
|
+
preRuns: dedupeDockerRuns([...libDocker.preRuns, ...docker?.preRuns ?? []]),
|
|
692
|
+
postRuns: dedupeDockerRuns([...libDocker.postRuns, ...docker?.postRuns ?? []]),
|
|
693
|
+
command: docker?.command ?? ["bun", "main.js"]
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
#makeDockerfile() {
|
|
697
|
+
if (typeof this.docker === "string")
|
|
698
|
+
return this.docker;
|
|
699
|
+
const { image, preRuns, postRuns, command } = this.docker;
|
|
700
|
+
const preRunScripts = this.#getDockerRunScripts(preRuns);
|
|
701
|
+
const postRunScripts = this.#getDockerRunScripts(postRuns);
|
|
702
|
+
const imageScript = this.#getDockerImageScript(image, DEFAULT_DOCKER_IMAGE);
|
|
703
|
+
const webEnvLines = [
|
|
704
|
+
...this.web.ssr ? [] : ["ENV AKAN_SSR=false"],
|
|
705
|
+
...this.web.csr ? [] : ["ENV AKAN_CSR=false"]
|
|
706
|
+
].join(`
|
|
707
|
+
`);
|
|
708
|
+
return `${imageScript}
|
|
709
|
+
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
|
|
468
710
|
RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
|
|
469
|
-
RUN apt-get update && apt-get upgrade -y
|
|
470
|
-
RUN apt-get install -y --no-install-recommends git redis build-essential python3 ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils udev ffmpeg
|
|
471
711
|
ARG TARGETARCH
|
|
472
712
|
${preRunScripts.join(`
|
|
473
713
|
`)}
|
|
@@ -487,16 +727,12 @@ ENV AKAN_PUBLIC_ENV=${this.baseDevEnv.env}
|
|
|
487
727
|
${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join(",")}` : ""}
|
|
488
728
|
ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
|
|
489
729
|
ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
|
|
730
|
+
ENV AKAN_PUBLIC_API_PREFIX=${this.api.prefix}
|
|
731
|
+
ENV AKAN_PUBLIC_WS_PREFIX=${this.api.websocketPrefix}
|
|
490
732
|
ENV AKAN_PUBLIC_OPERATION_MODE=cloud
|
|
491
|
-
|
|
733
|
+
ENV AKAN_LOG_TO_FILE=0
|
|
734
|
+
${webEnvLines}
|
|
492
735
|
CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
493
|
-
return {
|
|
494
|
-
content,
|
|
495
|
-
image: imageScript,
|
|
496
|
-
preRuns: docker.preRuns ?? [],
|
|
497
|
-
postRuns: docker.postRuns ?? [],
|
|
498
|
-
command
|
|
499
|
-
};
|
|
500
736
|
}
|
|
501
737
|
static #importGeneration = 0;
|
|
502
738
|
static async importConfigModule(cwdPath, { bustImportCache = false } = {}) {
|
|
@@ -507,13 +743,28 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
507
743
|
static async from(app, { bustImportCache = false } = {}) {
|
|
508
744
|
const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
|
|
509
745
|
AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
|
|
510
|
-
WorkspaceExecutor.getBaseDevEnv(
|
|
746
|
+
WorkspaceExecutor.getBaseDevEnv(path2.join(app.workspace.workspaceRoot, ".env")),
|
|
511
747
|
app.workspace.getLibs(),
|
|
512
748
|
app.workspace.getPackageJson()
|
|
513
749
|
]);
|
|
514
750
|
const resolved = typeof configImp === "function" ? configImp(app) : configImp;
|
|
515
751
|
const { plugins, ...config } = resolved ?? {};
|
|
516
|
-
|
|
752
|
+
const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
|
|
753
|
+
return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
|
|
754
|
+
}
|
|
755
|
+
static async#collectLibContributions(app, libs, bustImportCache) {
|
|
756
|
+
const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
|
|
757
|
+
app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
|
|
758
|
+
return null;
|
|
759
|
+
})));
|
|
760
|
+
return {
|
|
761
|
+
externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
|
|
762
|
+
docker: {
|
|
763
|
+
preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
|
|
764
|
+
postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? [])
|
|
765
|
+
},
|
|
766
|
+
keepFonts: libConfigs.flatMap((libConfig) => (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`))
|
|
767
|
+
};
|
|
517
768
|
}
|
|
518
769
|
#resolveProductionDependencyVersion(lib) {
|
|
519
770
|
const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
|
|
@@ -523,13 +774,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
523
774
|
return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
524
775
|
}
|
|
525
776
|
#getProductionRuntimePackages() {
|
|
526
|
-
return [
|
|
527
|
-
...this.externalLibs,
|
|
528
|
-
...SSR_RUNTIME_PACKAGES,
|
|
529
|
-
...NATIVE_RUNTIME_PACKAGES,
|
|
530
|
-
...DEFAULT_BACKEND_RUNTIME_PACKAGES,
|
|
531
|
-
...this.getDatabaseModeRuntimePackages()
|
|
532
|
-
];
|
|
777
|
+
return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
|
|
533
778
|
}
|
|
534
779
|
getDatabaseModeRuntimePackages(databaseMode = this.defaultDatabaseMode) {
|
|
535
780
|
return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
|
|
@@ -578,11 +823,11 @@ var akanPackageJson = null;
|
|
|
578
823
|
function getAkanPackageJson() {
|
|
579
824
|
if (akanPackageJson)
|
|
580
825
|
return akanPackageJson;
|
|
581
|
-
const sourceDir =
|
|
826
|
+
const sourceDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
582
827
|
const packageJsonPaths = [
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
828
|
+
path2.join(sourceDir, "../../../akanjs/package.json"),
|
|
829
|
+
path2.join(process.cwd(), "pkgs/akanjs/package.json"),
|
|
830
|
+
path2.join(path2.dirname(Bun.main), "node_modules/akanjs/package.json")
|
|
586
831
|
];
|
|
587
832
|
try {
|
|
588
833
|
packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
|
|
@@ -617,14 +862,18 @@ function mergeImageConfig(config = {}) {
|
|
|
617
862
|
class AkanLibConfig {
|
|
618
863
|
lib;
|
|
619
864
|
externalLibs;
|
|
865
|
+
docker;
|
|
866
|
+
assets;
|
|
620
867
|
plugins;
|
|
621
868
|
constructor(lib, config, plugins = []) {
|
|
622
869
|
this.lib = lib;
|
|
623
870
|
this.externalLibs = config?.externalLibs ?? [];
|
|
871
|
+
this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
|
|
872
|
+
this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
|
|
624
873
|
this.plugins = plugins;
|
|
625
874
|
}
|
|
626
|
-
static async from(lib) {
|
|
627
|
-
const
|
|
875
|
+
static async from(lib, { bustImportCache = false } = {}) {
|
|
876
|
+
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
628
877
|
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
629
878
|
const { plugins, ...config } = resolved ?? {};
|
|
630
879
|
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
@@ -633,23 +882,422 @@ class AkanLibConfig {
|
|
|
633
882
|
//! need to refactor
|
|
634
883
|
var increaseBuildNum = async (app) => {
|
|
635
884
|
const appConfig = await AkanAppConfig.from(app);
|
|
636
|
-
const akanConfigPath =
|
|
885
|
+
const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
|
|
637
886
|
const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
|
|
638
887
|
const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum + 1}`);
|
|
639
888
|
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
640
889
|
};
|
|
641
890
|
var decreaseBuildNum = async (app) => {
|
|
642
891
|
const appConfig = await AkanAppConfig.from(app);
|
|
643
|
-
const akanConfigPath =
|
|
892
|
+
const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
|
|
644
893
|
const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
|
|
645
894
|
const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
|
|
646
895
|
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
647
896
|
};
|
|
897
|
+
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
898
|
+
import { mkdir as mkdir2 } from "fs/promises";
|
|
899
|
+
import path4 from "path";
|
|
900
|
+
|
|
901
|
+
// pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
|
|
902
|
+
import fs2 from "fs";
|
|
903
|
+
import path3 from "path";
|
|
904
|
+
|
|
905
|
+
class AsyncDefaultExportDetector {
|
|
906
|
+
static #typescriptLoad;
|
|
907
|
+
static #loadTypescript() {
|
|
908
|
+
AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
|
|
909
|
+
return AsyncDefaultExportDetector.#typescriptLoad;
|
|
910
|
+
}
|
|
911
|
+
static async detect(moduleAbsPath) {
|
|
912
|
+
try {
|
|
913
|
+
const typescript = await AsyncDefaultExportDetector.#loadTypescript();
|
|
914
|
+
const source = fs2.readFileSync(path3.resolve(moduleAbsPath), "utf8");
|
|
915
|
+
const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, AsyncDefaultExportDetector.#scriptKind(typescript, moduleAbsPath));
|
|
916
|
+
return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
|
|
917
|
+
} catch {
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
#ts;
|
|
922
|
+
constructor(typescript) {
|
|
923
|
+
this.#ts = typescript;
|
|
924
|
+
}
|
|
925
|
+
detectInSourceFile(sourceFile) {
|
|
926
|
+
const ts = this.#ts;
|
|
927
|
+
const asyncBindings = new Map;
|
|
928
|
+
let defaultIdentifier = null;
|
|
929
|
+
for (const statement of sourceFile.statements) {
|
|
930
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
931
|
+
if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
|
|
932
|
+
return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
|
|
933
|
+
}
|
|
934
|
+
if (statement.name) {
|
|
935
|
+
asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
|
|
936
|
+
}
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (ts.isVariableStatement(statement)) {
|
|
940
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
941
|
+
if (!ts.isIdentifier(declaration.name))
|
|
942
|
+
continue;
|
|
943
|
+
asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
|
|
944
|
+
}
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
if (ts.isExportAssignment(statement)) {
|
|
948
|
+
if (this.#isAsyncFunctionExpression(statement.expression))
|
|
949
|
+
return true;
|
|
950
|
+
if (ts.isCallExpression(statement.expression))
|
|
951
|
+
return true;
|
|
952
|
+
if (ts.isIdentifier(statement.expression))
|
|
953
|
+
defaultIdentifier = statement.expression.text;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
957
|
+
const exportClause = statement.exportClause;
|
|
958
|
+
for (const specifier of exportClause.elements) {
|
|
959
|
+
if (specifier.name.text !== "default")
|
|
960
|
+
continue;
|
|
961
|
+
defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
|
|
966
|
+
}
|
|
967
|
+
#hasModifier(node, kind) {
|
|
968
|
+
const ts = this.#ts;
|
|
969
|
+
return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
|
|
970
|
+
}
|
|
971
|
+
#isAsyncFunctionExpression(node) {
|
|
972
|
+
const ts = this.#ts;
|
|
973
|
+
return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
|
|
974
|
+
}
|
|
975
|
+
static #scriptKind(typescript, moduleAbsPath) {
|
|
976
|
+
return moduleAbsPath.endsWith(".tsx") || moduleAbsPath.endsWith(".jsx") ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
981
|
+
var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
982
|
+
async function appHasStModule(appCwdPath) {
|
|
983
|
+
return Bun.file(path4.join(appCwdPath, "lib", "st.ts")).exists();
|
|
984
|
+
}
|
|
985
|
+
var IMPLICIT_LAYOUT_DIR = path4.join(".akan", "generated", "root-layouts");
|
|
986
|
+
var IMPLICIT_DICT_DIR = path4.join(".akan", "generated", "dict");
|
|
987
|
+
var IMPLICIT_OVERRIDES_DIR = path4.join(".akan", "generated", "overrides");
|
|
988
|
+
var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
989
|
+
async function writeGeneratedOverridesLayoutFile(opts) {
|
|
990
|
+
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
991
|
+
const absPath = path4.join(path4.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
992
|
+
const userRel = path4.relative(path4.dirname(absPath), opts.userAbsPath).split(path4.sep).join("/");
|
|
993
|
+
const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
|
|
994
|
+
const source = `"use client";
|
|
995
|
+
import { UiOverrideProvider } from "akanjs/ui";
|
|
996
|
+
import { createElement, type ReactNode } from "react";
|
|
997
|
+
import value from ${JSON.stringify(userSpecifier)};
|
|
998
|
+
|
|
999
|
+
export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
|
|
1000
|
+
return createElement(UiOverrideProvider, { value }, children);
|
|
1001
|
+
}
|
|
1002
|
+
`;
|
|
1003
|
+
await Bun.write(absPath, source);
|
|
1004
|
+
return absPath;
|
|
1005
|
+
}
|
|
1006
|
+
function getRootBoundarySegments(key) {
|
|
1007
|
+
const match = LAYOUT_KEY_RE.exec(key);
|
|
1008
|
+
if (!match)
|
|
1009
|
+
return null;
|
|
1010
|
+
const prefix = match[1]?.replace(/\/$/, "");
|
|
1011
|
+
if (!prefix)
|
|
1012
|
+
return [];
|
|
1013
|
+
return prefix.split("/").filter(Boolean);
|
|
1014
|
+
}
|
|
1015
|
+
function implicitRootLayoutKey(segments) {
|
|
1016
|
+
return `./${[...segments, "__root_layout"].join("/")}.tsx`;
|
|
1017
|
+
}
|
|
1018
|
+
function implicitRootLayoutAbsPath(appCwdPath, segments) {
|
|
1019
|
+
const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
|
|
1020
|
+
return path4.join(path4.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
1021
|
+
}
|
|
1022
|
+
function implicitDictionaryMacroAbsPath(appCwdPath) {
|
|
1023
|
+
return path4.join(path4.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
1024
|
+
}
|
|
1025
|
+
function isRootBoundarySegments(segments, basePaths) {
|
|
1026
|
+
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
1027
|
+
if (firstVisibleIndex === -1)
|
|
1028
|
+
return segments.length <= 1;
|
|
1029
|
+
if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
|
|
1030
|
+
return false;
|
|
1031
|
+
const visible = segments.slice(firstVisibleIndex);
|
|
1032
|
+
const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
|
|
1033
|
+
return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
|
|
1034
|
+
}
|
|
1035
|
+
function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
|
|
1036
|
+
const boundaries = new Map;
|
|
1037
|
+
for (const key of pageKeys) {
|
|
1038
|
+
const segments = getRootBoundarySegments(key);
|
|
1039
|
+
if (!segments)
|
|
1040
|
+
continue;
|
|
1041
|
+
if (!isRootBoundarySegments(segments, basePaths))
|
|
1042
|
+
continue;
|
|
1043
|
+
const id = segments.join("/");
|
|
1044
|
+
boundaries.set(id, {
|
|
1045
|
+
sourceKey: key,
|
|
1046
|
+
sourceAbsPath: path4.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
|
|
1047
|
+
segments
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
const hasExplicitRootBoundary = [...boundaries.values()].some((boundary) => boundary.segments.length === 0);
|
|
1051
|
+
if (!hasExplicitRootBoundary && boundaries.size === 0) {
|
|
1052
|
+
boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
1053
|
+
}
|
|
1054
|
+
return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
|
|
1055
|
+
}
|
|
1056
|
+
function hasAncestorRootBoundary(boundary, boundaries) {
|
|
1057
|
+
return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
|
|
1058
|
+
}
|
|
1059
|
+
function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
|
|
1060
|
+
const rootLayoutKey = pageKeys.find((key) => {
|
|
1061
|
+
const segments = getRootBoundarySegments(key);
|
|
1062
|
+
return segments !== null && segments.length === 0;
|
|
1063
|
+
});
|
|
1064
|
+
return rootLayoutKey ? path4.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
1065
|
+
}
|
|
1066
|
+
function routePrefixForSegments(segments) {
|
|
1067
|
+
const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
|
|
1068
|
+
return visible[0] ?? null;
|
|
1069
|
+
}
|
|
1070
|
+
async function assertEnvClientConvention(appCwdPath, appName) {
|
|
1071
|
+
const envPath = path4.join(appCwdPath, "env", "env.client.ts");
|
|
1072
|
+
if (!await Bun.file(envPath).exists()) {
|
|
1073
|
+
throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
|
|
1077
|
+
const absPath = implicitDictionaryMacroAbsPath(appCwdPath);
|
|
1078
|
+
await mkdir2(path4.dirname(absPath), { recursive: true });
|
|
1079
|
+
await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
1080
|
+
|
|
1081
|
+
export const allDictionary = getAllDictionary();
|
|
1082
|
+
`);
|
|
1083
|
+
return absPath;
|
|
1084
|
+
}
|
|
1085
|
+
async function writeGeneratedRootLayoutFile(opts) {
|
|
1086
|
+
await assertEnvClientConvention(opts.appCwdPath, opts.appName);
|
|
1087
|
+
const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
|
|
1088
|
+
const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
|
|
1089
|
+
await mkdir2(path4.dirname(absPath), { recursive: true });
|
|
1090
|
+
const dictMacroRel = dictMacroAbsPath ? path4.relative(path4.dirname(absPath), dictMacroAbsPath).split(path4.sep).join("/") : null;
|
|
1091
|
+
const dictMacroSpecifier = dictMacroRel ? dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}` : null;
|
|
1092
|
+
const sourceRel = opts.boundary.sourceAbsPath ? path4.relative(path4.dirname(absPath), opts.boundary.sourceAbsPath).split(path4.sep).join("/") : null;
|
|
1093
|
+
const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
|
|
1094
|
+
const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
|
|
1095
|
+
const inheritedSourceRel = inheritedSourceAbsPath ? path4.relative(path4.dirname(absPath), inheritedSourceAbsPath).split(path4.sep).join("/") : null;
|
|
1096
|
+
const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
|
|
1097
|
+
const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
|
|
1098
|
+
void st;
|
|
1099
|
+
` : `import "@apps/${opts.appName}/client";
|
|
1100
|
+
`;
|
|
1101
|
+
const inheritedLabel = inheritedSourceAbsPath ? path4.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
|
|
1102
|
+
const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
|
|
1103
|
+
const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
|
|
1104
|
+
` : `const inheritedLayout: LayoutModule = {};
|
|
1105
|
+
`;
|
|
1106
|
+
const prefix = routePrefixForSegments(opts.boundary.segments);
|
|
1107
|
+
const userLabel = opts.boundary.sourceAbsPath ? path4.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
|
|
1108
|
+
const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
|
|
1109
|
+
const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
|
|
1110
|
+
const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
|
|
1111
|
+
` : `const UserLayout = ({ children }: LayoutProps) => children;
|
|
1112
|
+
const userLayout: LayoutModule = {};
|
|
1113
|
+
`;
|
|
1114
|
+
const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
|
|
1115
|
+
const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
|
|
1116
|
+
const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
|
|
1117
|
+
const layoutBinding = isAsyncUserLayout ? ` const layout =
|
|
1118
|
+
process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
|
|
1119
|
+
? await UserLayout({ params, searchParams, children })
|
|
1120
|
+
: ${userLayoutElement};
|
|
1121
|
+
` : "";
|
|
1122
|
+
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
1123
|
+
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
1124
|
+
const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
1125
|
+
import { loadFonts, resolveRouteModule } from "akanjs/client";
|
|
1126
|
+
import type { ReactNode } from "react";
|
|
1127
|
+
import { System } from "akanjs/ui";
|
|
1128
|
+
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
1129
|
+
import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
|
|
1130
|
+
${clientImport}${inheritedImport}${userImport}
|
|
1131
|
+
const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
|
|
1132
|
+
const defaultFonts = userFonts.filter((font) => font.default);
|
|
1133
|
+
if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
|
|
1134
|
+
const defaultFont = defaultFonts[0];
|
|
1135
|
+
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
1136
|
+
|
|
1137
|
+
export async function generateHead(props: PageProps) {
|
|
1138
|
+
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
1139
|
+
if (userLayout.head !== undefined) return userLayout.head;
|
|
1140
|
+
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
1141
|
+
return inheritedLayout.head;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
export async function generateMetadata(props: PageProps) {
|
|
1145
|
+
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
1146
|
+
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
1147
|
+
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
1148
|
+
return inheritedLayout.metadata;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
1152
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
1153
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
1154
|
+
|
|
1155
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
1156
|
+
${layoutBinding} return (
|
|
1157
|
+
<System.Provider
|
|
1158
|
+
of={GeneratedLayout as never}
|
|
1159
|
+
appName=${JSON.stringify(opts.appName)}
|
|
1160
|
+
${prefix ? `prefix=${JSON.stringify(prefix)}
|
|
1161
|
+
` : ""}params={params}
|
|
1162
|
+
manifest={userLayout.manifest ?? inheritedLayout.manifest}
|
|
1163
|
+
env={env}
|
|
1164
|
+
theme={userLayout.theme ?? inheritedLayout.theme}
|
|
1165
|
+
fonts={loadFonts(userFonts)}
|
|
1166
|
+
className={defaultFontClassName}
|
|
1167
|
+
gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
|
|
1168
|
+
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
1169
|
+
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
1170
|
+
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
|
|
1171
|
+
allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
|
|
1172
|
+
>
|
|
1173
|
+
${layoutChild}
|
|
1174
|
+
</System.Provider>
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
1178
|
+
import { resolveRouteModule } from "akanjs/client";
|
|
1179
|
+
import type { ReactNode } from "react";
|
|
1180
|
+
${inheritedImport}${userImport}
|
|
1181
|
+
export async function generateHead(props: PageProps) {
|
|
1182
|
+
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
1183
|
+
if (userLayout.head !== undefined) return userLayout.head;
|
|
1184
|
+
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
1185
|
+
return inheritedLayout.head;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export async function generateMetadata(props: PageProps) {
|
|
1189
|
+
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
1190
|
+
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
1191
|
+
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
1192
|
+
return inheritedLayout.metadata;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
1196
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
1197
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
1198
|
+
|
|
1199
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
1200
|
+
${layoutBinding} return ${layoutReturn};
|
|
1201
|
+
}
|
|
1202
|
+
`;
|
|
1203
|
+
await Bun.write(absPath, source);
|
|
1204
|
+
return absPath;
|
|
1205
|
+
}
|
|
1206
|
+
async function resolveSsrPageEntries(opts) {
|
|
1207
|
+
const absPageDir = path4.resolve(opts.appCwdPath, "page");
|
|
1208
|
+
const hasSt = await appHasStModule(opts.appCwdPath);
|
|
1209
|
+
const basePaths = opts.basePaths ?? [];
|
|
1210
|
+
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
1211
|
+
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
1212
|
+
const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
|
|
1213
|
+
const segments = getRootBoundarySegments(key);
|
|
1214
|
+
return segments !== null && isRootBoundarySegments(segments, basePaths);
|
|
1215
|
+
}));
|
|
1216
|
+
const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
|
|
1217
|
+
const userAbsPath = path4.resolve(absPageDir, key);
|
|
1218
|
+
if (OVERRIDES_KEY_RE.test(key)) {
|
|
1219
|
+
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
1220
|
+
appCwdPath: opts.appCwdPath,
|
|
1221
|
+
key,
|
|
1222
|
+
userAbsPath
|
|
1223
|
+
});
|
|
1224
|
+
return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
|
|
1225
|
+
}
|
|
1226
|
+
return { key, moduleAbsPath: userAbsPath };
|
|
1227
|
+
}));
|
|
1228
|
+
const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
|
|
1229
|
+
key: implicitRootLayoutKey(boundary.segments),
|
|
1230
|
+
moduleAbsPath: await writeGeneratedRootLayoutFile({
|
|
1231
|
+
appCwdPath: opts.appCwdPath,
|
|
1232
|
+
appName: opts.appName,
|
|
1233
|
+
boundary,
|
|
1234
|
+
rootSourceAbsPath,
|
|
1235
|
+
includeStInit: hasSt && boundary.segments.length === 0,
|
|
1236
|
+
includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
|
|
1237
|
+
}),
|
|
1238
|
+
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
|
|
1239
|
+
})));
|
|
1240
|
+
const entries = [...base, ...generated];
|
|
1241
|
+
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
1242
|
+
return entries;
|
|
1243
|
+
}
|
|
1244
|
+
async function resolveSsrPageEntriesForApp(app, pageKeys) {
|
|
1245
|
+
const config = await app.getConfig();
|
|
1246
|
+
return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
|
|
1247
|
+
}
|
|
1248
|
+
|
|
648
1249
|
// pkgs/@akanjs/devkit/linter.ts
|
|
649
1250
|
import { spawn } from "child_process";
|
|
650
1251
|
import { existsSync, readFileSync } from "fs";
|
|
651
|
-
import
|
|
1252
|
+
import path5 from "path";
|
|
652
1253
|
import chalk from "chalk";
|
|
1254
|
+
var parseBiomeReport = (output) => {
|
|
1255
|
+
for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
|
|
1256
|
+
const end = objectEndAt(output, start);
|
|
1257
|
+
if (end === -1)
|
|
1258
|
+
break;
|
|
1259
|
+
const report = asBiomeReport(output.slice(start, end + 1));
|
|
1260
|
+
if (report)
|
|
1261
|
+
return report;
|
|
1262
|
+
}
|
|
1263
|
+
throw new Error(output.trim() || "No Biome JSON output");
|
|
1264
|
+
};
|
|
1265
|
+
var objectEndAt = (output, start) => {
|
|
1266
|
+
let depth = 0;
|
|
1267
|
+
let inString = false;
|
|
1268
|
+
let escaped = false;
|
|
1269
|
+
for (let idx = start;idx < output.length; idx += 1) {
|
|
1270
|
+
const char = output[idx];
|
|
1271
|
+
if (inString) {
|
|
1272
|
+
if (escaped)
|
|
1273
|
+
escaped = false;
|
|
1274
|
+
else if (char === "\\")
|
|
1275
|
+
escaped = true;
|
|
1276
|
+
else if (char === '"')
|
|
1277
|
+
inString = false;
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
if (char === '"')
|
|
1281
|
+
inString = true;
|
|
1282
|
+
else if (char === "{")
|
|
1283
|
+
depth += 1;
|
|
1284
|
+
else if (char === "}" && --depth === 0)
|
|
1285
|
+
return idx;
|
|
1286
|
+
}
|
|
1287
|
+
return -1;
|
|
1288
|
+
};
|
|
1289
|
+
var asBiomeReport = (candidate) => {
|
|
1290
|
+
try {
|
|
1291
|
+
const parsed = JSON.parse(candidate);
|
|
1292
|
+
if (!parsed || typeof parsed !== "object")
|
|
1293
|
+
return null;
|
|
1294
|
+
if (!("diagnostics" in parsed) && !("summary" in parsed))
|
|
1295
|
+
return null;
|
|
1296
|
+
return parsed;
|
|
1297
|
+
} catch {
|
|
1298
|
+
return null;
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
653
1301
|
var BIOME_CONFIG_FILES = ["biome.json", "biome.jsonc"];
|
|
654
1302
|
|
|
655
1303
|
class Linter {
|
|
@@ -658,13 +1306,13 @@ class Linter {
|
|
|
658
1306
|
#biomeBin;
|
|
659
1307
|
constructor(cwdPath) {
|
|
660
1308
|
this.lintRoot = this.#findBiomeRootPath(cwdPath);
|
|
661
|
-
this.configPath = Linter.#configPathIn(this.lintRoot) ??
|
|
662
|
-
const localBiomeBin =
|
|
1309
|
+
this.configPath = Linter.#configPathIn(this.lintRoot) ?? path5.join(this.lintRoot, "biome.json");
|
|
1310
|
+
const localBiomeBin = path5.join(this.lintRoot, "node_modules/.bin/biome");
|
|
663
1311
|
this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
|
|
664
1312
|
}
|
|
665
1313
|
static #configPathIn(dir) {
|
|
666
1314
|
for (const fileName of BIOME_CONFIG_FILES) {
|
|
667
|
-
const configPath =
|
|
1315
|
+
const configPath = path5.join(dir, fileName);
|
|
668
1316
|
if (existsSync(configPath))
|
|
669
1317
|
return configPath;
|
|
670
1318
|
}
|
|
@@ -673,19 +1321,19 @@ class Linter {
|
|
|
673
1321
|
#findBiomeRootPath(dir) {
|
|
674
1322
|
if (Linter.#configPathIn(dir))
|
|
675
1323
|
return dir;
|
|
676
|
-
const parentDir =
|
|
1324
|
+
const parentDir = path5.dirname(dir);
|
|
677
1325
|
if (parentDir === dir)
|
|
678
1326
|
throw new Error(`biome.json not found from ${dir}`);
|
|
679
1327
|
return this.#findBiomeRootPath(parentDir);
|
|
680
1328
|
}
|
|
681
1329
|
#toBiomePath(filePath) {
|
|
682
|
-
const relativePath =
|
|
683
|
-
if (!relativePath.startsWith("..") && !
|
|
1330
|
+
const relativePath = path5.relative(this.lintRoot, filePath);
|
|
1331
|
+
if (!relativePath.startsWith("..") && !path5.isAbsolute(relativePath))
|
|
684
1332
|
return relativePath;
|
|
685
1333
|
return filePath;
|
|
686
1334
|
}
|
|
687
1335
|
#resolveFilePath(filePath) {
|
|
688
|
-
return
|
|
1336
|
+
return path5.isAbsolute(filePath) ? filePath : path5.join(this.lintRoot, filePath);
|
|
689
1337
|
}
|
|
690
1338
|
async#runBiome(args, input) {
|
|
691
1339
|
return await new Promise((resolve, reject) => {
|
|
@@ -706,18 +1354,11 @@ class Linter {
|
|
|
706
1354
|
proc.stdin.end(input);
|
|
707
1355
|
});
|
|
708
1356
|
}
|
|
709
|
-
#parseBiomeReport(output) {
|
|
710
|
-
const jsonStart = output.indexOf("{");
|
|
711
|
-
const jsonEnd = output.lastIndexOf("}");
|
|
712
|
-
if (jsonStart === -1 || jsonEnd === -1 || jsonEnd < jsonStart)
|
|
713
|
-
throw new Error(output.trim() || "No Biome JSON output");
|
|
714
|
-
return JSON.parse(output.slice(jsonStart, jsonEnd + 1));
|
|
715
|
-
}
|
|
716
1357
|
#diagnosticFilePath(diagnostic, fallbackFilePath) {
|
|
717
1358
|
const diagnosticPath = diagnostic.location?.path;
|
|
718
1359
|
if (!diagnosticPath)
|
|
719
1360
|
return fallbackFilePath;
|
|
720
|
-
return
|
|
1361
|
+
return path5.isAbsolute(diagnosticPath) ? diagnosticPath : path5.join(this.lintRoot, diagnosticPath);
|
|
721
1362
|
}
|
|
722
1363
|
#createLintMessage(diagnostic) {
|
|
723
1364
|
const start = diagnostic.location?.start;
|
|
@@ -785,7 +1426,7 @@ class Linter {
|
|
|
785
1426
|
this.configPath,
|
|
786
1427
|
this.#toBiomePath(filePath)
|
|
787
1428
|
]);
|
|
788
|
-
const report =
|
|
1429
|
+
const report = parseBiomeReport(stdout || stderr);
|
|
789
1430
|
const results = this.#toLintResults(report, filePath);
|
|
790
1431
|
const { errors, warnings } = this.#splitMessages(results);
|
|
791
1432
|
const output = write && existsSync(filePath) ? readFileSync(filePath, "utf8") : undefined;
|
|
@@ -907,6 +1548,25 @@ ${errorText}, ${warningText} found`];
|
|
|
907
1548
|
const lintResult = await this.lintFile(resolvedFilePath);
|
|
908
1549
|
return { ...lintResult, fixed: stdout !== source, output: stdout };
|
|
909
1550
|
}
|
|
1551
|
+
async fixFiles(filePaths) {
|
|
1552
|
+
const resolved = filePaths.map((filePath) => this.#resolveFilePath(filePath)).filter(existsSync);
|
|
1553
|
+
if (resolved.length === 0)
|
|
1554
|
+
return { fixed: [] };
|
|
1555
|
+
const before = new Map(resolved.map((filePath) => [filePath, readFileSync(filePath, "utf8")]));
|
|
1556
|
+
await this.#runBiome([
|
|
1557
|
+
"check",
|
|
1558
|
+
"--write",
|
|
1559
|
+
"--reporter=json",
|
|
1560
|
+
"--max-diagnostics=none",
|
|
1561
|
+
"--no-errors-on-unmatched",
|
|
1562
|
+
"--config-path",
|
|
1563
|
+
this.configPath,
|
|
1564
|
+
...resolved.map((filePath) => this.#toBiomePath(filePath))
|
|
1565
|
+
]);
|
|
1566
|
+
return {
|
|
1567
|
+
fixed: resolved.filter((filePath) => readFileSync(filePath, "utf8") !== before.get(filePath))
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
910
1570
|
async getConfigForFile(filePath) {
|
|
911
1571
|
const resolvedFilePath = this.#resolveFilePath(filePath);
|
|
912
1572
|
if (!existsSync(resolvedFilePath))
|
|
@@ -928,7 +1588,7 @@ ${errorText}, ${warningText} found`];
|
|
|
928
1588
|
|
|
929
1589
|
// pkgs/@akanjs/devkit/repoIdentity.ts
|
|
930
1590
|
import { execFileSync } from "child_process";
|
|
931
|
-
import
|
|
1591
|
+
import path6 from "path";
|
|
932
1592
|
var resolved = new Map;
|
|
933
1593
|
var readRemoteName = (workspaceRoot) => {
|
|
934
1594
|
try {
|
|
@@ -946,14 +1606,14 @@ var resolveRepoName = (workspaceRoot) => {
|
|
|
946
1606
|
const cached = resolved.get(workspaceRoot);
|
|
947
1607
|
if (cached)
|
|
948
1608
|
return cached;
|
|
949
|
-
const repoName = readRemoteName(workspaceRoot) ??
|
|
1609
|
+
const repoName = readRemoteName(workspaceRoot) ?? path6.basename(workspaceRoot);
|
|
950
1610
|
resolved.set(workspaceRoot, repoName);
|
|
951
1611
|
return repoName;
|
|
952
1612
|
};
|
|
953
1613
|
|
|
954
1614
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
955
|
-
import { rm } from "fs/promises";
|
|
956
|
-
import
|
|
1615
|
+
import { rm as rm2 } from "fs/promises";
|
|
1616
|
+
import path7 from "path";
|
|
957
1617
|
|
|
958
1618
|
// pkgs/@akanjs/devkit/workspaceLayout.ts
|
|
959
1619
|
var appRootAllowedFiles = new Set([
|
|
@@ -987,6 +1647,34 @@ var appRootAllowedDirs = new Set([
|
|
|
987
1647
|
"ui",
|
|
988
1648
|
"webkit"
|
|
989
1649
|
]);
|
|
1650
|
+
var libRootAllowedFiles = new Set([
|
|
1651
|
+
"AGENTS.md",
|
|
1652
|
+
"CLAUDE.md",
|
|
1653
|
+
"README.md",
|
|
1654
|
+
"akan.config.ts",
|
|
1655
|
+
"akan.lib.json",
|
|
1656
|
+
"client.ts",
|
|
1657
|
+
"index.ts",
|
|
1658
|
+
"package.json",
|
|
1659
|
+
"server.ts",
|
|
1660
|
+
"tsconfig.json",
|
|
1661
|
+
"tsconfig.spec.json",
|
|
1662
|
+
"tsconfig.tsbuildinfo"
|
|
1663
|
+
]);
|
|
1664
|
+
var libRootAllowedDirs = new Set([
|
|
1665
|
+
"common",
|
|
1666
|
+
"env",
|
|
1667
|
+
"lib",
|
|
1668
|
+
"page",
|
|
1669
|
+
"plugin",
|
|
1670
|
+
"private",
|
|
1671
|
+
"public",
|
|
1672
|
+
"srvkit",
|
|
1673
|
+
"ui",
|
|
1674
|
+
"webkit"
|
|
1675
|
+
]);
|
|
1676
|
+
var rootAllowedFiles = { app: appRootAllowedFiles, lib: libRootAllowedFiles };
|
|
1677
|
+
var rootAllowedDirs = { app: appRootAllowedDirs, lib: libRootAllowedDirs };
|
|
990
1678
|
var libFacetRootAllowedFiles = new Set([
|
|
991
1679
|
"cnst.ts",
|
|
992
1680
|
"db.ts",
|
|
@@ -998,7 +1686,9 @@ var libFacetRootAllowedFiles = new Set([
|
|
|
998
1686
|
"useClient.ts",
|
|
999
1687
|
"useServer.ts"
|
|
1000
1688
|
]);
|
|
1001
|
-
var
|
|
1689
|
+
var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
|
|
1690
|
+
var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
|
|
1691
|
+
var isScannedRootEntry = (type, name) => !name.startsWith(".") || rootAllowedDirs[type].has(name);
|
|
1002
1692
|
|
|
1003
1693
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
1004
1694
|
var databaseFileTypes = [
|
|
@@ -1027,18 +1717,16 @@ var moduleUiFileTypes = {
|
|
|
1027
1717
|
scalar: new Set(["Template", "Unit"])
|
|
1028
1718
|
};
|
|
1029
1719
|
var testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
|
|
1030
|
-
var
|
|
1031
|
-
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-f6grrep3.js")).TypeScriptDependencyScanner.from(exec);
|
|
1720
|
+
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-shs82ynp.js")).TypeScriptDependencyScanner.from(exec);
|
|
1032
1721
|
var isAllowedTestFile = (filename) => testFilePattern.test(filename);
|
|
1033
|
-
var
|
|
1034
|
-
var getScanPath = (exec, relativePath) => path4.posix.join(`${exec.type}s`, exec.name, relativePath.split(path4.sep).join("/"));
|
|
1722
|
+
var getScanPath = (exec, relativePath) => path7.posix.join(`${exec.type}s`, exec.name, relativePath.split(path7.sep).join("/"));
|
|
1035
1723
|
async function clearGeneratedRootCapacitorConfigs(exec) {
|
|
1036
1724
|
if (exec.type !== "app")
|
|
1037
1725
|
return;
|
|
1038
|
-
await Promise.all(generatedRootCapacitorConfigFiles.map((filename) =>
|
|
1726
|
+
await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm2(exec.getPath(filename), { force: true })));
|
|
1039
1727
|
}
|
|
1040
1728
|
var getModuleNameFromPath = (kind, modulePath) => {
|
|
1041
|
-
const dirname =
|
|
1729
|
+
const dirname = path7.basename(modulePath);
|
|
1042
1730
|
return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
|
|
1043
1731
|
};
|
|
1044
1732
|
async function assertScanConvention(exec, libRoot) {
|
|
@@ -1047,28 +1735,31 @@ async function assertScanConvention(exec, libRoot) {
|
|
|
1047
1735
|
const addViolation = (relativePath, reason) => {
|
|
1048
1736
|
violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
|
|
1049
1737
|
};
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
});
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
});
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1738
|
+
const allowedRootFiles = rootAllowedFiles[exec.type];
|
|
1739
|
+
const allowedRootDirs = rootAllowedDirs[exec.type];
|
|
1740
|
+
const { files, dirs } = await exec.getFilesAndDirs(".");
|
|
1741
|
+
files.filter((filename) => !allowedRootFiles.has(filename)).forEach((filename) => {
|
|
1742
|
+
addViolation(filename, `unsupported ${exec.type} root file`);
|
|
1743
|
+
});
|
|
1744
|
+
dirs.filter((dirname) => !allowedRootDirs.has(dirname)).forEach((dirname) => {
|
|
1745
|
+
addViolation(dirname, `unsupported ${exec.type} root folder`);
|
|
1746
|
+
});
|
|
1747
|
+
if (exec.type === "lib")
|
|
1748
|
+
for (const { relativePath, reason } of await exec.getPageConventionViolations())
|
|
1749
|
+
addViolation(relativePath, reason);
|
|
1750
|
+
libRoot.files.filter((filename) => !isAllowedLibFacetRootFile(filename)).forEach((filename) => {
|
|
1751
|
+
addViolation(path7.join("lib", filename), "unsupported lib facet root file");
|
|
1061
1752
|
});
|
|
1062
1753
|
libRoot.dirs.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname)).forEach((dirname) => {
|
|
1063
|
-
addViolation(
|
|
1754
|
+
addViolation(path7.join("lib", dirname), "unsupported internal lib folder");
|
|
1064
1755
|
});
|
|
1065
1756
|
const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
|
|
1066
1757
|
const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
|
|
1067
1758
|
const scalarDirs = await exec.readdir("lib/__scalar");
|
|
1068
1759
|
await Promise.all([
|
|
1069
|
-
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database",
|
|
1070
|
-
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service",
|
|
1071
|
-
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar",
|
|
1760
|
+
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path7.join("lib", dirname))),
|
|
1761
|
+
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path7.join("lib", dirname))),
|
|
1762
|
+
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path7.join("lib/__scalar", dirname)))
|
|
1072
1763
|
]);
|
|
1073
1764
|
if (violations.length > 0) {
|
|
1074
1765
|
throw new Error(`[scan-convention]
|
|
@@ -1080,11 +1771,11 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
|
|
|
1080
1771
|
const { files, dirs } = await exec.getFilesAndDirs(modulePath);
|
|
1081
1772
|
const moduleName = getModuleNameFromPath(kind, modulePath);
|
|
1082
1773
|
dirs.forEach((dirname) => {
|
|
1083
|
-
violations.push(`${getScanPath(exec,
|
|
1774
|
+
violations.push(`${getScanPath(exec, path7.join(modulePath, dirname))}: unsupported module folder`);
|
|
1084
1775
|
});
|
|
1085
1776
|
const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
|
|
1086
1777
|
files.forEach((filename) => {
|
|
1087
|
-
const filePath =
|
|
1778
|
+
const filePath = path7.join(modulePath, filename);
|
|
1088
1779
|
if (filename === "index.ts" || filename === "index.tsx" || isAllowedTestFile(filename))
|
|
1089
1780
|
return;
|
|
1090
1781
|
if (filename === `${moduleName}.abstract.md`)
|
|
@@ -1128,7 +1819,7 @@ class ScanInfo {
|
|
|
1128
1819
|
{ all: new Set, databases: new Set, services: new Set, scalars: new Set }
|
|
1129
1820
|
]));
|
|
1130
1821
|
static async getScanResult(exec) {
|
|
1131
|
-
const [
|
|
1822
|
+
const [akanConfig, scanner, pkgs, libs] = await Promise.all([
|
|
1132
1823
|
exec.getConfig(),
|
|
1133
1824
|
createDependencyScanner(exec),
|
|
1134
1825
|
exec.workspace.getPkgs(),
|
|
@@ -1164,7 +1855,7 @@ class ScanInfo {
|
|
|
1164
1855
|
});
|
|
1165
1856
|
await Promise.all([
|
|
1166
1857
|
...databaseDirs.map(async (name) => {
|
|
1167
|
-
const filenames = await exec.readdir(
|
|
1858
|
+
const filenames = await exec.readdir(path7.join("lib", name));
|
|
1168
1859
|
filenames.forEach((filename) => {
|
|
1169
1860
|
if (filename.endsWith(".constant.ts"))
|
|
1170
1861
|
files.constant.databases.push(name);
|
|
@@ -1192,7 +1883,7 @@ class ScanInfo {
|
|
|
1192
1883
|
}),
|
|
1193
1884
|
...serviceDirs.map(async (dirname) => {
|
|
1194
1885
|
const name = dirname.slice(1);
|
|
1195
|
-
const filenames = await exec.readdir(
|
|
1886
|
+
const filenames = await exec.readdir(path7.join("lib", dirname));
|
|
1196
1887
|
filenames.forEach((filename) => {
|
|
1197
1888
|
if (filename.endsWith(".dictionary.ts"))
|
|
1198
1889
|
files.dictionary.services.push(name);
|
|
@@ -1215,7 +1906,7 @@ class ScanInfo {
|
|
|
1215
1906
|
});
|
|
1216
1907
|
}),
|
|
1217
1908
|
...scalarDirs.map(async (name) => {
|
|
1218
|
-
const filenames = await exec.readdir(
|
|
1909
|
+
const filenames = await exec.readdir(path7.join("lib/__scalar", name));
|
|
1219
1910
|
filenames.forEach((filename) => {
|
|
1220
1911
|
if (filename.endsWith(".constant.ts"))
|
|
1221
1912
|
files.constant.scalars.push(name);
|
|
@@ -1237,12 +1928,11 @@ class ScanInfo {
|
|
|
1237
1928
|
})
|
|
1238
1929
|
]);
|
|
1239
1930
|
const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
|
|
1240
|
-
const
|
|
1931
|
+
const common = {
|
|
1241
1932
|
name: exec.name,
|
|
1242
1933
|
type: exec.type,
|
|
1243
1934
|
repoName: exec.workspace.repoName,
|
|
1244
|
-
serveDomain: WorkspaceExecutor.getBaseDevEnv(
|
|
1245
|
-
akanConfig: akanConfig2,
|
|
1935
|
+
serveDomain: WorkspaceExecutor.getBaseDevEnv(path7.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
|
|
1246
1936
|
files,
|
|
1247
1937
|
libDeps,
|
|
1248
1938
|
pkgDeps,
|
|
@@ -1250,6 +1940,7 @@ class ScanInfo {
|
|
|
1250
1940
|
devDependencies: npmDevDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
|
|
1251
1941
|
routes
|
|
1252
1942
|
};
|
|
1943
|
+
const scanResult = akanConfig instanceof AkanAppConfig ? { ...common, akanConfig } : { ...common, akanConfig };
|
|
1253
1944
|
return scanResult;
|
|
1254
1945
|
}
|
|
1255
1946
|
constructor(scanResult) {
|
|
@@ -1443,16 +2134,16 @@ class LibInfo extends ScanInfo {
|
|
|
1443
2134
|
class PkgInfo {
|
|
1444
2135
|
exec;
|
|
1445
2136
|
name;
|
|
1446
|
-
scanResult;
|
|
2137
|
+
#scanResult;
|
|
1447
2138
|
static async scanExecutor(exec) {
|
|
1448
2139
|
const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
|
|
1449
2140
|
const scanner = await createDependencyScanner(exec);
|
|
1450
2141
|
const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
|
|
1451
|
-
const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((
|
|
2142
|
+
const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path) => tsconfig.compilerOptions.paths?.[path]?.some((resolve) => resolve.startsWith("pkgs/"))).map((path) => path.replace("/*", "")));
|
|
1452
2143
|
const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
|
|
1453
|
-
const pkgDeps = [...pkgPathDepSet].map((
|
|
1454
|
-
const pathSplitLength =
|
|
1455
|
-
return (tsconfig.compilerOptions.paths?.[
|
|
2144
|
+
const pkgDeps = [...pkgPathDepSet].map((path) => {
|
|
2145
|
+
const pathSplitLength = path.split("/").length;
|
|
2146
|
+
return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
|
|
1456
2147
|
}).filter((pkg) => pkg !== exec.name);
|
|
1457
2148
|
const pkgScanResult = {
|
|
1458
2149
|
name: exec.name,
|
|
@@ -1474,10 +2165,10 @@ class PkgInfo {
|
|
|
1474
2165
|
constructor(exec, scanResult) {
|
|
1475
2166
|
this.exec = exec;
|
|
1476
2167
|
this.name = exec.name;
|
|
1477
|
-
this
|
|
2168
|
+
this.#scanResult = scanResult;
|
|
1478
2169
|
}
|
|
1479
2170
|
getScanResult() {
|
|
1480
|
-
return this
|
|
2171
|
+
return this.#scanResult;
|
|
1481
2172
|
}
|
|
1482
2173
|
}
|
|
1483
2174
|
|
|
@@ -1638,16 +2329,16 @@ class Executor {
|
|
|
1638
2329
|
linter = null;
|
|
1639
2330
|
constructor(name, cwdPath) {
|
|
1640
2331
|
this.name = name;
|
|
1641
|
-
this.logger = new
|
|
2332
|
+
this.logger = new Logger2(name);
|
|
1642
2333
|
this.logs = [];
|
|
1643
2334
|
this.cwdPath = cwdPath;
|
|
1644
2335
|
}
|
|
1645
2336
|
#stdout(data) {
|
|
1646
2337
|
if (Executor.verbose)
|
|
1647
|
-
|
|
2338
|
+
Logger2.raw(chalk2.dim(data.toString()));
|
|
1648
2339
|
}
|
|
1649
2340
|
#stderr(data) {
|
|
1650
|
-
|
|
2341
|
+
Logger2.raw(chalk2.red(data.toString()));
|
|
1651
2342
|
}
|
|
1652
2343
|
exec(command, options = {}) {
|
|
1653
2344
|
const cwd = options.cwd?.toString() ?? this.cwdPath;
|
|
@@ -1675,7 +2366,7 @@ class Executor {
|
|
|
1675
2366
|
}));
|
|
1676
2367
|
});
|
|
1677
2368
|
proc.on("exit", (code, signal) => {
|
|
1678
|
-
if (
|
|
2369
|
+
if (code || signal)
|
|
1679
2370
|
reject(new CommandExecutionError({
|
|
1680
2371
|
command,
|
|
1681
2372
|
cwd,
|
|
@@ -1773,7 +2464,7 @@ class Executor {
|
|
|
1773
2464
|
}));
|
|
1774
2465
|
});
|
|
1775
2466
|
proc.on("exit", (code, signal) => {
|
|
1776
|
-
if (
|
|
2467
|
+
if (code || signal)
|
|
1777
2468
|
reject(new CommandExecutionError({
|
|
1778
2469
|
command: modulePath,
|
|
1779
2470
|
args,
|
|
@@ -1789,10 +2480,10 @@ class Executor {
|
|
|
1789
2480
|
});
|
|
1790
2481
|
}
|
|
1791
2482
|
getPath(filePath) {
|
|
1792
|
-
if (
|
|
2483
|
+
if (path8.isAbsolute(filePath))
|
|
1793
2484
|
return filePath;
|
|
1794
2485
|
if (filePath.startsWith("."))
|
|
1795
|
-
return
|
|
2486
|
+
return path8.join(this.cwdPath, filePath);
|
|
1796
2487
|
const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
|
|
1797
2488
|
const targetParts = filePath.split(/[\\/]/).filter(Boolean);
|
|
1798
2489
|
let overlapLength = 0;
|
|
@@ -1806,12 +2497,12 @@ class Executor {
|
|
|
1806
2497
|
if (isOverlap)
|
|
1807
2498
|
overlapLength = i;
|
|
1808
2499
|
}
|
|
1809
|
-
return
|
|
2500
|
+
return path8.join(this.cwdPath, ...targetParts.slice(overlapLength));
|
|
1810
2501
|
}
|
|
1811
2502
|
async mkdir(dirPath) {
|
|
1812
2503
|
const writePath = this.getPath(dirPath);
|
|
1813
2504
|
if (!await FileSys.dirExists(writePath))
|
|
1814
|
-
await
|
|
2505
|
+
await mkdir3(writePath, { recursive: true });
|
|
1815
2506
|
this.logger.verbose(`Make directory ${writePath}`);
|
|
1816
2507
|
return this;
|
|
1817
2508
|
}
|
|
@@ -1849,18 +2540,19 @@ class Executor {
|
|
|
1849
2540
|
return this;
|
|
1850
2541
|
}
|
|
1851
2542
|
async removeDir(dirPath) {
|
|
1852
|
-
const readPath =
|
|
2543
|
+
const readPath = path8.join(this.getPath(dirPath), ".");
|
|
1853
2544
|
if (await FileSys.entryExists(readPath))
|
|
1854
|
-
await
|
|
2545
|
+
await rm3(readPath, { recursive: true, force: true });
|
|
1855
2546
|
this.logger.verbose(`Remove directory ${readPath}`);
|
|
1856
2547
|
return this;
|
|
1857
2548
|
}
|
|
1858
2549
|
async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
|
|
1859
2550
|
const writePath = this.getPath(filePath);
|
|
1860
|
-
const dir =
|
|
2551
|
+
const dir = path8.dirname(writePath);
|
|
1861
2552
|
if (!await FileSys.dirExists(dir))
|
|
1862
|
-
await
|
|
1863
|
-
let contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2)
|
|
2553
|
+
await mkdir3(dir, { recursive: true });
|
|
2554
|
+
let contentStr = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}
|
|
2555
|
+
`;
|
|
1864
2556
|
if (await FileSys.fileExists(writePath)) {
|
|
1865
2557
|
const currentContent = await FileSys.readText(writePath);
|
|
1866
2558
|
if (currentContent === contentStr || !overwrite) {
|
|
@@ -1868,7 +2560,7 @@ class Executor {
|
|
|
1868
2560
|
contentStr = currentContent;
|
|
1869
2561
|
} else {
|
|
1870
2562
|
await FileSys.writeText(writePath, contentStr);
|
|
1871
|
-
if (
|
|
2563
|
+
if (Logger2.isVerbose())
|
|
1872
2564
|
this.logger.rawLog(chalk2.yellow(`File Update: ${filePath}`));
|
|
1873
2565
|
}
|
|
1874
2566
|
} else {
|
|
@@ -1882,7 +2574,7 @@ class Executor {
|
|
|
1882
2574
|
await this.writeFile(filePath, content);
|
|
1883
2575
|
}
|
|
1884
2576
|
async getLocalFile(targetPath) {
|
|
1885
|
-
const filePath =
|
|
2577
|
+
const filePath = path8.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
|
|
1886
2578
|
const content = await this.readFile(filePath);
|
|
1887
2579
|
return { filePath, content };
|
|
1888
2580
|
}
|
|
@@ -1899,9 +2591,9 @@ class Executor {
|
|
|
1899
2591
|
const dest = this.getPath(destPath);
|
|
1900
2592
|
if (!await FileSys.exists(src))
|
|
1901
2593
|
return;
|
|
1902
|
-
const isDirectory = (await
|
|
2594
|
+
const isDirectory = (await stat2(src)).isDirectory();
|
|
1903
2595
|
if (!await FileSys.exists(dest) && isDirectory)
|
|
1904
|
-
await
|
|
2596
|
+
await mkdir3(dest, { recursive: true });
|
|
1905
2597
|
if (dereference)
|
|
1906
2598
|
await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
|
|
1907
2599
|
else
|
|
@@ -1979,26 +2671,26 @@ class Executor {
|
|
|
1979
2671
|
const result = await getContent.default(scanInfo ?? null, dict, options);
|
|
1980
2672
|
if (result === null)
|
|
1981
2673
|
return null;
|
|
1982
|
-
const filename = typeof result === "object" ? result.filename :
|
|
2674
|
+
const filename = typeof result === "object" ? result.filename : path8.basename(targetPath).replace(".js", ".ts");
|
|
1983
2675
|
const content = typeof result === "object" ? result.content : result;
|
|
1984
|
-
const dirname =
|
|
1985
|
-
const convertedTargetPath = Object.entries(dict).reduce((
|
|
2676
|
+
const dirname = path8.dirname(targetPath);
|
|
2677
|
+
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), `${dirname}/${filename}`);
|
|
1986
2678
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1987
2679
|
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
1988
2680
|
} else if (targetPath.endsWith(".template")) {
|
|
1989
2681
|
const content = await FileSys.readText(templatePath);
|
|
1990
|
-
const convertedTargetPath = Object.entries(dict).reduce((
|
|
2682
|
+
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
|
|
1991
2683
|
const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
|
|
1992
2684
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1993
2685
|
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
1994
2686
|
overwrite
|
|
1995
2687
|
});
|
|
1996
|
-
} else if (staticTemplateFileExtensions.has(
|
|
1997
|
-
const convertedTargetPath = Object.entries(dict).reduce((
|
|
2688
|
+
} else if (staticTemplateFileExtensions.has(path8.extname(targetPath).toLowerCase())) {
|
|
2689
|
+
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
|
|
1998
2690
|
const writePath = this.getPath(convertedTargetPath);
|
|
1999
|
-
const dirname =
|
|
2691
|
+
const dirname = path8.dirname(writePath);
|
|
2000
2692
|
if (!await FileSys.dirExists(dirname))
|
|
2001
|
-
await
|
|
2693
|
+
await mkdir3(dirname, { recursive: true });
|
|
2002
2694
|
await copyFile(templatePath, writePath);
|
|
2003
2695
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2004
2696
|
return { filePath: writePath, content: "" };
|
|
@@ -2016,11 +2708,11 @@ class Executor {
|
|
|
2016
2708
|
const templateRoot = await this.#resolveTemplateRoot();
|
|
2017
2709
|
const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
|
|
2018
2710
|
const prefixTemplatePath = templatePath;
|
|
2019
|
-
if ((await
|
|
2020
|
-
const filename =
|
|
2711
|
+
if ((await stat2(prefixTemplatePath)).isFile()) {
|
|
2712
|
+
const filename = path8.basename(prefixTemplatePath);
|
|
2021
2713
|
const fileContent = await this.#applyTemplateFile({
|
|
2022
2714
|
templatePath: prefixTemplatePath,
|
|
2023
|
-
targetPath:
|
|
2715
|
+
targetPath: path8.join(basePath, filename),
|
|
2024
2716
|
scanInfo,
|
|
2025
2717
|
overwrite
|
|
2026
2718
|
}, dict, options);
|
|
@@ -2028,19 +2720,19 @@ class Executor {
|
|
|
2028
2720
|
} else {
|
|
2029
2721
|
const subdirs = await readDirEntries(templatePath);
|
|
2030
2722
|
const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
|
|
2031
|
-
const subpath =
|
|
2032
|
-
if ((await
|
|
2723
|
+
const subpath = path8.join(templatePath, subdir);
|
|
2724
|
+
if ((await stat2(subpath)).isFile()) {
|
|
2033
2725
|
const fileContent = await this.#applyTemplateFile({
|
|
2034
2726
|
templatePath: subpath,
|
|
2035
|
-
targetPath:
|
|
2727
|
+
targetPath: path8.join(basePath, subdir),
|
|
2036
2728
|
scanInfo,
|
|
2037
2729
|
overwrite
|
|
2038
2730
|
}, dict, options);
|
|
2039
2731
|
return fileContent ? [fileContent] : [];
|
|
2040
2732
|
} else
|
|
2041
2733
|
return await this._applyTemplate({
|
|
2042
|
-
basePath:
|
|
2043
|
-
template:
|
|
2734
|
+
basePath: path8.join(basePath, subdir),
|
|
2735
|
+
template: path8.join(template, subdir),
|
|
2044
2736
|
scanInfo,
|
|
2045
2737
|
dict,
|
|
2046
2738
|
overwrite,
|
|
@@ -2052,8 +2744,8 @@ class Executor {
|
|
|
2052
2744
|
}
|
|
2053
2745
|
async#resolveTemplateRoot() {
|
|
2054
2746
|
const candidates = [
|
|
2055
|
-
|
|
2056
|
-
|
|
2747
|
+
path8.resolve(getDirname2(import.meta.url), "templates"),
|
|
2748
|
+
path8.resolve(getDirname2(import.meta.url), "../cli/templates")
|
|
2057
2749
|
];
|
|
2058
2750
|
for (const candidate of candidates) {
|
|
2059
2751
|
if (await FileSys.dirExists(candidate))
|
|
@@ -2066,29 +2758,43 @@ class Executor {
|
|
|
2066
2758
|
...options.dict ?? {},
|
|
2067
2759
|
...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
|
|
2068
2760
|
};
|
|
2069
|
-
|
|
2761
|
+
const fileContents = await this._applyTemplate({ ...options, dict });
|
|
2762
|
+
await this.#formatAppliedTemplate(fileContents);
|
|
2763
|
+
return fileContents;
|
|
2764
|
+
}
|
|
2765
|
+
async#formatAppliedTemplate(fileContents) {
|
|
2766
|
+
const filePaths = fileContents.map((fileContent) => fileContent.filePath).filter((filePath) => filePath.endsWith(".ts") || filePath.endsWith(".tsx"));
|
|
2767
|
+
if (filePaths.length === 0)
|
|
2768
|
+
return;
|
|
2769
|
+
try {
|
|
2770
|
+
const { fixed } = await this.getLinter().fixFiles(filePaths);
|
|
2771
|
+
if (fixed.length > 0)
|
|
2772
|
+
this.logger.verbose(`Formatted ${fixed.length} scaffolded file(s)`);
|
|
2773
|
+
} catch (err) {
|
|
2774
|
+
this.logger.verbose(`Skipped formatting scaffolded files: ${err instanceof Error ? err.message : String(err)}`);
|
|
2775
|
+
}
|
|
2070
2776
|
}
|
|
2071
2777
|
async getTypeChecker() {
|
|
2072
|
-
const { TypeChecker } = await import("./typeChecker-
|
|
2778
|
+
const { TypeChecker } = await import("./typeChecker-3xgrzc2v.js");
|
|
2073
2779
|
this.typeChecker ??= new TypeChecker(this);
|
|
2074
2780
|
return this.typeChecker;
|
|
2075
2781
|
}
|
|
2076
2782
|
async typeCheck(filePath) {
|
|
2077
|
-
const
|
|
2783
|
+
const path = this.getPath(filePath);
|
|
2078
2784
|
const typeChecker = await this.getTypeChecker();
|
|
2079
|
-
const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(
|
|
2785
|
+
const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path);
|
|
2080
2786
|
const message = typeChecker.formatDiagnostics(fileDiagnostics);
|
|
2081
2787
|
return { fileDiagnostics, fileErrors, fileWarnings, message };
|
|
2082
2788
|
}
|
|
2083
2789
|
async typeCheckAsync(filePath) {
|
|
2084
|
-
const
|
|
2790
|
+
const path = this.getPath(filePath);
|
|
2085
2791
|
const entry = await this.#resolveTypecheckWorkerEntry();
|
|
2086
2792
|
const proc = Bun.spawn([process.execPath, entry], {
|
|
2087
2793
|
cwd: this.cwdPath,
|
|
2088
2794
|
env: {
|
|
2089
2795
|
...process.env,
|
|
2090
2796
|
AKAN_TYPECHECK_CWD: this.cwdPath,
|
|
2091
|
-
AKAN_TYPECHECK_FILE:
|
|
2797
|
+
AKAN_TYPECHECK_FILE: path
|
|
2092
2798
|
},
|
|
2093
2799
|
stdout: "pipe",
|
|
2094
2800
|
stderr: "pipe"
|
|
@@ -2109,13 +2815,13 @@ class Executor {
|
|
|
2109
2815
|
};
|
|
2110
2816
|
}
|
|
2111
2817
|
async#resolveTypecheckWorkerEntry() {
|
|
2112
|
-
const dirname =
|
|
2818
|
+
const dirname = getDirname2(import.meta.url);
|
|
2113
2819
|
const candidates = [
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2820
|
+
path8.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2821
|
+
path8.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2822
|
+
path8.join(dirname, "typecheck/typecheck.proc.ts"),
|
|
2823
|
+
path8.join(dirname, "typecheck.proc.js"),
|
|
2824
|
+
path8.join(dirname, "typecheck.proc.ts")
|
|
2119
2825
|
];
|
|
2120
2826
|
for (const candidate of candidates)
|
|
2121
2827
|
if (await Bun.file(candidate).exists())
|
|
@@ -2127,9 +2833,9 @@ class Executor {
|
|
|
2127
2833
|
return this.linter;
|
|
2128
2834
|
}
|
|
2129
2835
|
async lint(filePath, { fix = false, dryRun = false } = {}) {
|
|
2130
|
-
const
|
|
2836
|
+
const path = this.getPath(filePath);
|
|
2131
2837
|
const linter = this.getLinter();
|
|
2132
|
-
const { results, errors, warnings } = await linter.lint(
|
|
2838
|
+
const { results, errors, warnings } = await linter.lint(path, {
|
|
2133
2839
|
fix,
|
|
2134
2840
|
dryRun
|
|
2135
2841
|
});
|
|
@@ -2242,8 +2948,8 @@ class WorkspaceExecutor extends Executor {
|
|
|
2242
2948
|
await Promise.all(dirs.map(async (dir) => {
|
|
2243
2949
|
if (dir.includes("_") || AVOID_DIRS.includes(dir))
|
|
2244
2950
|
return;
|
|
2245
|
-
const dirPath =
|
|
2246
|
-
if ((await
|
|
2951
|
+
const dirPath = path8.join(dirname, dir);
|
|
2952
|
+
if ((await stat2(dirPath)).isDirectory()) {
|
|
2247
2953
|
results.push(`${prefix}${dir}`);
|
|
2248
2954
|
if (maxDepth > 0)
|
|
2249
2955
|
await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
|
|
@@ -2255,10 +2961,20 @@ class WorkspaceExecutor extends Executor {
|
|
|
2255
2961
|
}
|
|
2256
2962
|
async commit(message, { init = false, add = true } = {}) {
|
|
2257
2963
|
if (init)
|
|
2258
|
-
await this.
|
|
2964
|
+
await this.spawn("git", ["init", "--quiet"]);
|
|
2259
2965
|
if (add)
|
|
2260
|
-
await this.
|
|
2261
|
-
await this.
|
|
2966
|
+
await this.spawn("git", ["add", "."]);
|
|
2967
|
+
await this.spawn("git", ["commit", "--quiet", "-m", message]);
|
|
2968
|
+
}
|
|
2969
|
+
async hasChanges() {
|
|
2970
|
+
return !!(await this.spawn("git", ["status", "--porcelain"])).trim();
|
|
2971
|
+
}
|
|
2972
|
+
async listGitFiles(paths, { untracked = false } = {}) {
|
|
2973
|
+
if (!paths.length)
|
|
2974
|
+
return [];
|
|
2975
|
+
const mode = untracked ? ["--cached", "--others", "--exclude-standard"] : ["--cached"];
|
|
2976
|
+
const stdout = await this.spawn("git", ["ls-files", "-z", ...mode, "--", ...paths]);
|
|
2977
|
+
return stdout.split("\x00").filter((file) => !!file).sort();
|
|
2262
2978
|
}
|
|
2263
2979
|
async#getDirHasFile(basePath, targetFilename) {
|
|
2264
2980
|
const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
|
|
@@ -2267,9 +2983,9 @@ class WorkspaceExecutor extends Executor {
|
|
|
2267
2983
|
await Promise.all(dirs.map(async (dir) => {
|
|
2268
2984
|
if (AVOID_DIRS.includes(dir))
|
|
2269
2985
|
return;
|
|
2270
|
-
const dirPath =
|
|
2986
|
+
const dirPath = path8.join(dirname, dir);
|
|
2271
2987
|
if (await FileSys.dirExists(dirPath)) {
|
|
2272
|
-
const hasTargetFile = await FileSys.fileExists(
|
|
2988
|
+
const hasTargetFile = await FileSys.fileExists(path8.join(dirPath, targetFilename));
|
|
2273
2989
|
if (hasTargetFile)
|
|
2274
2990
|
results.push(`${prefix}${dir}`);
|
|
2275
2991
|
if (maxDepth > 0)
|
|
@@ -2335,8 +3051,20 @@ class SysExecutor extends Executor {
|
|
|
2335
3051
|
return this.#akanConfig;
|
|
2336
3052
|
}
|
|
2337
3053
|
async getModules() {
|
|
2338
|
-
const
|
|
2339
|
-
return await this.workspace.getDirInModule(
|
|
3054
|
+
const path = this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
|
|
3055
|
+
return await this.workspace.getDirInModule(path, this.name);
|
|
3056
|
+
}
|
|
3057
|
+
async getPageConventionViolations() {
|
|
3058
|
+
if (!await this.exists("page"))
|
|
3059
|
+
return [];
|
|
3060
|
+
const violations = [];
|
|
3061
|
+
for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
|
|
3062
|
+
const relativePath = entry.split(path8.sep).join("/");
|
|
3063
|
+
const reason = getPageSourceFileViolation(relativePath);
|
|
3064
|
+
if (reason)
|
|
3065
|
+
violations.push({ relativePath: `page/${relativePath}`, reason });
|
|
3066
|
+
}
|
|
3067
|
+
return violations;
|
|
2340
3068
|
}
|
|
2341
3069
|
#scanInfo = null;
|
|
2342
3070
|
hasScanInfo() {
|
|
@@ -2394,18 +3122,19 @@ class SysExecutor extends Executor {
|
|
|
2394
3122
|
}) : await LibInfo.fromExecutor(this, {
|
|
2395
3123
|
refresh
|
|
2396
3124
|
});
|
|
2397
|
-
if (write)
|
|
2398
|
-
await
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
this
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
3125
|
+
if (write)
|
|
3126
|
+
await CodegenLock.run(this.workspace.workspaceRoot, `scan:${this.name}`, async () => {
|
|
3127
|
+
await Promise.all(this.#getScanTemplateTasks(scanInfo));
|
|
3128
|
+
await this.writeJson(`akan.${this.type}.json`, scanInfo.getScanResult());
|
|
3129
|
+
if (this.type === "lib")
|
|
3130
|
+
this.#updateDependencies(scanInfo);
|
|
3131
|
+
if (writeLib) {
|
|
3132
|
+
const libInfos = [...scanInfo.getLibInfos().values()];
|
|
3133
|
+
await this.#updateDependencies(scanInfo);
|
|
3134
|
+
await Promise.all(libInfos.flatMap((libInfo) => libInfo.exec.#getScanTemplateTasks(libInfo)));
|
|
3135
|
+
}
|
|
3136
|
+
await this.syncAgentsIndex(scanInfo);
|
|
3137
|
+
});
|
|
2409
3138
|
this.#scanInfo = scanInfo;
|
|
2410
3139
|
return scanInfo;
|
|
2411
3140
|
}
|
|
@@ -2440,7 +3169,7 @@ class SysExecutor extends Executor {
|
|
|
2440
3169
|
await this.setPackageJson(libPkgJsonWithDeps);
|
|
2441
3170
|
}
|
|
2442
3171
|
async getLocalFile(targetPath) {
|
|
2443
|
-
const filePath =
|
|
3172
|
+
const filePath = path8.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
|
|
2444
3173
|
const content = await this.workspace.readFile(filePath);
|
|
2445
3174
|
return { filePath, content };
|
|
2446
3175
|
}
|
|
@@ -2526,7 +3255,7 @@ class SysExecutor extends Executor {
|
|
|
2526
3255
|
class AppExecutor extends SysExecutor {
|
|
2527
3256
|
static #routeSourceValidator = null;
|
|
2528
3257
|
static async#getRouteSourceValidator() {
|
|
2529
|
-
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-
|
|
3258
|
+
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-apyqw7wk.js")).RouteSourceValidator;
|
|
2530
3259
|
return AppExecutor.#routeSourceValidator;
|
|
2531
3260
|
}
|
|
2532
3261
|
dist;
|
|
@@ -2537,9 +3266,9 @@ class AppExecutor extends SysExecutor {
|
|
|
2537
3266
|
}
|
|
2538
3267
|
static #execs = new Map;
|
|
2539
3268
|
static from(executor, name) {
|
|
2540
|
-
const
|
|
2541
|
-
if (
|
|
2542
|
-
return
|
|
3269
|
+
const exec = AppExecutor.#execs.get(name);
|
|
3270
|
+
if (exec)
|
|
3271
|
+
return exec;
|
|
2543
3272
|
else if (executor instanceof WorkspaceExecutor)
|
|
2544
3273
|
return new AppExecutor({ workspace: executor, name });
|
|
2545
3274
|
else
|
|
@@ -2575,33 +3304,43 @@ class AppExecutor extends SysExecutor {
|
|
|
2575
3304
|
};
|
|
2576
3305
|
}
|
|
2577
3306
|
async prepareCommand(type) {
|
|
2578
|
-
const
|
|
2579
|
-
const databaseMode = process.env.AKAN_DATABASE_MODE ??
|
|
3307
|
+
const akanConfig = await this.getConfig();
|
|
3308
|
+
const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
|
|
2580
3309
|
const routeEnv = {
|
|
2581
|
-
AKAN_PUBLIC_BASE_PATHS: [...
|
|
3310
|
+
AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
|
|
3311
|
+
AKAN_PUBLIC_API_PREFIX: akanConfig.api.prefix,
|
|
3312
|
+
AKAN_PUBLIC_WS_PREFIX: akanConfig.api.websocketPrefix,
|
|
2582
3313
|
AKAN_DATABASE_MODE: databaseMode
|
|
2583
3314
|
};
|
|
2584
3315
|
Object.assign(process.env, routeEnv);
|
|
2585
3316
|
if (type === "build") {
|
|
2586
3317
|
this.#excludeDevOnlyPages = true;
|
|
2587
3318
|
this.#pageKeys = null;
|
|
2588
|
-
|
|
2589
|
-
await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
|
|
3319
|
+
await this.dist.removeDir(this.dist.cwdPath);
|
|
2590
3320
|
await Promise.all([this.dist.mkdir("private"), this.dist.mkdir("public")]);
|
|
2591
3321
|
await Promise.all([
|
|
2592
3322
|
this.cp("private", `${this.dist.cwdPath}/private`, { dereference: true }),
|
|
2593
|
-
this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })
|
|
3323
|
+
...akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []
|
|
2594
3324
|
]);
|
|
2595
|
-
} else
|
|
3325
|
+
} else {
|
|
2596
3326
|
await this.removeDir(".akan");
|
|
3327
|
+
if (!akanConfig.web.ssr || !akanConfig.web.csr)
|
|
3328
|
+
this.logger.verbose(`akan.config.ts disables web.${!akanConfig.web.ssr ? "ssr" : "csr"}; \`akan start\` still serves it, \`akan build\` will not`);
|
|
3329
|
+
}
|
|
3330
|
+
if (type === "start")
|
|
3331
|
+
process.env.NODE_ENV = "development";
|
|
2597
3332
|
const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
|
|
2598
3333
|
const env = this.getCommandEnv({
|
|
2599
3334
|
AKAN_COMMAND_TYPE: type,
|
|
2600
3335
|
...routeEnv,
|
|
2601
3336
|
...devPort ? { PORT: devPort, AKAN_PUBLIC_CLIENT_PORT: devPort, AKAN_PUBLIC_SERVER_PORT: devPort } : {}
|
|
2602
3337
|
});
|
|
2603
|
-
if (type === "build")
|
|
2604
|
-
|
|
3338
|
+
if (type === "build") {
|
|
3339
|
+
const buildEnv = { ...env };
|
|
3340
|
+
delete buildEnv.AKAN_PUBLIC_CLIENT_PORT;
|
|
3341
|
+
delete buildEnv.AKAN_PUBLIC_SERVER_PORT;
|
|
3342
|
+
Object.assign(process.env, buildEnv);
|
|
3343
|
+
}
|
|
2605
3344
|
return { env };
|
|
2606
3345
|
}
|
|
2607
3346
|
#publicEnv = null;
|
|
@@ -2641,7 +3380,7 @@ class AppExecutor extends SysExecutor {
|
|
|
2641
3380
|
async getPageKeys({ refresh } = {}) {
|
|
2642
3381
|
if (this.#pageKeys && !refresh)
|
|
2643
3382
|
return this.#pageKeys;
|
|
2644
|
-
const
|
|
3383
|
+
const akanConfig = await this.getConfig();
|
|
2645
3384
|
const glob = new Bun.Glob("**/*");
|
|
2646
3385
|
const pageKeys = [];
|
|
2647
3386
|
const owners = new Map;
|
|
@@ -2655,16 +3394,16 @@ class AppExecutor extends SysExecutor {
|
|
|
2655
3394
|
absolute: false,
|
|
2656
3395
|
onlyFiles: true
|
|
2657
3396
|
})) {
|
|
2658
|
-
const segments = rel.split(
|
|
3397
|
+
const segments = rel.split(path8.sep);
|
|
2659
3398
|
if (segments.some((s) => s === "node_modules"))
|
|
2660
3399
|
continue;
|
|
2661
3400
|
const posix = `${root.keyPrefix}${segments.join("/")}`;
|
|
2662
|
-
const absPath =
|
|
3401
|
+
const absPath = path8.join(root.dir, ...segments);
|
|
2663
3402
|
validatePageSourceFile(posix, { filePath: absPath });
|
|
2664
3403
|
if (!isRouteSourceFile(posix))
|
|
2665
3404
|
continue;
|
|
2666
3405
|
const key = `./${posix}`;
|
|
2667
|
-
validateSubRoutePageKey(key,
|
|
3406
|
+
validateSubRoutePageKey(key, akanConfig.basePaths, {
|
|
2668
3407
|
appName: this.name,
|
|
2669
3408
|
filePath: absPath
|
|
2670
3409
|
});
|
|
@@ -2682,14 +3421,16 @@ class AppExecutor extends SysExecutor {
|
|
|
2682
3421
|
}
|
|
2683
3422
|
if (!owner)
|
|
2684
3423
|
owners.set(routeId, { absPath, fromLib });
|
|
2685
|
-
const
|
|
3424
|
+
const layoutSegments = getRootBoundarySegments(key);
|
|
3425
|
+
const isRootLayout = layoutSegments !== null && isRootBoundarySegments(layoutSegments, akanConfig.basePaths);
|
|
2686
3426
|
const routeSource = await Bun.file(absPath).text();
|
|
2687
3427
|
const validator = await AppExecutor.#getRouteSourceValidator();
|
|
2688
3428
|
if (parsed.kind === "overrides")
|
|
2689
3429
|
validator.validateOverridesSourceExports(routeSource, absPath);
|
|
2690
3430
|
else {
|
|
2691
3431
|
const info = validator.validateRouteSourceExports(routeSource, absPath, parsed.kind, {
|
|
2692
|
-
rootLayout: isRootLayout
|
|
3432
|
+
rootLayout: isRootLayout,
|
|
3433
|
+
pattern: parsed.pattern
|
|
2693
3434
|
});
|
|
2694
3435
|
if (info.devOnly) {
|
|
2695
3436
|
devOnlyKeys.add(key);
|
|
@@ -2713,10 +3454,10 @@ class AppExecutor extends SysExecutor {
|
|
|
2713
3454
|
return pageKeys.filter((key) => !isDevOnly(key));
|
|
2714
3455
|
}
|
|
2715
3456
|
async getPageRoots() {
|
|
2716
|
-
const
|
|
3457
|
+
const akanConfig = await this.getConfig();
|
|
2717
3458
|
const pageDir = `${this.cwdPath}/page`;
|
|
2718
3459
|
const roots = [{ dir: pageDir, realDir: pageDir, keyPrefix: "" }];
|
|
2719
|
-
for (const parent of AppExecutor.#pageLibParents(
|
|
3460
|
+
for (const parent of AppExecutor.#pageLibParents(akanConfig.basePaths)) {
|
|
2720
3461
|
const libsDir = `${pageDir}/${parent}${AppExecutor.#pageLibsDir}`;
|
|
2721
3462
|
const entries = await readDirEntries(libsDir).catch(() => []);
|
|
2722
3463
|
for (const entry of entries.sort()) {
|
|
@@ -2737,9 +3478,9 @@ class AppExecutor extends SysExecutor {
|
|
|
2737
3478
|
return parents.length ? parents : [""];
|
|
2738
3479
|
}
|
|
2739
3480
|
async syncPages(libDeps) {
|
|
2740
|
-
const
|
|
2741
|
-
const parents = AppExecutor.#pageLibParents(
|
|
2742
|
-
const libs = await this.#resolvePageLibs(
|
|
3481
|
+
const akanConfig = await this.getConfig();
|
|
3482
|
+
const parents = AppExecutor.#pageLibParents(akanConfig.basePaths);
|
|
3483
|
+
const libs = await this.#resolvePageLibs(akanConfig.syncPageLibs, libDeps);
|
|
2743
3484
|
const linked = (await Promise.all(parents.map(async (parent) => {
|
|
2744
3485
|
const libsDir = `${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`;
|
|
2745
3486
|
const entries = await readDirEntries(libsDir).catch(() => []);
|
|
@@ -2797,12 +3538,12 @@ class AppExecutor extends SysExecutor {
|
|
|
2797
3538
|
static async#linkLibAsset(targetPath, linkPath) {
|
|
2798
3539
|
const isWindows = process.platform === "win32";
|
|
2799
3540
|
try {
|
|
2800
|
-
const target = isWindows ? targetPath :
|
|
3541
|
+
const target = isWindows ? targetPath : path8.relative(path8.dirname(linkPath), targetPath);
|
|
2801
3542
|
await symlink(target, linkPath, isWindows ? "junction" : "dir");
|
|
2802
3543
|
} catch (error) {
|
|
2803
3544
|
if (!isWindows)
|
|
2804
3545
|
throw error;
|
|
2805
|
-
await
|
|
3546
|
+
await mkdir3(linkPath, { recursive: true });
|
|
2806
3547
|
await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
|
|
2807
3548
|
}
|
|
2808
3549
|
}
|
|
@@ -2839,7 +3580,7 @@ class AppExecutor extends SysExecutor {
|
|
|
2839
3580
|
await this.writeFile(rel, content, opts);
|
|
2840
3581
|
},
|
|
2841
3582
|
readEnvClient: async () => {
|
|
2842
|
-
const envClientPath =
|
|
3583
|
+
const envClientPath = path8.join(this.cwdPath, "env", "env.client.ts");
|
|
2843
3584
|
if (!await FileSys.fileExists(envClientPath))
|
|
2844
3585
|
return null;
|
|
2845
3586
|
try {
|
|
@@ -2898,9 +3639,9 @@ class LibExecutor extends SysExecutor {
|
|
|
2898
3639
|
}
|
|
2899
3640
|
static #execs = new Map;
|
|
2900
3641
|
static from(executor, name) {
|
|
2901
|
-
const
|
|
2902
|
-
if (
|
|
2903
|
-
return
|
|
3642
|
+
const exec = LibExecutor.#execs.get(name);
|
|
3643
|
+
if (exec)
|
|
3644
|
+
return exec;
|
|
2904
3645
|
else if (executor instanceof WorkspaceExecutor)
|
|
2905
3646
|
return new LibExecutor({ workspace: executor, name });
|
|
2906
3647
|
else
|
|
@@ -2910,7 +3651,7 @@ class LibExecutor extends SysExecutor {
|
|
|
2910
3651
|
async getConfig({ refresh } = {}) {
|
|
2911
3652
|
if (this.#akanConfig && !refresh)
|
|
2912
3653
|
return this.#akanConfig;
|
|
2913
|
-
this.#akanConfig = await AkanLibConfig.from(this);
|
|
3654
|
+
this.#akanConfig = await AkanLibConfig.from(this, { bustImportCache: refresh });
|
|
2914
3655
|
return this.#akanConfig;
|
|
2915
3656
|
}
|
|
2916
3657
|
}
|
|
@@ -2949,7 +3690,7 @@ class PkgExecutor extends Executor {
|
|
|
2949
3690
|
return rootVersion;
|
|
2950
3691
|
try {
|
|
2951
3692
|
const packageJsonPath = `pkgs/${dep}/package.json`;
|
|
2952
|
-
if (!await Bun.file(
|
|
3693
|
+
if (!await Bun.file(path8.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
|
|
2953
3694
|
return;
|
|
2954
3695
|
const packageJson = await this.workspace.readJson(packageJsonPath);
|
|
2955
3696
|
if (packageJson.name === dep)
|
|
@@ -3007,7 +3748,7 @@ class PkgExecutor extends Executor {
|
|
|
3007
3748
|
default: "./index.ts"
|
|
3008
3749
|
}
|
|
3009
3750
|
},
|
|
3010
|
-
engines: { bun: ">=1.
|
|
3751
|
+
engines: { bun: ">=1.4.0" },
|
|
3011
3752
|
...dependencyMaps
|
|
3012
3753
|
};
|
|
3013
3754
|
await Promise.all([this.dist.writeJson("package.json", distPkgJson), this.writeJson("package.json", distPkgJson)]);
|
|
@@ -3050,4 +3791,4 @@ class ModuleExecutor extends Executor {
|
|
|
3050
3791
|
}
|
|
3051
3792
|
}
|
|
3052
3793
|
|
|
3053
|
-
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock,
|
|
3794
|
+
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, isScannedRootEntry, AppInfo, Spinner, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
|