@akanjs/cli 3.0.0-alpha.9 → 3.0.0-alpha.90
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-cefxzggj.js} +3 -5
- package/{agent.command-f0gqkfgz.js → agent.command-r93wmq7m.js} +11 -13
- package/{application.command-x903m2qq.js → application.command-5nqfqgdn.js} +62 -38
- package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-b21m07fs.js} +23 -20
- package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-3wh58n2m.js} +8 -9
- package/buildBatch.proc.js +12 -14
- package/capacitorApp-gzcx6txb.js +12 -0
- package/{cloud.command-zs6kyf83.js → cloud.command-g0pk0k60.js} +15 -18
- package/commandManifest.json +1 -1
- package/{context.command-fmepgjzg.js → context.command-qd8kqrnh.js} +19 -23
- package/dependencyScanner-shs82ynp.js +8 -0
- package/devTui-2xf0meaq.js +552 -0
- package/devTuiView-7jy8f30g.js +9 -0
- package/getDirname-dr3jz1xg.js +7 -0
- package/{guideline.command-n6sw0a0c.js → guideline.command-f38x8jhw.js} +16 -18
- package/guidelines/agentRule/agentRule.generate.json +31 -0
- package/guidelines/agentRule/agentRule.instruction.md +533 -0
- package/guidelines/conventions/conventions.instruction.md +406 -197
- package/guidelines/cssRule/cssRule.instruction.md +7 -0
- package/guidelines/framework/framework.generate.json +3 -2
- package/guidelines/framework/framework.instruction.md +1 -9
- package/guidelines/mcpRule/mcpRule.generate.json +25 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +217 -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 +143 -0
- package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
- package/guidelines/runtimeRule/runtimeRule.instruction.md +213 -0
- package/guidelines/ssrRule/ssrRule.instruction.md +49 -5
- package/guidelines/transportRule/transportRule.generate.json +31 -0
- package/guidelines/transportRule/transportRule.instruction.md +60 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +51 -760
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
- package/incrementalBuilder.proc.js +26 -25
- package/{index-774gcx29.js → index-0emb9eat.js} +41 -34
- package/index-3d9y066c.js +76 -0
- package/{index-3vta1tqa.js → index-3vvbv974.js} +32 -32
- package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
- package/{index-y4vkyhvv.js → index-5xdqxbn9.js} +14 -14
- package/{index-1577bej2.js → index-6wwzs1bc.js} +2 -2
- package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
- package/{index-jhpa13zj.js → index-9bwhxrqf.js} +57 -57
- package/{index-bjpxzr6s.js → index-9wtbw98m.js} +6 -3
- package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
- package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
- package/{index-kdh76j26.js → index-cezy7yb3.js} +100 -32
- package/{index-62955kn5.js → index-d61rcfa2.js} +758 -127
- package/{index-82jrh4hj.js → index-deekbtkj.js} +13 -13
- package/{index-rxkpk7n4.js → index-e82dyeqy.js} +4 -4
- package/{index-qzdjd4ct.js → index-h759vjfa.js} +18 -18
- package/{index-j37qq1f2.js → index-h7yw2z9n.js} +7 -7
- package/{index-qb61h0k7.js → index-jydtyzva.js} +1098 -1360
- package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
- package/{index-4m0bphac.js → index-kfnr6c19.js} +31 -30
- package/{index-xmmzf150.js → index-mpmyqm1y.js} +1 -1
- package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
- package/{index-0cj2zxbm.js → index-n85fer99.js} +12 -11
- package/{index-779ske1y.js → index-pgsswppj.js} +27 -27
- package/index-pvb4vdwr.js +159 -0
- package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
- package/{index-z8ptfkzv.js → index-q8sn827n.js} +969 -240
- package/{index-5sn5w2mt.js → index-r50qen0z.js} +94 -61
- package/{index-eyyf2149.js → index-rntcdss1.js} +36 -37
- package/{index-gcv8ncfh.js → index-sjak5v5b.js} +16 -12
- package/{index-j9cxndc0.js → index-w8kmx1b6.js} +20 -11
- package/{index-jf2ya7r2.js → index-y74k5anj.js} +8 -8
- package/{index-d43t8af2.js → index-zkvpaggp.js} +58 -25
- package/index.js +22 -25
- package/{library.command-cv9gdbmg.js → library.command-em103wtn.js} +11 -10
- package/{localRegistry.command-d8fg36zc.js → localRegistry.command-gz8apn4y.js} +20 -23
- package/{module.command-qyr1s9cn.js → module.command-451pztkc.js} +13 -16
- package/{package.command-wsjx0k47.js → package.command-rfhyf38t.js} +8 -10
- package/package.json +5 -7
- package/{page.command-rdzg65rm.js → page.command-kt19da5g.js} +7 -9
- package/{primitive.command-89g38b53.js → primitive.command-pgy69ere.js} +13 -16
- package/{quality.command-0cs2z92d.js → quality.command-gpe2vqb5.js} +226 -105
- package/recipeScanner-fpagtgc1.js +11 -0
- package/{repair.command-6fphxfwj.js → repair.command-2sbrz187.js} +12 -14
- package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-ze6x894c.js} +0 -2
- package/{scalar.command-xfer69s6.js → scalar.command-jcf4e5sv.js} +11 -14
- package/subspace.command-kqzqtk0p.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/appSample/lib/task/task.signal.ts +11 -6
- package/templates/appSample/srvkit/AuthGuard.ts +9 -5
- package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
- package/templates/crudPages/[__model__Id]/edit/page.tsx +5 -5
- package/templates/crudPages/[__model__Id]/page.tsx +10 -13
- package/templates/crudPages/new/page.tsx +5 -5
- package/templates/crudPages/page.tsx +9 -9
- package/templates/crudSinglePage/page.tsx +11 -7
- 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 +1 -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 +2 -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-2ygfqt4f.js → workflow.command-m4z25a2p.js} +22 -25
- package/{workspace.command-88m0zs64.js → workspace.command-dqgj9zbk.js} +96 -81
- package/capacitorApp-9xswcgx7.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,49 +882,448 @@ 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.isIdentifier(statement.expression))
|
|
951
|
+
defaultIdentifier = statement.expression.text;
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
955
|
+
const exportClause = statement.exportClause;
|
|
956
|
+
for (const specifier of exportClause.elements) {
|
|
957
|
+
if (specifier.name.text !== "default")
|
|
958
|
+
continue;
|
|
959
|
+
defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
|
|
964
|
+
}
|
|
965
|
+
#hasModifier(node, kind) {
|
|
966
|
+
const ts = this.#ts;
|
|
967
|
+
return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
|
|
968
|
+
}
|
|
969
|
+
#isAsyncFunctionExpression(node) {
|
|
970
|
+
const ts = this.#ts;
|
|
971
|
+
return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
|
|
972
|
+
}
|
|
973
|
+
static #scriptKind(typescript, moduleAbsPath) {
|
|
974
|
+
return moduleAbsPath.endsWith(".tsx") || moduleAbsPath.endsWith(".jsx") ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
979
|
+
var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
980
|
+
async function appHasStModule(appCwdPath) {
|
|
981
|
+
return Bun.file(path4.join(appCwdPath, "lib", "st.ts")).exists();
|
|
982
|
+
}
|
|
983
|
+
var IMPLICIT_LAYOUT_DIR = path4.join(".akan", "generated", "root-layouts");
|
|
984
|
+
var IMPLICIT_DICT_DIR = path4.join(".akan", "generated", "dict");
|
|
985
|
+
var IMPLICIT_OVERRIDES_DIR = path4.join(".akan", "generated", "overrides");
|
|
986
|
+
var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
987
|
+
async function writeGeneratedOverridesLayoutFile(opts) {
|
|
988
|
+
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
989
|
+
const absPath = path4.join(path4.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
990
|
+
const userRel = path4.relative(path4.dirname(absPath), opts.userAbsPath).split(path4.sep).join("/");
|
|
991
|
+
const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
|
|
992
|
+
const source = `"use client";
|
|
993
|
+
import { UiOverrideProvider } from "akanjs/ui";
|
|
994
|
+
import { createElement, type ReactNode } from "react";
|
|
995
|
+
import value from ${JSON.stringify(userSpecifier)};
|
|
996
|
+
|
|
997
|
+
export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
|
|
998
|
+
return createElement(UiOverrideProvider, { value }, children);
|
|
999
|
+
}
|
|
1000
|
+
`;
|
|
1001
|
+
await Bun.write(absPath, source);
|
|
1002
|
+
return absPath;
|
|
1003
|
+
}
|
|
1004
|
+
function getRootBoundarySegments(key) {
|
|
1005
|
+
const match = LAYOUT_KEY_RE.exec(key);
|
|
1006
|
+
if (!match)
|
|
1007
|
+
return null;
|
|
1008
|
+
const prefix = match[1]?.replace(/\/$/, "");
|
|
1009
|
+
if (!prefix)
|
|
1010
|
+
return [];
|
|
1011
|
+
return prefix.split("/").filter(Boolean);
|
|
1012
|
+
}
|
|
1013
|
+
function implicitRootLayoutKey(segments) {
|
|
1014
|
+
return `./${[...segments, "__root_layout"].join("/")}.tsx`;
|
|
1015
|
+
}
|
|
1016
|
+
function implicitRootLayoutAbsPath(appCwdPath, segments) {
|
|
1017
|
+
const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
|
|
1018
|
+
return path4.join(path4.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
1019
|
+
}
|
|
1020
|
+
function implicitDictionaryMacroAbsPath(appCwdPath) {
|
|
1021
|
+
return path4.join(path4.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
1022
|
+
}
|
|
1023
|
+
function isRootBoundarySegments(segments, basePaths) {
|
|
1024
|
+
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
1025
|
+
if (firstVisibleIndex === -1)
|
|
1026
|
+
return segments.length <= 1;
|
|
1027
|
+
if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
|
|
1028
|
+
return false;
|
|
1029
|
+
const visible = segments.slice(firstVisibleIndex);
|
|
1030
|
+
const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
|
|
1031
|
+
return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
|
|
1032
|
+
}
|
|
1033
|
+
function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
|
|
1034
|
+
const boundaries = new Map;
|
|
1035
|
+
for (const key of pageKeys) {
|
|
1036
|
+
const segments = getRootBoundarySegments(key);
|
|
1037
|
+
if (!segments)
|
|
1038
|
+
continue;
|
|
1039
|
+
if (!isRootBoundarySegments(segments, basePaths))
|
|
1040
|
+
continue;
|
|
1041
|
+
const id = segments.join("/");
|
|
1042
|
+
boundaries.set(id, {
|
|
1043
|
+
sourceKey: key,
|
|
1044
|
+
sourceAbsPath: path4.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
|
|
1045
|
+
segments
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
const hasExplicitRootBoundary = [...boundaries.values()].some((boundary) => boundary.segments.length === 0);
|
|
1049
|
+
if (!hasExplicitRootBoundary && boundaries.size === 0) {
|
|
1050
|
+
boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
1051
|
+
}
|
|
1052
|
+
return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
|
|
1053
|
+
}
|
|
1054
|
+
function hasAncestorRootBoundary(boundary, boundaries) {
|
|
1055
|
+
return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
|
|
1056
|
+
}
|
|
1057
|
+
function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
|
|
1058
|
+
const rootLayoutKey = pageKeys.find((key) => {
|
|
1059
|
+
const segments = getRootBoundarySegments(key);
|
|
1060
|
+
return segments !== null && segments.length === 0;
|
|
1061
|
+
});
|
|
1062
|
+
return rootLayoutKey ? path4.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
1063
|
+
}
|
|
1064
|
+
function routePrefixForSegments(segments) {
|
|
1065
|
+
const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
|
|
1066
|
+
return visible[0] ?? null;
|
|
1067
|
+
}
|
|
1068
|
+
async function assertEnvClientConvention(appCwdPath, appName) {
|
|
1069
|
+
const envPath = path4.join(appCwdPath, "env", "env.client.ts");
|
|
1070
|
+
if (!await Bun.file(envPath).exists()) {
|
|
1071
|
+
throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
|
|
1075
|
+
const absPath = implicitDictionaryMacroAbsPath(appCwdPath);
|
|
1076
|
+
await mkdir2(path4.dirname(absPath), { recursive: true });
|
|
1077
|
+
await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
1078
|
+
|
|
1079
|
+
export const allDictionary = getAllDictionary();
|
|
1080
|
+
`);
|
|
1081
|
+
return absPath;
|
|
1082
|
+
}
|
|
1083
|
+
async function writeGeneratedRootLayoutFile(opts) {
|
|
1084
|
+
await assertEnvClientConvention(opts.appCwdPath, opts.appName);
|
|
1085
|
+
const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
|
|
1086
|
+
const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
|
|
1087
|
+
await mkdir2(path4.dirname(absPath), { recursive: true });
|
|
1088
|
+
const dictMacroRel = dictMacroAbsPath ? path4.relative(path4.dirname(absPath), dictMacroAbsPath).split(path4.sep).join("/") : null;
|
|
1089
|
+
const dictMacroSpecifier = dictMacroRel ? dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}` : null;
|
|
1090
|
+
const sourceRel = opts.boundary.sourceAbsPath ? path4.relative(path4.dirname(absPath), opts.boundary.sourceAbsPath).split(path4.sep).join("/") : null;
|
|
1091
|
+
const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
|
|
1092
|
+
const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
|
|
1093
|
+
const inheritedSourceRel = inheritedSourceAbsPath ? path4.relative(path4.dirname(absPath), inheritedSourceAbsPath).split(path4.sep).join("/") : null;
|
|
1094
|
+
const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
|
|
1095
|
+
const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
|
|
1096
|
+
void st;
|
|
1097
|
+
` : `import "@apps/${opts.appName}/client";
|
|
1098
|
+
`;
|
|
1099
|
+
const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedLayout from ${JSON.stringify(inheritedSourceSpecifier)};
|
|
1100
|
+
` : `const inheritedLayout = {};
|
|
1101
|
+
`;
|
|
1102
|
+
const prefix = routePrefixForSegments(opts.boundary.segments);
|
|
1103
|
+
const userImport = sourceSpecifier ? `import UserLayout, * as userLayout from ${JSON.stringify(sourceSpecifier)};
|
|
1104
|
+
` : `const UserLayout = ({ children }) => children;
|
|
1105
|
+
const userLayout = {};
|
|
1106
|
+
`;
|
|
1107
|
+
const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
|
|
1108
|
+
const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
|
|
1109
|
+
const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
|
|
1110
|
+
const layoutBinding = isAsyncUserLayout ? ` const layout =
|
|
1111
|
+
process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
|
|
1112
|
+
? await UserLayout({ params, searchParams, children })
|
|
1113
|
+
: ${userLayoutElement};
|
|
1114
|
+
` : "";
|
|
1115
|
+
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
1116
|
+
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
1117
|
+
const source = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
|
|
1118
|
+
import { loadFonts } from "akanjs/client";
|
|
1119
|
+
import { System } from "akanjs/ui";
|
|
1120
|
+
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
1121
|
+
import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
|
|
1122
|
+
${clientImport}${inheritedImport}${userImport}
|
|
1123
|
+
const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
|
|
1124
|
+
const defaultFonts = userFonts.filter((font) => font.default);
|
|
1125
|
+
if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
|
|
1126
|
+
const defaultFont = defaultFonts[0];
|
|
1127
|
+
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
1128
|
+
|
|
1129
|
+
export async function generateHead(props: PageProps) {
|
|
1130
|
+
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
1131
|
+
if (userLayout.head !== undefined) return userLayout.head;
|
|
1132
|
+
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
1133
|
+
return inheritedLayout.head;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
export async function generateMetadata(props: PageProps) {
|
|
1137
|
+
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
1138
|
+
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
1139
|
+
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
1140
|
+
return inheritedLayout.metadata;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
1144
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
1145
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
1146
|
+
|
|
1147
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
1148
|
+
${layoutBinding} return (
|
|
1149
|
+
<System.Provider
|
|
1150
|
+
of={GeneratedLayout as never}
|
|
1151
|
+
appName=${JSON.stringify(opts.appName)}
|
|
1152
|
+
${prefix ? `prefix=${JSON.stringify(prefix)}
|
|
1153
|
+
` : ""}params={params}
|
|
1154
|
+
manifest={userLayout.manifest ?? inheritedLayout.manifest}
|
|
1155
|
+
env={env}
|
|
1156
|
+
theme={userLayout.theme ?? inheritedLayout.theme}
|
|
1157
|
+
fonts={loadFonts(userFonts)}
|
|
1158
|
+
className={defaultFontClassName}
|
|
1159
|
+
gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
|
|
1160
|
+
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
1161
|
+
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
1162
|
+
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
|
|
1163
|
+
allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
|
|
1164
|
+
>
|
|
1165
|
+
${layoutChild}
|
|
1166
|
+
</System.Provider>
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
` : `import type { LayoutProps, PageProps } from "akanjs/client";
|
|
1170
|
+
${inheritedImport}${userImport}
|
|
1171
|
+
export async function generateHead(props: PageProps) {
|
|
1172
|
+
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
1173
|
+
if (userLayout.head !== undefined) return userLayout.head;
|
|
1174
|
+
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
1175
|
+
return inheritedLayout.head;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
export async function generateMetadata(props: PageProps) {
|
|
1179
|
+
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
1180
|
+
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
1181
|
+
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
1182
|
+
return inheritedLayout.metadata;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
1186
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
1187
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
1188
|
+
|
|
1189
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
1190
|
+
${layoutBinding} return ${layoutReturn};
|
|
1191
|
+
}
|
|
1192
|
+
`;
|
|
1193
|
+
await Bun.write(absPath, source);
|
|
1194
|
+
return absPath;
|
|
1195
|
+
}
|
|
1196
|
+
async function resolveSsrPageEntries(opts) {
|
|
1197
|
+
const absPageDir = path4.resolve(opts.appCwdPath, "page");
|
|
1198
|
+
const hasSt = await appHasStModule(opts.appCwdPath);
|
|
1199
|
+
const basePaths = opts.basePaths ?? [];
|
|
1200
|
+
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
1201
|
+
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
1202
|
+
const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
|
|
1203
|
+
const segments = getRootBoundarySegments(key);
|
|
1204
|
+
return segments !== null && isRootBoundarySegments(segments, basePaths);
|
|
1205
|
+
}));
|
|
1206
|
+
const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
|
|
1207
|
+
const userAbsPath = path4.resolve(absPageDir, key);
|
|
1208
|
+
if (OVERRIDES_KEY_RE.test(key)) {
|
|
1209
|
+
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
1210
|
+
appCwdPath: opts.appCwdPath,
|
|
1211
|
+
key,
|
|
1212
|
+
userAbsPath
|
|
1213
|
+
});
|
|
1214
|
+
return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
|
|
1215
|
+
}
|
|
1216
|
+
return { key, moduleAbsPath: userAbsPath };
|
|
1217
|
+
}));
|
|
1218
|
+
const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
|
|
1219
|
+
key: implicitRootLayoutKey(boundary.segments),
|
|
1220
|
+
moduleAbsPath: await writeGeneratedRootLayoutFile({
|
|
1221
|
+
appCwdPath: opts.appCwdPath,
|
|
1222
|
+
appName: opts.appName,
|
|
1223
|
+
boundary,
|
|
1224
|
+
rootSourceAbsPath,
|
|
1225
|
+
includeStInit: hasSt && boundary.segments.length === 0,
|
|
1226
|
+
includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
|
|
1227
|
+
}),
|
|
1228
|
+
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
|
|
1229
|
+
})));
|
|
1230
|
+
const entries = [...base, ...generated];
|
|
1231
|
+
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
1232
|
+
return entries;
|
|
1233
|
+
}
|
|
1234
|
+
async function resolveSsrPageEntriesForApp(app, pageKeys) {
|
|
1235
|
+
const config = await app.getConfig();
|
|
1236
|
+
return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
|
|
1237
|
+
}
|
|
1238
|
+
|
|
648
1239
|
// pkgs/@akanjs/devkit/linter.ts
|
|
649
1240
|
import { spawn } from "child_process";
|
|
650
1241
|
import { existsSync, readFileSync } from "fs";
|
|
651
|
-
import
|
|
1242
|
+
import path5 from "path";
|
|
652
1243
|
import chalk from "chalk";
|
|
1244
|
+
var parseBiomeReport = (output) => {
|
|
1245
|
+
for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
|
|
1246
|
+
const end = objectEndAt(output, start);
|
|
1247
|
+
if (end === -1)
|
|
1248
|
+
break;
|
|
1249
|
+
const report = asBiomeReport(output.slice(start, end + 1));
|
|
1250
|
+
if (report)
|
|
1251
|
+
return report;
|
|
1252
|
+
}
|
|
1253
|
+
throw new Error(output.trim() || "No Biome JSON output");
|
|
1254
|
+
};
|
|
1255
|
+
var objectEndAt = (output, start) => {
|
|
1256
|
+
let depth = 0;
|
|
1257
|
+
let inString = false;
|
|
1258
|
+
let escaped = false;
|
|
1259
|
+
for (let idx = start;idx < output.length; idx += 1) {
|
|
1260
|
+
const char = output[idx];
|
|
1261
|
+
if (inString) {
|
|
1262
|
+
if (escaped)
|
|
1263
|
+
escaped = false;
|
|
1264
|
+
else if (char === "\\")
|
|
1265
|
+
escaped = true;
|
|
1266
|
+
else if (char === '"')
|
|
1267
|
+
inString = false;
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
if (char === '"')
|
|
1271
|
+
inString = true;
|
|
1272
|
+
else if (char === "{")
|
|
1273
|
+
depth += 1;
|
|
1274
|
+
else if (char === "}" && --depth === 0)
|
|
1275
|
+
return idx;
|
|
1276
|
+
}
|
|
1277
|
+
return -1;
|
|
1278
|
+
};
|
|
1279
|
+
var asBiomeReport = (candidate) => {
|
|
1280
|
+
try {
|
|
1281
|
+
const parsed = JSON.parse(candidate);
|
|
1282
|
+
if (!parsed || typeof parsed !== "object")
|
|
1283
|
+
return null;
|
|
1284
|
+
if (!("diagnostics" in parsed) && !("summary" in parsed))
|
|
1285
|
+
return null;
|
|
1286
|
+
return parsed;
|
|
1287
|
+
} catch {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1291
|
+
var BIOME_CONFIG_FILES = ["biome.json", "biome.jsonc"];
|
|
653
1292
|
|
|
654
1293
|
class Linter {
|
|
655
1294
|
lintRoot;
|
|
1295
|
+
configPath;
|
|
656
1296
|
#biomeBin;
|
|
657
1297
|
constructor(cwdPath) {
|
|
658
1298
|
this.lintRoot = this.#findBiomeRootPath(cwdPath);
|
|
659
|
-
|
|
1299
|
+
this.configPath = Linter.#configPathIn(this.lintRoot) ?? path5.join(this.lintRoot, "biome.json");
|
|
1300
|
+
const localBiomeBin = path5.join(this.lintRoot, "node_modules/.bin/biome");
|
|
660
1301
|
this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
|
|
661
1302
|
}
|
|
1303
|
+
static #configPathIn(dir) {
|
|
1304
|
+
for (const fileName of BIOME_CONFIG_FILES) {
|
|
1305
|
+
const configPath = path5.join(dir, fileName);
|
|
1306
|
+
if (existsSync(configPath))
|
|
1307
|
+
return configPath;
|
|
1308
|
+
}
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
662
1311
|
#findBiomeRootPath(dir) {
|
|
663
|
-
|
|
664
|
-
if (existsSync(configPath))
|
|
1312
|
+
if (Linter.#configPathIn(dir))
|
|
665
1313
|
return dir;
|
|
666
|
-
const parentDir =
|
|
1314
|
+
const parentDir = path5.dirname(dir);
|
|
667
1315
|
if (parentDir === dir)
|
|
668
1316
|
throw new Error(`biome.json not found from ${dir}`);
|
|
669
1317
|
return this.#findBiomeRootPath(parentDir);
|
|
670
1318
|
}
|
|
671
1319
|
#toBiomePath(filePath) {
|
|
672
|
-
const relativePath =
|
|
673
|
-
if (!relativePath.startsWith("..") && !
|
|
1320
|
+
const relativePath = path5.relative(this.lintRoot, filePath);
|
|
1321
|
+
if (!relativePath.startsWith("..") && !path5.isAbsolute(relativePath))
|
|
674
1322
|
return relativePath;
|
|
675
1323
|
return filePath;
|
|
676
1324
|
}
|
|
677
1325
|
#resolveFilePath(filePath) {
|
|
678
|
-
return
|
|
1326
|
+
return path5.isAbsolute(filePath) ? filePath : path5.join(this.lintRoot, filePath);
|
|
679
1327
|
}
|
|
680
1328
|
async#runBiome(args, input) {
|
|
681
1329
|
return await new Promise((resolve, reject) => {
|
|
@@ -696,18 +1344,11 @@ class Linter {
|
|
|
696
1344
|
proc.stdin.end(input);
|
|
697
1345
|
});
|
|
698
1346
|
}
|
|
699
|
-
#parseBiomeReport(output) {
|
|
700
|
-
const jsonStart = output.indexOf("{");
|
|
701
|
-
const jsonEnd = output.lastIndexOf("}");
|
|
702
|
-
if (jsonStart === -1 || jsonEnd === -1 || jsonEnd < jsonStart)
|
|
703
|
-
throw new Error(output.trim() || "No Biome JSON output");
|
|
704
|
-
return JSON.parse(output.slice(jsonStart, jsonEnd + 1));
|
|
705
|
-
}
|
|
706
1347
|
#diagnosticFilePath(diagnostic, fallbackFilePath) {
|
|
707
1348
|
const diagnosticPath = diagnostic.location?.path;
|
|
708
1349
|
if (!diagnosticPath)
|
|
709
1350
|
return fallbackFilePath;
|
|
710
|
-
return
|
|
1351
|
+
return path5.isAbsolute(diagnosticPath) ? diagnosticPath : path5.join(this.lintRoot, diagnosticPath);
|
|
711
1352
|
}
|
|
712
1353
|
#createLintMessage(diagnostic) {
|
|
713
1354
|
const start = diagnostic.location?.start;
|
|
@@ -772,10 +1413,10 @@ class Linter {
|
|
|
772
1413
|
"--max-diagnostics=none",
|
|
773
1414
|
"--no-errors-on-unmatched",
|
|
774
1415
|
"--config-path",
|
|
775
|
-
|
|
1416
|
+
this.configPath,
|
|
776
1417
|
this.#toBiomePath(filePath)
|
|
777
1418
|
]);
|
|
778
|
-
const report =
|
|
1419
|
+
const report = parseBiomeReport(stdout || stderr);
|
|
779
1420
|
const results = this.#toLintResults(report, filePath);
|
|
780
1421
|
const { errors, warnings } = this.#splitMessages(results);
|
|
781
1422
|
const output = write && existsSync(filePath) ? readFileSync(filePath, "utf8") : undefined;
|
|
@@ -893,22 +1534,34 @@ ${errorText}, ${warningText} found`];
|
|
|
893
1534
|
if (!dryRun)
|
|
894
1535
|
return await this.#checkFile(resolvedFilePath, { write: true });
|
|
895
1536
|
const source = readFileSync(resolvedFilePath, "utf8");
|
|
896
|
-
const { stdout } = await this.#runBiome([
|
|
1537
|
+
const { stdout } = await this.#runBiome(["check", "--write", "--config-path", this.configPath, "--stdin-file-path", this.#toBiomePath(resolvedFilePath)], source);
|
|
1538
|
+
const lintResult = await this.lintFile(resolvedFilePath);
|
|
1539
|
+
return { ...lintResult, fixed: stdout !== source, output: stdout };
|
|
1540
|
+
}
|
|
1541
|
+
async fixFiles(filePaths) {
|
|
1542
|
+
const resolved = filePaths.map((filePath) => this.#resolveFilePath(filePath)).filter(existsSync);
|
|
1543
|
+
if (resolved.length === 0)
|
|
1544
|
+
return { fixed: [] };
|
|
1545
|
+
const before = new Map(resolved.map((filePath) => [filePath, readFileSync(filePath, "utf8")]));
|
|
1546
|
+
await this.#runBiome([
|
|
897
1547
|
"check",
|
|
898
1548
|
"--write",
|
|
1549
|
+
"--reporter=json",
|
|
1550
|
+
"--max-diagnostics=none",
|
|
1551
|
+
"--no-errors-on-unmatched",
|
|
899
1552
|
"--config-path",
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1553
|
+
this.configPath,
|
|
1554
|
+
...resolved.map((filePath) => this.#toBiomePath(filePath))
|
|
1555
|
+
]);
|
|
1556
|
+
return {
|
|
1557
|
+
fixed: resolved.filter((filePath) => readFileSync(filePath, "utf8") !== before.get(filePath))
|
|
1558
|
+
};
|
|
906
1559
|
}
|
|
907
1560
|
async getConfigForFile(filePath) {
|
|
908
1561
|
const resolvedFilePath = this.#resolveFilePath(filePath);
|
|
909
1562
|
if (!existsSync(resolvedFilePath))
|
|
910
1563
|
throw new Error(`File not found: ${filePath}`);
|
|
911
|
-
return JSON.parse(readFileSync(
|
|
1564
|
+
return JSON.parse(readFileSync(this.configPath, "utf8"));
|
|
912
1565
|
}
|
|
913
1566
|
async getProblematicRules(filePath) {
|
|
914
1567
|
const { results } = await this.lintFile(filePath);
|
|
@@ -925,7 +1578,7 @@ ${errorText}, ${warningText} found`];
|
|
|
925
1578
|
|
|
926
1579
|
// pkgs/@akanjs/devkit/repoIdentity.ts
|
|
927
1580
|
import { execFileSync } from "child_process";
|
|
928
|
-
import
|
|
1581
|
+
import path6 from "path";
|
|
929
1582
|
var resolved = new Map;
|
|
930
1583
|
var readRemoteName = (workspaceRoot) => {
|
|
931
1584
|
try {
|
|
@@ -943,14 +1596,14 @@ var resolveRepoName = (workspaceRoot) => {
|
|
|
943
1596
|
const cached = resolved.get(workspaceRoot);
|
|
944
1597
|
if (cached)
|
|
945
1598
|
return cached;
|
|
946
|
-
const repoName = readRemoteName(workspaceRoot) ??
|
|
1599
|
+
const repoName = readRemoteName(workspaceRoot) ?? path6.basename(workspaceRoot);
|
|
947
1600
|
resolved.set(workspaceRoot, repoName);
|
|
948
1601
|
return repoName;
|
|
949
1602
|
};
|
|
950
1603
|
|
|
951
1604
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
952
|
-
import { rm } from "fs/promises";
|
|
953
|
-
import
|
|
1605
|
+
import { rm as rm2 } from "fs/promises";
|
|
1606
|
+
import path7 from "path";
|
|
954
1607
|
|
|
955
1608
|
// pkgs/@akanjs/devkit/workspaceLayout.ts
|
|
956
1609
|
var appRootAllowedFiles = new Set([
|
|
@@ -984,6 +1637,34 @@ var appRootAllowedDirs = new Set([
|
|
|
984
1637
|
"ui",
|
|
985
1638
|
"webkit"
|
|
986
1639
|
]);
|
|
1640
|
+
var libRootAllowedFiles = new Set([
|
|
1641
|
+
"AGENTS.md",
|
|
1642
|
+
"CLAUDE.md",
|
|
1643
|
+
"README.md",
|
|
1644
|
+
"akan.config.ts",
|
|
1645
|
+
"akan.lib.json",
|
|
1646
|
+
"client.ts",
|
|
1647
|
+
"index.ts",
|
|
1648
|
+
"package.json",
|
|
1649
|
+
"server.ts",
|
|
1650
|
+
"tsconfig.json",
|
|
1651
|
+
"tsconfig.spec.json",
|
|
1652
|
+
"tsconfig.tsbuildinfo"
|
|
1653
|
+
]);
|
|
1654
|
+
var libRootAllowedDirs = new Set([
|
|
1655
|
+
"common",
|
|
1656
|
+
"env",
|
|
1657
|
+
"lib",
|
|
1658
|
+
"page",
|
|
1659
|
+
"plugin",
|
|
1660
|
+
"private",
|
|
1661
|
+
"public",
|
|
1662
|
+
"srvkit",
|
|
1663
|
+
"ui",
|
|
1664
|
+
"webkit"
|
|
1665
|
+
]);
|
|
1666
|
+
var rootAllowedFiles = { app: appRootAllowedFiles, lib: libRootAllowedFiles };
|
|
1667
|
+
var rootAllowedDirs = { app: appRootAllowedDirs, lib: libRootAllowedDirs };
|
|
987
1668
|
var libFacetRootAllowedFiles = new Set([
|
|
988
1669
|
"cnst.ts",
|
|
989
1670
|
"db.ts",
|
|
@@ -995,7 +1676,9 @@ var libFacetRootAllowedFiles = new Set([
|
|
|
995
1676
|
"useClient.ts",
|
|
996
1677
|
"useServer.ts"
|
|
997
1678
|
]);
|
|
998
|
-
var
|
|
1679
|
+
var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
|
|
1680
|
+
var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
|
|
1681
|
+
var isScannedRootEntry = (type, name) => !name.startsWith(".") || rootAllowedDirs[type].has(name);
|
|
999
1682
|
|
|
1000
1683
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
1001
1684
|
var databaseFileTypes = [
|
|
@@ -1024,18 +1707,16 @@ var moduleUiFileTypes = {
|
|
|
1024
1707
|
scalar: new Set(["Template", "Unit"])
|
|
1025
1708
|
};
|
|
1026
1709
|
var testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
|
|
1027
|
-
var
|
|
1028
|
-
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-f6grrep3.js")).TypeScriptDependencyScanner.from(exec);
|
|
1710
|
+
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-shs82ynp.js")).TypeScriptDependencyScanner.from(exec);
|
|
1029
1711
|
var isAllowedTestFile = (filename) => testFilePattern.test(filename);
|
|
1030
|
-
var
|
|
1031
|
-
var getScanPath = (exec, relativePath) => path4.posix.join(`${exec.type}s`, exec.name, relativePath.split(path4.sep).join("/"));
|
|
1712
|
+
var getScanPath = (exec, relativePath) => path7.posix.join(`${exec.type}s`, exec.name, relativePath.split(path7.sep).join("/"));
|
|
1032
1713
|
async function clearGeneratedRootCapacitorConfigs(exec) {
|
|
1033
1714
|
if (exec.type !== "app")
|
|
1034
1715
|
return;
|
|
1035
|
-
await Promise.all(generatedRootCapacitorConfigFiles.map((filename) =>
|
|
1716
|
+
await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm2(exec.getPath(filename), { force: true })));
|
|
1036
1717
|
}
|
|
1037
1718
|
var getModuleNameFromPath = (kind, modulePath) => {
|
|
1038
|
-
const dirname =
|
|
1719
|
+
const dirname = path7.basename(modulePath);
|
|
1039
1720
|
return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
|
|
1040
1721
|
};
|
|
1041
1722
|
async function assertScanConvention(exec, libRoot) {
|
|
@@ -1044,28 +1725,31 @@ async function assertScanConvention(exec, libRoot) {
|
|
|
1044
1725
|
const addViolation = (relativePath, reason) => {
|
|
1045
1726
|
violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
|
|
1046
1727
|
};
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1728
|
+
const allowedRootFiles = rootAllowedFiles[exec.type];
|
|
1729
|
+
const allowedRootDirs = rootAllowedDirs[exec.type];
|
|
1730
|
+
const { files, dirs } = await exec.getFilesAndDirs(".");
|
|
1731
|
+
files.filter((filename) => !allowedRootFiles.has(filename)).forEach((filename) => {
|
|
1732
|
+
addViolation(filename, `unsupported ${exec.type} root file`);
|
|
1733
|
+
});
|
|
1734
|
+
dirs.filter((dirname) => !allowedRootDirs.has(dirname)).forEach((dirname) => {
|
|
1735
|
+
addViolation(dirname, `unsupported ${exec.type} root folder`);
|
|
1736
|
+
});
|
|
1737
|
+
if (exec.type === "lib")
|
|
1738
|
+
for (const { relativePath, reason } of await exec.getPageConventionViolations())
|
|
1739
|
+
addViolation(relativePath, reason);
|
|
1740
|
+
libRoot.files.filter((filename) => !isAllowedLibFacetRootFile(filename)).forEach((filename) => {
|
|
1741
|
+
addViolation(path7.join("lib", filename), "unsupported lib facet root file");
|
|
1058
1742
|
});
|
|
1059
1743
|
libRoot.dirs.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname)).forEach((dirname) => {
|
|
1060
|
-
addViolation(
|
|
1744
|
+
addViolation(path7.join("lib", dirname), "unsupported internal lib folder");
|
|
1061
1745
|
});
|
|
1062
1746
|
const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
|
|
1063
1747
|
const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
|
|
1064
1748
|
const scalarDirs = await exec.readdir("lib/__scalar");
|
|
1065
1749
|
await Promise.all([
|
|
1066
|
-
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database",
|
|
1067
|
-
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service",
|
|
1068
|
-
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar",
|
|
1750
|
+
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path7.join("lib", dirname))),
|
|
1751
|
+
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path7.join("lib", dirname))),
|
|
1752
|
+
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path7.join("lib/__scalar", dirname)))
|
|
1069
1753
|
]);
|
|
1070
1754
|
if (violations.length > 0) {
|
|
1071
1755
|
throw new Error(`[scan-convention]
|
|
@@ -1077,11 +1761,11 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
|
|
|
1077
1761
|
const { files, dirs } = await exec.getFilesAndDirs(modulePath);
|
|
1078
1762
|
const moduleName = getModuleNameFromPath(kind, modulePath);
|
|
1079
1763
|
dirs.forEach((dirname) => {
|
|
1080
|
-
violations.push(`${getScanPath(exec,
|
|
1764
|
+
violations.push(`${getScanPath(exec, path7.join(modulePath, dirname))}: unsupported module folder`);
|
|
1081
1765
|
});
|
|
1082
1766
|
const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
|
|
1083
1767
|
files.forEach((filename) => {
|
|
1084
|
-
const filePath =
|
|
1768
|
+
const filePath = path7.join(modulePath, filename);
|
|
1085
1769
|
if (filename === "index.ts" || filename === "index.tsx" || isAllowedTestFile(filename))
|
|
1086
1770
|
return;
|
|
1087
1771
|
if (filename === `${moduleName}.abstract.md`)
|
|
@@ -1125,7 +1809,7 @@ class ScanInfo {
|
|
|
1125
1809
|
{ all: new Set, databases: new Set, services: new Set, scalars: new Set }
|
|
1126
1810
|
]));
|
|
1127
1811
|
static async getScanResult(exec) {
|
|
1128
|
-
const [
|
|
1812
|
+
const [akanConfig, scanner, pkgs, libs] = await Promise.all([
|
|
1129
1813
|
exec.getConfig(),
|
|
1130
1814
|
createDependencyScanner(exec),
|
|
1131
1815
|
exec.workspace.getPkgs(),
|
|
@@ -1161,7 +1845,7 @@ class ScanInfo {
|
|
|
1161
1845
|
});
|
|
1162
1846
|
await Promise.all([
|
|
1163
1847
|
...databaseDirs.map(async (name) => {
|
|
1164
|
-
const filenames = await exec.readdir(
|
|
1848
|
+
const filenames = await exec.readdir(path7.join("lib", name));
|
|
1165
1849
|
filenames.forEach((filename) => {
|
|
1166
1850
|
if (filename.endsWith(".constant.ts"))
|
|
1167
1851
|
files.constant.databases.push(name);
|
|
@@ -1189,7 +1873,7 @@ class ScanInfo {
|
|
|
1189
1873
|
}),
|
|
1190
1874
|
...serviceDirs.map(async (dirname) => {
|
|
1191
1875
|
const name = dirname.slice(1);
|
|
1192
|
-
const filenames = await exec.readdir(
|
|
1876
|
+
const filenames = await exec.readdir(path7.join("lib", dirname));
|
|
1193
1877
|
filenames.forEach((filename) => {
|
|
1194
1878
|
if (filename.endsWith(".dictionary.ts"))
|
|
1195
1879
|
files.dictionary.services.push(name);
|
|
@@ -1212,7 +1896,7 @@ class ScanInfo {
|
|
|
1212
1896
|
});
|
|
1213
1897
|
}),
|
|
1214
1898
|
...scalarDirs.map(async (name) => {
|
|
1215
|
-
const filenames = await exec.readdir(
|
|
1899
|
+
const filenames = await exec.readdir(path7.join("lib/__scalar", name));
|
|
1216
1900
|
filenames.forEach((filename) => {
|
|
1217
1901
|
if (filename.endsWith(".constant.ts"))
|
|
1218
1902
|
files.constant.scalars.push(name);
|
|
@@ -1234,12 +1918,11 @@ class ScanInfo {
|
|
|
1234
1918
|
})
|
|
1235
1919
|
]);
|
|
1236
1920
|
const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
|
|
1237
|
-
const
|
|
1921
|
+
const common = {
|
|
1238
1922
|
name: exec.name,
|
|
1239
1923
|
type: exec.type,
|
|
1240
1924
|
repoName: exec.workspace.repoName,
|
|
1241
|
-
serveDomain: WorkspaceExecutor.getBaseDevEnv(
|
|
1242
|
-
akanConfig: akanConfig2,
|
|
1925
|
+
serveDomain: WorkspaceExecutor.getBaseDevEnv(path7.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
|
|
1243
1926
|
files,
|
|
1244
1927
|
libDeps,
|
|
1245
1928
|
pkgDeps,
|
|
@@ -1247,6 +1930,7 @@ class ScanInfo {
|
|
|
1247
1930
|
devDependencies: npmDevDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
|
|
1248
1931
|
routes
|
|
1249
1932
|
};
|
|
1933
|
+
const scanResult = akanConfig instanceof AkanAppConfig ? { ...common, akanConfig } : { ...common, akanConfig };
|
|
1250
1934
|
return scanResult;
|
|
1251
1935
|
}
|
|
1252
1936
|
constructor(scanResult) {
|
|
@@ -1440,16 +2124,16 @@ class LibInfo extends ScanInfo {
|
|
|
1440
2124
|
class PkgInfo {
|
|
1441
2125
|
exec;
|
|
1442
2126
|
name;
|
|
1443
|
-
scanResult;
|
|
2127
|
+
#scanResult;
|
|
1444
2128
|
static async scanExecutor(exec) {
|
|
1445
2129
|
const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
|
|
1446
2130
|
const scanner = await createDependencyScanner(exec);
|
|
1447
2131
|
const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
|
|
1448
|
-
const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((
|
|
2132
|
+
const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path) => tsconfig.compilerOptions.paths?.[path]?.some((resolve) => resolve.startsWith("pkgs/"))).map((path) => path.replace("/*", "")));
|
|
1449
2133
|
const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
|
|
1450
|
-
const pkgDeps = [...pkgPathDepSet].map((
|
|
1451
|
-
const pathSplitLength =
|
|
1452
|
-
return (tsconfig.compilerOptions.paths?.[
|
|
2134
|
+
const pkgDeps = [...pkgPathDepSet].map((path) => {
|
|
2135
|
+
const pathSplitLength = path.split("/").length;
|
|
2136
|
+
return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
|
|
1453
2137
|
}).filter((pkg) => pkg !== exec.name);
|
|
1454
2138
|
const pkgScanResult = {
|
|
1455
2139
|
name: exec.name,
|
|
@@ -1471,10 +2155,10 @@ class PkgInfo {
|
|
|
1471
2155
|
constructor(exec, scanResult) {
|
|
1472
2156
|
this.exec = exec;
|
|
1473
2157
|
this.name = exec.name;
|
|
1474
|
-
this
|
|
2158
|
+
this.#scanResult = scanResult;
|
|
1475
2159
|
}
|
|
1476
2160
|
getScanResult() {
|
|
1477
|
-
return this
|
|
2161
|
+
return this.#scanResult;
|
|
1478
2162
|
}
|
|
1479
2163
|
}
|
|
1480
2164
|
|
|
@@ -1635,16 +2319,16 @@ class Executor {
|
|
|
1635
2319
|
linter = null;
|
|
1636
2320
|
constructor(name, cwdPath) {
|
|
1637
2321
|
this.name = name;
|
|
1638
|
-
this.logger = new
|
|
2322
|
+
this.logger = new Logger2(name);
|
|
1639
2323
|
this.logs = [];
|
|
1640
2324
|
this.cwdPath = cwdPath;
|
|
1641
2325
|
}
|
|
1642
2326
|
#stdout(data) {
|
|
1643
2327
|
if (Executor.verbose)
|
|
1644
|
-
|
|
2328
|
+
Logger2.raw(chalk2.dim(data.toString()));
|
|
1645
2329
|
}
|
|
1646
2330
|
#stderr(data) {
|
|
1647
|
-
|
|
2331
|
+
Logger2.raw(chalk2.red(data.toString()));
|
|
1648
2332
|
}
|
|
1649
2333
|
exec(command, options = {}) {
|
|
1650
2334
|
const cwd = options.cwd?.toString() ?? this.cwdPath;
|
|
@@ -1672,7 +2356,7 @@ class Executor {
|
|
|
1672
2356
|
}));
|
|
1673
2357
|
});
|
|
1674
2358
|
proc.on("exit", (code, signal) => {
|
|
1675
|
-
if (
|
|
2359
|
+
if (code || signal)
|
|
1676
2360
|
reject(new CommandExecutionError({
|
|
1677
2361
|
command,
|
|
1678
2362
|
cwd,
|
|
@@ -1770,7 +2454,7 @@ class Executor {
|
|
|
1770
2454
|
}));
|
|
1771
2455
|
});
|
|
1772
2456
|
proc.on("exit", (code, signal) => {
|
|
1773
|
-
if (
|
|
2457
|
+
if (code || signal)
|
|
1774
2458
|
reject(new CommandExecutionError({
|
|
1775
2459
|
command: modulePath,
|
|
1776
2460
|
args,
|
|
@@ -1786,10 +2470,10 @@ class Executor {
|
|
|
1786
2470
|
});
|
|
1787
2471
|
}
|
|
1788
2472
|
getPath(filePath) {
|
|
1789
|
-
if (
|
|
2473
|
+
if (path8.isAbsolute(filePath))
|
|
1790
2474
|
return filePath;
|
|
1791
2475
|
if (filePath.startsWith("."))
|
|
1792
|
-
return
|
|
2476
|
+
return path8.join(this.cwdPath, filePath);
|
|
1793
2477
|
const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
|
|
1794
2478
|
const targetParts = filePath.split(/[\\/]/).filter(Boolean);
|
|
1795
2479
|
let overlapLength = 0;
|
|
@@ -1803,12 +2487,12 @@ class Executor {
|
|
|
1803
2487
|
if (isOverlap)
|
|
1804
2488
|
overlapLength = i;
|
|
1805
2489
|
}
|
|
1806
|
-
return
|
|
2490
|
+
return path8.join(this.cwdPath, ...targetParts.slice(overlapLength));
|
|
1807
2491
|
}
|
|
1808
2492
|
async mkdir(dirPath) {
|
|
1809
2493
|
const writePath = this.getPath(dirPath);
|
|
1810
2494
|
if (!await FileSys.dirExists(writePath))
|
|
1811
|
-
await
|
|
2495
|
+
await mkdir3(writePath, { recursive: true });
|
|
1812
2496
|
this.logger.verbose(`Make directory ${writePath}`);
|
|
1813
2497
|
return this;
|
|
1814
2498
|
}
|
|
@@ -1846,18 +2530,19 @@ class Executor {
|
|
|
1846
2530
|
return this;
|
|
1847
2531
|
}
|
|
1848
2532
|
async removeDir(dirPath) {
|
|
1849
|
-
const readPath =
|
|
2533
|
+
const readPath = path8.join(this.getPath(dirPath), ".");
|
|
1850
2534
|
if (await FileSys.entryExists(readPath))
|
|
1851
|
-
await
|
|
2535
|
+
await rm3(readPath, { recursive: true, force: true });
|
|
1852
2536
|
this.logger.verbose(`Remove directory ${readPath}`);
|
|
1853
2537
|
return this;
|
|
1854
2538
|
}
|
|
1855
2539
|
async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
|
|
1856
2540
|
const writePath = this.getPath(filePath);
|
|
1857
|
-
const dir =
|
|
2541
|
+
const dir = path8.dirname(writePath);
|
|
1858
2542
|
if (!await FileSys.dirExists(dir))
|
|
1859
|
-
await
|
|
1860
|
-
let contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2)
|
|
2543
|
+
await mkdir3(dir, { recursive: true });
|
|
2544
|
+
let contentStr = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}
|
|
2545
|
+
`;
|
|
1861
2546
|
if (await FileSys.fileExists(writePath)) {
|
|
1862
2547
|
const currentContent = await FileSys.readText(writePath);
|
|
1863
2548
|
if (currentContent === contentStr || !overwrite) {
|
|
@@ -1865,7 +2550,7 @@ class Executor {
|
|
|
1865
2550
|
contentStr = currentContent;
|
|
1866
2551
|
} else {
|
|
1867
2552
|
await FileSys.writeText(writePath, contentStr);
|
|
1868
|
-
if (
|
|
2553
|
+
if (Logger2.isVerbose())
|
|
1869
2554
|
this.logger.rawLog(chalk2.yellow(`File Update: ${filePath}`));
|
|
1870
2555
|
}
|
|
1871
2556
|
} else {
|
|
@@ -1879,7 +2564,7 @@ class Executor {
|
|
|
1879
2564
|
await this.writeFile(filePath, content);
|
|
1880
2565
|
}
|
|
1881
2566
|
async getLocalFile(targetPath) {
|
|
1882
|
-
const filePath =
|
|
2567
|
+
const filePath = path8.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
|
|
1883
2568
|
const content = await this.readFile(filePath);
|
|
1884
2569
|
return { filePath, content };
|
|
1885
2570
|
}
|
|
@@ -1896,9 +2581,9 @@ class Executor {
|
|
|
1896
2581
|
const dest = this.getPath(destPath);
|
|
1897
2582
|
if (!await FileSys.exists(src))
|
|
1898
2583
|
return;
|
|
1899
|
-
const isDirectory = (await
|
|
2584
|
+
const isDirectory = (await stat2(src)).isDirectory();
|
|
1900
2585
|
if (!await FileSys.exists(dest) && isDirectory)
|
|
1901
|
-
await
|
|
2586
|
+
await mkdir3(dest, { recursive: true });
|
|
1902
2587
|
if (dereference)
|
|
1903
2588
|
await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
|
|
1904
2589
|
else
|
|
@@ -1976,26 +2661,26 @@ class Executor {
|
|
|
1976
2661
|
const result = await getContent.default(scanInfo ?? null, dict, options);
|
|
1977
2662
|
if (result === null)
|
|
1978
2663
|
return null;
|
|
1979
|
-
const filename = typeof result === "object" ? result.filename :
|
|
2664
|
+
const filename = typeof result === "object" ? result.filename : path8.basename(targetPath).replace(".js", ".ts");
|
|
1980
2665
|
const content = typeof result === "object" ? result.content : result;
|
|
1981
|
-
const dirname =
|
|
1982
|
-
const convertedTargetPath = Object.entries(dict).reduce((
|
|
2666
|
+
const dirname = path8.dirname(targetPath);
|
|
2667
|
+
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), `${dirname}/${filename}`);
|
|
1983
2668
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1984
2669
|
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
1985
2670
|
} else if (targetPath.endsWith(".template")) {
|
|
1986
2671
|
const content = await FileSys.readText(templatePath);
|
|
1987
|
-
const convertedTargetPath = Object.entries(dict).reduce((
|
|
2672
|
+
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
|
|
1988
2673
|
const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
|
|
1989
2674
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1990
2675
|
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
1991
2676
|
overwrite
|
|
1992
2677
|
});
|
|
1993
|
-
} else if (staticTemplateFileExtensions.has(
|
|
1994
|
-
const convertedTargetPath = Object.entries(dict).reduce((
|
|
2678
|
+
} else if (staticTemplateFileExtensions.has(path8.extname(targetPath).toLowerCase())) {
|
|
2679
|
+
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
|
|
1995
2680
|
const writePath = this.getPath(convertedTargetPath);
|
|
1996
|
-
const dirname =
|
|
2681
|
+
const dirname = path8.dirname(writePath);
|
|
1997
2682
|
if (!await FileSys.dirExists(dirname))
|
|
1998
|
-
await
|
|
2683
|
+
await mkdir3(dirname, { recursive: true });
|
|
1999
2684
|
await copyFile(templatePath, writePath);
|
|
2000
2685
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2001
2686
|
return { filePath: writePath, content: "" };
|
|
@@ -2013,11 +2698,11 @@ class Executor {
|
|
|
2013
2698
|
const templateRoot = await this.#resolveTemplateRoot();
|
|
2014
2699
|
const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
|
|
2015
2700
|
const prefixTemplatePath = templatePath;
|
|
2016
|
-
if ((await
|
|
2017
|
-
const filename =
|
|
2701
|
+
if ((await stat2(prefixTemplatePath)).isFile()) {
|
|
2702
|
+
const filename = path8.basename(prefixTemplatePath);
|
|
2018
2703
|
const fileContent = await this.#applyTemplateFile({
|
|
2019
2704
|
templatePath: prefixTemplatePath,
|
|
2020
|
-
targetPath:
|
|
2705
|
+
targetPath: path8.join(basePath, filename),
|
|
2021
2706
|
scanInfo,
|
|
2022
2707
|
overwrite
|
|
2023
2708
|
}, dict, options);
|
|
@@ -2025,19 +2710,19 @@ class Executor {
|
|
|
2025
2710
|
} else {
|
|
2026
2711
|
const subdirs = await readDirEntries(templatePath);
|
|
2027
2712
|
const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
|
|
2028
|
-
const subpath =
|
|
2029
|
-
if ((await
|
|
2713
|
+
const subpath = path8.join(templatePath, subdir);
|
|
2714
|
+
if ((await stat2(subpath)).isFile()) {
|
|
2030
2715
|
const fileContent = await this.#applyTemplateFile({
|
|
2031
2716
|
templatePath: subpath,
|
|
2032
|
-
targetPath:
|
|
2717
|
+
targetPath: path8.join(basePath, subdir),
|
|
2033
2718
|
scanInfo,
|
|
2034
2719
|
overwrite
|
|
2035
2720
|
}, dict, options);
|
|
2036
2721
|
return fileContent ? [fileContent] : [];
|
|
2037
2722
|
} else
|
|
2038
2723
|
return await this._applyTemplate({
|
|
2039
|
-
basePath:
|
|
2040
|
-
template:
|
|
2724
|
+
basePath: path8.join(basePath, subdir),
|
|
2725
|
+
template: path8.join(template, subdir),
|
|
2041
2726
|
scanInfo,
|
|
2042
2727
|
dict,
|
|
2043
2728
|
overwrite,
|
|
@@ -2049,8 +2734,8 @@ class Executor {
|
|
|
2049
2734
|
}
|
|
2050
2735
|
async#resolveTemplateRoot() {
|
|
2051
2736
|
const candidates = [
|
|
2052
|
-
|
|
2053
|
-
|
|
2737
|
+
path8.resolve(getDirname2(import.meta.url), "templates"),
|
|
2738
|
+
path8.resolve(getDirname2(import.meta.url), "../cli/templates")
|
|
2054
2739
|
];
|
|
2055
2740
|
for (const candidate of candidates) {
|
|
2056
2741
|
if (await FileSys.dirExists(candidate))
|
|
@@ -2063,29 +2748,43 @@ class Executor {
|
|
|
2063
2748
|
...options.dict ?? {},
|
|
2064
2749
|
...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
|
|
2065
2750
|
};
|
|
2066
|
-
|
|
2751
|
+
const fileContents = await this._applyTemplate({ ...options, dict });
|
|
2752
|
+
await this.#formatAppliedTemplate(fileContents);
|
|
2753
|
+
return fileContents;
|
|
2754
|
+
}
|
|
2755
|
+
async#formatAppliedTemplate(fileContents) {
|
|
2756
|
+
const filePaths = fileContents.map((fileContent) => fileContent.filePath).filter((filePath) => filePath.endsWith(".ts") || filePath.endsWith(".tsx"));
|
|
2757
|
+
if (filePaths.length === 0)
|
|
2758
|
+
return;
|
|
2759
|
+
try {
|
|
2760
|
+
const { fixed } = await this.getLinter().fixFiles(filePaths);
|
|
2761
|
+
if (fixed.length > 0)
|
|
2762
|
+
this.logger.verbose(`Formatted ${fixed.length} scaffolded file(s)`);
|
|
2763
|
+
} catch (err) {
|
|
2764
|
+
this.logger.verbose(`Skipped formatting scaffolded files: ${err instanceof Error ? err.message : String(err)}`);
|
|
2765
|
+
}
|
|
2067
2766
|
}
|
|
2068
2767
|
async getTypeChecker() {
|
|
2069
|
-
const { TypeChecker } = await import("./typeChecker-
|
|
2768
|
+
const { TypeChecker } = await import("./typeChecker-3xgrzc2v.js");
|
|
2070
2769
|
this.typeChecker ??= new TypeChecker(this);
|
|
2071
2770
|
return this.typeChecker;
|
|
2072
2771
|
}
|
|
2073
2772
|
async typeCheck(filePath) {
|
|
2074
|
-
const
|
|
2773
|
+
const path = this.getPath(filePath);
|
|
2075
2774
|
const typeChecker = await this.getTypeChecker();
|
|
2076
|
-
const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(
|
|
2775
|
+
const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path);
|
|
2077
2776
|
const message = typeChecker.formatDiagnostics(fileDiagnostics);
|
|
2078
2777
|
return { fileDiagnostics, fileErrors, fileWarnings, message };
|
|
2079
2778
|
}
|
|
2080
2779
|
async typeCheckAsync(filePath) {
|
|
2081
|
-
const
|
|
2780
|
+
const path = this.getPath(filePath);
|
|
2082
2781
|
const entry = await this.#resolveTypecheckWorkerEntry();
|
|
2083
2782
|
const proc = Bun.spawn([process.execPath, entry], {
|
|
2084
2783
|
cwd: this.cwdPath,
|
|
2085
2784
|
env: {
|
|
2086
2785
|
...process.env,
|
|
2087
2786
|
AKAN_TYPECHECK_CWD: this.cwdPath,
|
|
2088
|
-
AKAN_TYPECHECK_FILE:
|
|
2787
|
+
AKAN_TYPECHECK_FILE: path
|
|
2089
2788
|
},
|
|
2090
2789
|
stdout: "pipe",
|
|
2091
2790
|
stderr: "pipe"
|
|
@@ -2106,13 +2805,13 @@ class Executor {
|
|
|
2106
2805
|
};
|
|
2107
2806
|
}
|
|
2108
2807
|
async#resolveTypecheckWorkerEntry() {
|
|
2109
|
-
const dirname =
|
|
2808
|
+
const dirname = getDirname2(import.meta.url);
|
|
2110
2809
|
const candidates = [
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2810
|
+
path8.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2811
|
+
path8.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2812
|
+
path8.join(dirname, "typecheck/typecheck.proc.ts"),
|
|
2813
|
+
path8.join(dirname, "typecheck.proc.js"),
|
|
2814
|
+
path8.join(dirname, "typecheck.proc.ts")
|
|
2116
2815
|
];
|
|
2117
2816
|
for (const candidate of candidates)
|
|
2118
2817
|
if (await Bun.file(candidate).exists())
|
|
@@ -2124,9 +2823,9 @@ class Executor {
|
|
|
2124
2823
|
return this.linter;
|
|
2125
2824
|
}
|
|
2126
2825
|
async lint(filePath, { fix = false, dryRun = false } = {}) {
|
|
2127
|
-
const
|
|
2826
|
+
const path = this.getPath(filePath);
|
|
2128
2827
|
const linter = this.getLinter();
|
|
2129
|
-
const { results, errors, warnings } = await linter.lint(
|
|
2828
|
+
const { results, errors, warnings } = await linter.lint(path, {
|
|
2130
2829
|
fix,
|
|
2131
2830
|
dryRun
|
|
2132
2831
|
});
|
|
@@ -2239,8 +2938,8 @@ class WorkspaceExecutor extends Executor {
|
|
|
2239
2938
|
await Promise.all(dirs.map(async (dir) => {
|
|
2240
2939
|
if (dir.includes("_") || AVOID_DIRS.includes(dir))
|
|
2241
2940
|
return;
|
|
2242
|
-
const dirPath =
|
|
2243
|
-
if ((await
|
|
2941
|
+
const dirPath = path8.join(dirname, dir);
|
|
2942
|
+
if ((await stat2(dirPath)).isDirectory()) {
|
|
2244
2943
|
results.push(`${prefix}${dir}`);
|
|
2245
2944
|
if (maxDepth > 0)
|
|
2246
2945
|
await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
|
|
@@ -2252,10 +2951,20 @@ class WorkspaceExecutor extends Executor {
|
|
|
2252
2951
|
}
|
|
2253
2952
|
async commit(message, { init = false, add = true } = {}) {
|
|
2254
2953
|
if (init)
|
|
2255
|
-
await this.
|
|
2954
|
+
await this.spawn("git", ["init", "--quiet"]);
|
|
2256
2955
|
if (add)
|
|
2257
|
-
await this.
|
|
2258
|
-
await this.
|
|
2956
|
+
await this.spawn("git", ["add", "."]);
|
|
2957
|
+
await this.spawn("git", ["commit", "--quiet", "-m", message]);
|
|
2958
|
+
}
|
|
2959
|
+
async hasChanges() {
|
|
2960
|
+
return !!(await this.spawn("git", ["status", "--porcelain"])).trim();
|
|
2961
|
+
}
|
|
2962
|
+
async listGitFiles(paths, { untracked = false } = {}) {
|
|
2963
|
+
if (!paths.length)
|
|
2964
|
+
return [];
|
|
2965
|
+
const mode = untracked ? ["--cached", "--others", "--exclude-standard"] : ["--cached"];
|
|
2966
|
+
const stdout = await this.spawn("git", ["ls-files", "-z", ...mode, "--", ...paths]);
|
|
2967
|
+
return stdout.split("\x00").filter((file) => !!file).sort();
|
|
2259
2968
|
}
|
|
2260
2969
|
async#getDirHasFile(basePath, targetFilename) {
|
|
2261
2970
|
const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
|
|
@@ -2264,9 +2973,9 @@ class WorkspaceExecutor extends Executor {
|
|
|
2264
2973
|
await Promise.all(dirs.map(async (dir) => {
|
|
2265
2974
|
if (AVOID_DIRS.includes(dir))
|
|
2266
2975
|
return;
|
|
2267
|
-
const dirPath =
|
|
2976
|
+
const dirPath = path8.join(dirname, dir);
|
|
2268
2977
|
if (await FileSys.dirExists(dirPath)) {
|
|
2269
|
-
const hasTargetFile = await FileSys.fileExists(
|
|
2978
|
+
const hasTargetFile = await FileSys.fileExists(path8.join(dirPath, targetFilename));
|
|
2270
2979
|
if (hasTargetFile)
|
|
2271
2980
|
results.push(`${prefix}${dir}`);
|
|
2272
2981
|
if (maxDepth > 0)
|
|
@@ -2332,8 +3041,20 @@ class SysExecutor extends Executor {
|
|
|
2332
3041
|
return this.#akanConfig;
|
|
2333
3042
|
}
|
|
2334
3043
|
async getModules() {
|
|
2335
|
-
const
|
|
2336
|
-
return await this.workspace.getDirInModule(
|
|
3044
|
+
const path = this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
|
|
3045
|
+
return await this.workspace.getDirInModule(path, this.name);
|
|
3046
|
+
}
|
|
3047
|
+
async getPageConventionViolations() {
|
|
3048
|
+
if (!await this.exists("page"))
|
|
3049
|
+
return [];
|
|
3050
|
+
const violations = [];
|
|
3051
|
+
for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
|
|
3052
|
+
const relativePath = entry.split(path8.sep).join("/");
|
|
3053
|
+
const reason = getPageSourceFileViolation(relativePath);
|
|
3054
|
+
if (reason)
|
|
3055
|
+
violations.push({ relativePath: `page/${relativePath}`, reason });
|
|
3056
|
+
}
|
|
3057
|
+
return violations;
|
|
2337
3058
|
}
|
|
2338
3059
|
#scanInfo = null;
|
|
2339
3060
|
hasScanInfo() {
|
|
@@ -2391,18 +3112,19 @@ class SysExecutor extends Executor {
|
|
|
2391
3112
|
}) : await LibInfo.fromExecutor(this, {
|
|
2392
3113
|
refresh
|
|
2393
3114
|
});
|
|
2394
|
-
if (write)
|
|
2395
|
-
await
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
this
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
3115
|
+
if (write)
|
|
3116
|
+
await CodegenLock.run(this.workspace.workspaceRoot, `scan:${this.name}`, async () => {
|
|
3117
|
+
await Promise.all(this.#getScanTemplateTasks(scanInfo));
|
|
3118
|
+
await this.writeJson(`akan.${this.type}.json`, scanInfo.getScanResult());
|
|
3119
|
+
if (this.type === "lib")
|
|
3120
|
+
this.#updateDependencies(scanInfo);
|
|
3121
|
+
if (writeLib) {
|
|
3122
|
+
const libInfos = [...scanInfo.getLibInfos().values()];
|
|
3123
|
+
await this.#updateDependencies(scanInfo);
|
|
3124
|
+
await Promise.all(libInfos.flatMap((libInfo) => libInfo.exec.#getScanTemplateTasks(libInfo)));
|
|
3125
|
+
}
|
|
3126
|
+
await this.syncAgentsIndex(scanInfo);
|
|
3127
|
+
});
|
|
2406
3128
|
this.#scanInfo = scanInfo;
|
|
2407
3129
|
return scanInfo;
|
|
2408
3130
|
}
|
|
@@ -2437,7 +3159,7 @@ class SysExecutor extends Executor {
|
|
|
2437
3159
|
await this.setPackageJson(libPkgJsonWithDeps);
|
|
2438
3160
|
}
|
|
2439
3161
|
async getLocalFile(targetPath) {
|
|
2440
|
-
const filePath =
|
|
3162
|
+
const filePath = path8.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
|
|
2441
3163
|
const content = await this.workspace.readFile(filePath);
|
|
2442
3164
|
return { filePath, content };
|
|
2443
3165
|
}
|
|
@@ -2523,7 +3245,7 @@ class SysExecutor extends Executor {
|
|
|
2523
3245
|
class AppExecutor extends SysExecutor {
|
|
2524
3246
|
static #routeSourceValidator = null;
|
|
2525
3247
|
static async#getRouteSourceValidator() {
|
|
2526
|
-
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-
|
|
3248
|
+
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-ze6x894c.js")).RouteSourceValidator;
|
|
2527
3249
|
return AppExecutor.#routeSourceValidator;
|
|
2528
3250
|
}
|
|
2529
3251
|
dist;
|
|
@@ -2534,9 +3256,9 @@ class AppExecutor extends SysExecutor {
|
|
|
2534
3256
|
}
|
|
2535
3257
|
static #execs = new Map;
|
|
2536
3258
|
static from(executor, name) {
|
|
2537
|
-
const
|
|
2538
|
-
if (
|
|
2539
|
-
return
|
|
3259
|
+
const exec = AppExecutor.#execs.get(name);
|
|
3260
|
+
if (exec)
|
|
3261
|
+
return exec;
|
|
2540
3262
|
else if (executor instanceof WorkspaceExecutor)
|
|
2541
3263
|
return new AppExecutor({ workspace: executor, name });
|
|
2542
3264
|
else
|
|
@@ -2572,25 +3294,31 @@ class AppExecutor extends SysExecutor {
|
|
|
2572
3294
|
};
|
|
2573
3295
|
}
|
|
2574
3296
|
async prepareCommand(type) {
|
|
2575
|
-
const
|
|
2576
|
-
const databaseMode = process.env.AKAN_DATABASE_MODE ??
|
|
3297
|
+
const akanConfig = await this.getConfig();
|
|
3298
|
+
const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
|
|
2577
3299
|
const routeEnv = {
|
|
2578
|
-
AKAN_PUBLIC_BASE_PATHS: [...
|
|
3300
|
+
AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
|
|
3301
|
+
AKAN_PUBLIC_API_PREFIX: akanConfig.api.prefix,
|
|
3302
|
+
AKAN_PUBLIC_WS_PREFIX: akanConfig.api.websocketPrefix,
|
|
2579
3303
|
AKAN_DATABASE_MODE: databaseMode
|
|
2580
3304
|
};
|
|
2581
3305
|
Object.assign(process.env, routeEnv);
|
|
2582
3306
|
if (type === "build") {
|
|
2583
3307
|
this.#excludeDevOnlyPages = true;
|
|
2584
3308
|
this.#pageKeys = null;
|
|
2585
|
-
|
|
2586
|
-
await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
|
|
3309
|
+
await this.dist.removeDir(this.dist.cwdPath);
|
|
2587
3310
|
await Promise.all([this.dist.mkdir("private"), this.dist.mkdir("public")]);
|
|
2588
3311
|
await Promise.all([
|
|
2589
3312
|
this.cp("private", `${this.dist.cwdPath}/private`, { dereference: true }),
|
|
2590
|
-
this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })
|
|
3313
|
+
...akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []
|
|
2591
3314
|
]);
|
|
2592
|
-
} else
|
|
3315
|
+
} else {
|
|
2593
3316
|
await this.removeDir(".akan");
|
|
3317
|
+
if (!akanConfig.web.ssr || !akanConfig.web.csr)
|
|
3318
|
+
this.logger.verbose(`akan.config.ts disables web.${!akanConfig.web.ssr ? "ssr" : "csr"}; \`akan start\` still serves it, \`akan build\` will not`);
|
|
3319
|
+
}
|
|
3320
|
+
if (type === "start")
|
|
3321
|
+
process.env.NODE_ENV = "development";
|
|
2594
3322
|
const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
|
|
2595
3323
|
const env = this.getCommandEnv({
|
|
2596
3324
|
AKAN_COMMAND_TYPE: type,
|
|
@@ -2638,7 +3366,7 @@ class AppExecutor extends SysExecutor {
|
|
|
2638
3366
|
async getPageKeys({ refresh } = {}) {
|
|
2639
3367
|
if (this.#pageKeys && !refresh)
|
|
2640
3368
|
return this.#pageKeys;
|
|
2641
|
-
const
|
|
3369
|
+
const akanConfig = await this.getConfig();
|
|
2642
3370
|
const glob = new Bun.Glob("**/*");
|
|
2643
3371
|
const pageKeys = [];
|
|
2644
3372
|
const owners = new Map;
|
|
@@ -2652,16 +3380,16 @@ class AppExecutor extends SysExecutor {
|
|
|
2652
3380
|
absolute: false,
|
|
2653
3381
|
onlyFiles: true
|
|
2654
3382
|
})) {
|
|
2655
|
-
const segments = rel.split(
|
|
3383
|
+
const segments = rel.split(path8.sep);
|
|
2656
3384
|
if (segments.some((s) => s === "node_modules"))
|
|
2657
3385
|
continue;
|
|
2658
3386
|
const posix = `${root.keyPrefix}${segments.join("/")}`;
|
|
2659
|
-
const absPath =
|
|
3387
|
+
const absPath = path8.join(root.dir, ...segments);
|
|
2660
3388
|
validatePageSourceFile(posix, { filePath: absPath });
|
|
2661
3389
|
if (!isRouteSourceFile(posix))
|
|
2662
3390
|
continue;
|
|
2663
3391
|
const key = `./${posix}`;
|
|
2664
|
-
validateSubRoutePageKey(key,
|
|
3392
|
+
validateSubRoutePageKey(key, akanConfig.basePaths, {
|
|
2665
3393
|
appName: this.name,
|
|
2666
3394
|
filePath: absPath
|
|
2667
3395
|
});
|
|
@@ -2679,7 +3407,8 @@ class AppExecutor extends SysExecutor {
|
|
|
2679
3407
|
}
|
|
2680
3408
|
if (!owner)
|
|
2681
3409
|
owners.set(routeId, { absPath, fromLib });
|
|
2682
|
-
const
|
|
3410
|
+
const layoutSegments = getRootBoundarySegments(key);
|
|
3411
|
+
const isRootLayout = layoutSegments !== null && isRootBoundarySegments(layoutSegments, akanConfig.basePaths);
|
|
2683
3412
|
const routeSource = await Bun.file(absPath).text();
|
|
2684
3413
|
const validator = await AppExecutor.#getRouteSourceValidator();
|
|
2685
3414
|
if (parsed.kind === "overrides")
|
|
@@ -2710,10 +3439,10 @@ class AppExecutor extends SysExecutor {
|
|
|
2710
3439
|
return pageKeys.filter((key) => !isDevOnly(key));
|
|
2711
3440
|
}
|
|
2712
3441
|
async getPageRoots() {
|
|
2713
|
-
const
|
|
3442
|
+
const akanConfig = await this.getConfig();
|
|
2714
3443
|
const pageDir = `${this.cwdPath}/page`;
|
|
2715
3444
|
const roots = [{ dir: pageDir, realDir: pageDir, keyPrefix: "" }];
|
|
2716
|
-
for (const parent of AppExecutor.#pageLibParents(
|
|
3445
|
+
for (const parent of AppExecutor.#pageLibParents(akanConfig.basePaths)) {
|
|
2717
3446
|
const libsDir = `${pageDir}/${parent}${AppExecutor.#pageLibsDir}`;
|
|
2718
3447
|
const entries = await readDirEntries(libsDir).catch(() => []);
|
|
2719
3448
|
for (const entry of entries.sort()) {
|
|
@@ -2734,9 +3463,9 @@ class AppExecutor extends SysExecutor {
|
|
|
2734
3463
|
return parents.length ? parents : [""];
|
|
2735
3464
|
}
|
|
2736
3465
|
async syncPages(libDeps) {
|
|
2737
|
-
const
|
|
2738
|
-
const parents = AppExecutor.#pageLibParents(
|
|
2739
|
-
const libs = await this.#resolvePageLibs(
|
|
3466
|
+
const akanConfig = await this.getConfig();
|
|
3467
|
+
const parents = AppExecutor.#pageLibParents(akanConfig.basePaths);
|
|
3468
|
+
const libs = await this.#resolvePageLibs(akanConfig.syncPageLibs, libDeps);
|
|
2740
3469
|
const linked = (await Promise.all(parents.map(async (parent) => {
|
|
2741
3470
|
const libsDir = `${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`;
|
|
2742
3471
|
const entries = await readDirEntries(libsDir).catch(() => []);
|
|
@@ -2794,12 +3523,12 @@ class AppExecutor extends SysExecutor {
|
|
|
2794
3523
|
static async#linkLibAsset(targetPath, linkPath) {
|
|
2795
3524
|
const isWindows = process.platform === "win32";
|
|
2796
3525
|
try {
|
|
2797
|
-
const target = isWindows ? targetPath :
|
|
3526
|
+
const target = isWindows ? targetPath : path8.relative(path8.dirname(linkPath), targetPath);
|
|
2798
3527
|
await symlink(target, linkPath, isWindows ? "junction" : "dir");
|
|
2799
3528
|
} catch (error) {
|
|
2800
3529
|
if (!isWindows)
|
|
2801
3530
|
throw error;
|
|
2802
|
-
await
|
|
3531
|
+
await mkdir3(linkPath, { recursive: true });
|
|
2803
3532
|
await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
|
|
2804
3533
|
}
|
|
2805
3534
|
}
|
|
@@ -2836,7 +3565,7 @@ class AppExecutor extends SysExecutor {
|
|
|
2836
3565
|
await this.writeFile(rel, content, opts);
|
|
2837
3566
|
},
|
|
2838
3567
|
readEnvClient: async () => {
|
|
2839
|
-
const envClientPath =
|
|
3568
|
+
const envClientPath = path8.join(this.cwdPath, "env", "env.client.ts");
|
|
2840
3569
|
if (!await FileSys.fileExists(envClientPath))
|
|
2841
3570
|
return null;
|
|
2842
3571
|
try {
|
|
@@ -2895,9 +3624,9 @@ class LibExecutor extends SysExecutor {
|
|
|
2895
3624
|
}
|
|
2896
3625
|
static #execs = new Map;
|
|
2897
3626
|
static from(executor, name) {
|
|
2898
|
-
const
|
|
2899
|
-
if (
|
|
2900
|
-
return
|
|
3627
|
+
const exec = LibExecutor.#execs.get(name);
|
|
3628
|
+
if (exec)
|
|
3629
|
+
return exec;
|
|
2901
3630
|
else if (executor instanceof WorkspaceExecutor)
|
|
2902
3631
|
return new LibExecutor({ workspace: executor, name });
|
|
2903
3632
|
else
|
|
@@ -2907,7 +3636,7 @@ class LibExecutor extends SysExecutor {
|
|
|
2907
3636
|
async getConfig({ refresh } = {}) {
|
|
2908
3637
|
if (this.#akanConfig && !refresh)
|
|
2909
3638
|
return this.#akanConfig;
|
|
2910
|
-
this.#akanConfig = await AkanLibConfig.from(this);
|
|
3639
|
+
this.#akanConfig = await AkanLibConfig.from(this, { bustImportCache: refresh });
|
|
2911
3640
|
return this.#akanConfig;
|
|
2912
3641
|
}
|
|
2913
3642
|
}
|
|
@@ -2946,7 +3675,7 @@ class PkgExecutor extends Executor {
|
|
|
2946
3675
|
return rootVersion;
|
|
2947
3676
|
try {
|
|
2948
3677
|
const packageJsonPath = `pkgs/${dep}/package.json`;
|
|
2949
|
-
if (!await Bun.file(
|
|
3678
|
+
if (!await Bun.file(path8.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
|
|
2950
3679
|
return;
|
|
2951
3680
|
const packageJson = await this.workspace.readJson(packageJsonPath);
|
|
2952
3681
|
if (packageJson.name === dep)
|
|
@@ -3004,7 +3733,7 @@ class PkgExecutor extends Executor {
|
|
|
3004
3733
|
default: "./index.ts"
|
|
3005
3734
|
}
|
|
3006
3735
|
},
|
|
3007
|
-
engines: { bun: ">=1.
|
|
3736
|
+
engines: { bun: ">=1.4.0" },
|
|
3008
3737
|
...dependencyMaps
|
|
3009
3738
|
};
|
|
3010
3739
|
await Promise.all([this.dist.writeJson("package.json", distPkgJson), this.writeJson("package.json", distPkgJson)]);
|
|
@@ -3047,4 +3776,4 @@ class ModuleExecutor extends Executor {
|
|
|
3047
3776
|
}
|
|
3048
3777
|
}
|
|
3049
3778
|
|
|
3050
|
-
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock,
|
|
3779
|
+
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 };
|