@akanjs/cli 3.0.0-alpha.1 → 3.0.0-alpha.100
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-byytxs22.js} +3 -5
- package/{agent.command-yv00p0s0.js → agent.command-v1a4746v.js} +11 -12
- package/{application.command-0qsczf4k.js → application.command-fzdcnzhg.js} +65 -37
- package/{applicationBuildRunner-ct2pxyvw.js → applicationBuildRunner-s4gsk728.js} +28 -24
- package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-28v9vbzk.js} +8 -9
- package/buildBatch.proc.js +12 -13
- package/capacitorApp-8d6d4zkm.js +12 -0
- package/{cloud.command-dxfv18sa.js → cloud.command-wygy2z05.js} +15 -17
- package/commandManifest.json +1 -1
- package/{context.command-8d9e3k6b.js → context.command-4vdt1aqq.js} +19 -22
- package/dependencyScanner-shs82ynp.js +8 -0
- package/devTui-qnf1txdz.js +553 -0
- package/devTuiView-yzct2cq6.js +9 -0
- package/getDirname-dr3jz1xg.js +7 -0
- package/{guideline.command-3jbkxzh6.js → guideline.command-p92dq6ts.js} +16 -17
- package/guidelines/agentRule/agentRule.generate.json +31 -0
- package/guidelines/agentRule/agentRule.instruction.md +533 -0
- package/guidelines/conventions/conventions.generate.json +25 -0
- package/guidelines/conventions/conventions.instruction.md +908 -0
- 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 +175 -0
- package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
- package/guidelines/runtimeRule/runtimeRule.instruction.md +220 -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.generate.json +24 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +263 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
- package/incrementalBuilder.proc.js +26 -24
- package/{index-mr5s7mqd.js → index-0amdxycf.js} +8 -8
- package/{index-t3723f99.js → index-1yxtbmbj.js} +111 -42
- package/{index-ej426mcr.js → index-29sxqfaw.js} +65 -38
- package/index-3d9y066c.js +76 -0
- package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
- package/index-5svjba73.js +159 -0
- package/index-6wwzs1bc.js +7 -0
- package/{index-k95xkh4p.js → index-7d6vny25.js} +16 -12
- package/{index-ec09pws3.js → index-8p8p0cf8.js} +14 -14
- package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
- package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
- package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
- package/{index-0cj2zxbm.js → index-c69j78kk.js} +12 -11
- package/{index-rzwcpcc3.js → index-fc1bh0a7.js} +4 -4
- package/{index-7c6k4yvg.js → index-gq44bqjt.js} +58 -25
- package/{index-a00jg8x8.js → index-gtmz74tj.js} +79 -55
- package/{index-tdbas9r0.js → index-h7yw2z9n.js} +7 -7
- package/{index-bjpxzr6s.js → index-hwkygn9s.js} +51 -3
- package/{index-bbkek1bp.js → index-hyywke0f.js} +1 -1
- package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
- package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
- package/{index-pyt2b4nw.js → index-p5neav9b.js} +1129 -1356
- package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
- package/{index-bekddz9q.js → index-qkk27bk6.js} +32 -32
- package/{index-hgna5rjy.js → index-qsq3q8fy.js} +1034 -257
- package/{index-2q1f5j9k.js → index-srdssqj7.js} +18 -18
- package/{index-rh16j3c7.js → index-w8kmx1b6.js} +23 -13
- package/{index-nm6wghb6.js → index-yks023mb.js} +13 -13
- package/{index-1pvex4az.js → index-yza7zkpt.js} +94 -61
- package/{index-yzmhasdb.js → index-zmjppp25.js} +57 -57
- package/{index-8hsbnyes.js → index-znynzt5w.js} +100 -30
- package/{index-zasydjxd.js → index-zxgr3s8s.js} +31 -30
- package/{index-e16gnn26.js → index-zz5jyd94.js} +1002 -128
- package/index.js +22 -24
- package/{library.command-ebdj1vnm.js → library.command-s4fszngs.js} +11 -9
- package/{localRegistry.command-gb178ne8.js → localRegistry.command-gyw4da9z.js} +20 -22
- package/{module.command-9c1bnsx4.js → module.command-6n7dwvav.js} +13 -15
- package/{package.command-t93p4jkj.js → package.command-gr82ypnr.js} +8 -9
- package/package.json +5 -7
- package/{page.command-k3k0meh2.js → page.command-7gvgb3jb.js} +7 -8
- package/{primitive.command-de6nktdt.js → primitive.command-tk5ckw38.js} +13 -15
- package/{quality.command-z5kth2wa.js → quality.command-38v81baf.js} +229 -138
- package/recipeScanner-fpagtgc1.js +11 -0
- package/{repair.command-ehf2g112.js → repair.command-5xbwvsxp.js} +12 -13
- package/{routeSourceValidator-cncd92fv.js → routeSourceValidator-ze6x894c.js} +2 -40
- package/{scalar.command-7a2jtv33.js → scalar.command-0epwcr5k.js} +11 -13
- package/subspace.command-e2t36xb2.js +755 -0
- package/templates/app/env/env.client.debug.ts.template +1 -5
- package/templates/app/env/env.client.develop.ts.template +1 -5
- package/templates/app/env/env.client.local.ts.template +1 -5
- package/templates/app/env/env.client.main.ts.template +1 -5
- package/templates/app/env/env.client.testing.ts.template +1 -5
- package/templates/app/env/env.client.type.ts +2 -6
- package/templates/app/env/env.server.debug.ts.template +1 -5
- package/templates/app/env/env.server.develop.ts.template +1 -5
- package/templates/app/env/env.server.local.ts.template +1 -5
- package/templates/app/env/env.server.main.ts.template +1 -5
- package/templates/app/main.ts +3 -1
- package/templates/app/page/styles.css.template +3 -3
- 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 +6 -6
- package/templates/crudPages/[__model__Id]/page.tsx +10 -13
- package/templates/crudPages/new/page.tsx +6 -6
- 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 +4 -2
- package/templates/lib/dict.ts +1 -1
- package/templates/lib/sig.ts +5 -1
- package/templates/lib/srv.ts +6 -2
- package/templates/lib/useClient.ts +9 -3
- package/templates/module/__Model__.Util.tsx +1 -1
- package/templates/module/__Model__.Zone.tsx +3 -5
- 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 -948
- package/templates/workspaceRoot/CLAUDE.md.template +19 -0
- package/templates/workspaceRoot/biome.json.template +4 -198
- package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +15 -16
- 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-e0tt0vk2.js → workflow.command-kphbr7eh.js} +21 -23
- package/{workspace.command-fpda0evj.js → workspace.command-j1gatz3d.js} +108 -91
- package/capacitorApp-4r9sm3qg.js +0 -58
- package/dependencyScanner-f6grrep3.js +0 -9
- package/index-mxvakhsm.js +0 -66
- package/index-r24hmh0q.js +0 -4
- package/recipeScanner-tghkzrrt.js +0 -12
- package/templates/libRoot/base/baseLogic.ts +0 -14
- package/templates/libRoot/base/index.ts +0 -8
- package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
- package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -33
- package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
- package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
- package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
- package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
- package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
- package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
- package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
- package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -18
- package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -61
- package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
- package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
- package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
- package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -58
- package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
- package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
- package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
- package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
- package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
- 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,13 +1,189 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import {
|
|
3
|
-
Spinner
|
|
4
|
-
} from "./index-mxvakhsm.js";
|
|
5
2
|
import {
|
|
6
3
|
FileSys
|
|
7
|
-
} from "./index-
|
|
4
|
+
} from "./index-q0y2jq9j.js";
|
|
8
5
|
import {
|
|
9
|
-
|
|
10
|
-
} 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
|
+
}
|
|
11
187
|
|
|
12
188
|
// pkgs/@akanjs/devkit/executors.ts
|
|
13
189
|
import {
|
|
@@ -19,19 +195,20 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
19
195
|
import {
|
|
20
196
|
copyFile,
|
|
21
197
|
cp as cpEntry,
|
|
22
|
-
mkdir,
|
|
198
|
+
mkdir as mkdir3,
|
|
23
199
|
readdir as readDirEntries,
|
|
24
200
|
realpath,
|
|
25
|
-
rm as
|
|
26
|
-
stat,
|
|
201
|
+
rm as rm3,
|
|
202
|
+
stat as stat2,
|
|
27
203
|
symlink
|
|
28
204
|
} from "fs/promises";
|
|
29
|
-
import
|
|
205
|
+
import path8 from "path";
|
|
30
206
|
import { pathToFileURL } from "url";
|
|
31
207
|
import {
|
|
32
208
|
capitalize,
|
|
209
|
+
getPageSourceFileViolation,
|
|
33
210
|
isRouteSourceFile,
|
|
34
|
-
Logger,
|
|
211
|
+
Logger as Logger2,
|
|
35
212
|
parseRouteModuleKey,
|
|
36
213
|
validatePageSourceFile,
|
|
37
214
|
validateSubRoutePageKey
|
|
@@ -41,9 +218,24 @@ import chalk2 from "chalk";
|
|
|
41
218
|
|
|
42
219
|
// pkgs/@akanjs/devkit/agentsIndex.ts
|
|
43
220
|
var recipeScannerLoad = null;
|
|
44
|
-
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-
|
|
221
|
+
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-fpagtgc1.js");
|
|
45
222
|
var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
|
|
46
223
|
var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
|
|
224
|
+
var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
|
|
225
|
+
var stampBlockVersion = (block, version) => `${AGENT_VERSION_PREFIX}${version} -->
|
|
226
|
+
|
|
227
|
+
${block}`;
|
|
228
|
+
var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
|
|
229
|
+
var readDevkitVersion = async () => {
|
|
230
|
+
const { readFile } = await import("fs/promises");
|
|
231
|
+
const { getDirname } = await import("./getDirname-dr3jz1xg.js");
|
|
232
|
+
try {
|
|
233
|
+
const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
|
|
234
|
+
return JSON.parse(raw).version ?? null;
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
47
239
|
var upsertAgentBlock = (existing, block) => {
|
|
48
240
|
const managed = `${AGENT_BLOCK_START}
|
|
49
241
|
${block}
|
|
@@ -137,15 +329,36 @@ ${AGENT_BLOCK_START}
|
|
|
137
329
|
${block}
|
|
138
330
|
${AGENT_BLOCK_END}
|
|
139
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.`;
|
|
140
350
|
var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
|
|
141
351
|
|
|
142
352
|
@AGENTS.md
|
|
353
|
+
|
|
354
|
+
${CLAUDE_COMMENT_RULE}
|
|
143
355
|
`;
|
|
144
356
|
|
|
145
357
|
// pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
|
|
146
358
|
import fs from "fs";
|
|
147
|
-
import
|
|
359
|
+
import path2 from "path";
|
|
148
360
|
import { fileURLToPath } from "url";
|
|
361
|
+
import { normalizeRoutePrefix } from "akanjs/base";
|
|
149
362
|
import { resolveAkanI18nConfig } from "akanjs/common";
|
|
150
363
|
|
|
151
364
|
// pkgs/@akanjs/devkit/akanConfig/types.ts
|
|
@@ -181,9 +394,8 @@ var DEFAULT_OPTIMIZE_IMPORTS = [
|
|
|
181
394
|
"react-icons/*"
|
|
182
395
|
];
|
|
183
396
|
var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
|
|
397
|
+
var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
|
|
184
398
|
var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
|
|
185
|
-
var NATIVE_RUNTIME_PACKAGES = ["sharp"];
|
|
186
|
-
var DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"];
|
|
187
399
|
var MOBILE_RUNTIME_PACKAGES = [
|
|
188
400
|
"@capacitor/cli",
|
|
189
401
|
"@capacitor/core",
|
|
@@ -212,8 +424,6 @@ var DATABASE_MODE_RUNTIME_PACKAGES = {
|
|
|
212
424
|
};
|
|
213
425
|
var AKAN_RUNTIME_PACKAGES = new Set([
|
|
214
426
|
...SSR_RUNTIME_PACKAGES,
|
|
215
|
-
...NATIVE_RUNTIME_PACKAGES,
|
|
216
|
-
...DEFAULT_BACKEND_RUNTIME_PACKAGES,
|
|
217
427
|
...MOBILE_RUNTIME_PACKAGES,
|
|
218
428
|
...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat()
|
|
219
429
|
]);
|
|
@@ -228,14 +438,15 @@ var DEFAULT_AKAN_IMAGE_CONFIG = {
|
|
|
228
438
|
dangerouslyAllowSVG: false,
|
|
229
439
|
maximumRedirects: 3,
|
|
230
440
|
fetchTimeoutMs: 7000,
|
|
231
|
-
maxRemoteBytes: 25 * 1024 * 1024
|
|
441
|
+
maxRemoteBytes: 25 * 1024 * 1024,
|
|
442
|
+
maxConcurrency: 0
|
|
232
443
|
};
|
|
233
444
|
var normalizeIndexPath = (indexPath) => {
|
|
234
445
|
const normalized = indexPath?.trim();
|
|
235
446
|
if (!normalized)
|
|
236
447
|
return;
|
|
237
|
-
const
|
|
238
|
-
return
|
|
448
|
+
const path = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
449
|
+
return path === "/" ? "/" : path;
|
|
239
450
|
};
|
|
240
451
|
var normalizeStringList = (values) => {
|
|
241
452
|
const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
@@ -275,21 +486,39 @@ var normalizeDeepLinks = (deepLinks) => {
|
|
|
275
486
|
...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
|
|
276
487
|
};
|
|
277
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
|
+
};
|
|
278
503
|
|
|
279
504
|
class AkanAppConfig {
|
|
280
505
|
app;
|
|
281
506
|
rootPackageJson;
|
|
282
507
|
docker;
|
|
508
|
+
dockerfile;
|
|
283
509
|
defaultDatabaseMode;
|
|
510
|
+
web;
|
|
284
511
|
externalLibs;
|
|
285
512
|
barrelImports;
|
|
286
513
|
optimizeImports;
|
|
287
514
|
images;
|
|
288
515
|
i18n;
|
|
516
|
+
api;
|
|
289
517
|
publicEnv;
|
|
290
518
|
mobile;
|
|
291
519
|
hasMobileConfig;
|
|
292
520
|
secrets;
|
|
521
|
+
assets;
|
|
293
522
|
syncPageLibs;
|
|
294
523
|
baseDevEnv;
|
|
295
524
|
libs;
|
|
@@ -298,7 +527,7 @@ class AkanAppConfig {
|
|
|
298
527
|
subRoutes = new Map;
|
|
299
528
|
basePaths = new Set;
|
|
300
529
|
branches = new Set(["debug", "develop", "main"]);
|
|
301
|
-
constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = []) {
|
|
530
|
+
constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = [], libContributions = emptyLibContributions()) {
|
|
302
531
|
this.app = app;
|
|
303
532
|
this.rootPackageJson = rootPackageJson;
|
|
304
533
|
this.libs = libs;
|
|
@@ -306,7 +535,7 @@ class AkanAppConfig {
|
|
|
306
535
|
this.plugins = plugins;
|
|
307
536
|
this.#applyRoutes(config?.routes);
|
|
308
537
|
this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
|
|
309
|
-
this.externalLibs = config?.externalLibs ?? [];
|
|
538
|
+
this.externalLibs = [...new Set([...config?.externalLibs ?? [], ...libContributions.externalLibs])];
|
|
310
539
|
this.barrelImports = [
|
|
311
540
|
...DEFAULT_BARREL_IMPORTS,
|
|
312
541
|
...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
|
|
@@ -318,12 +547,33 @@ class AkanAppConfig {
|
|
|
318
547
|
this.i18n = resolveAkanI18nConfig(config?.i18n);
|
|
319
548
|
process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
|
|
320
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;
|
|
321
556
|
this.publicEnv = config?.publicEnv ?? [];
|
|
322
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
|
+
};
|
|
323
565
|
this.syncPageLibs = config?.syncPageLibs ?? false;
|
|
324
566
|
this.hasMobileConfig = Boolean(config.mobile);
|
|
325
567
|
this.mobile = this.#resolveMobileConfig(config.mobile);
|
|
326
|
-
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;
|
|
327
577
|
}
|
|
328
578
|
#resolveMobileConfig(mobile) {
|
|
329
579
|
const {
|
|
@@ -433,23 +683,31 @@ class AkanAppConfig {
|
|
|
433
683
|
return archs.map((arch) => `FROM ${image[arch] ?? defaultImage} AS ${arch}`).join(`
|
|
434
684
|
`);
|
|
435
685
|
}
|
|
436
|
-
#
|
|
437
|
-
if (docker
|
|
438
|
-
return
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
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/*
|
|
450
710
|
RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
|
|
451
|
-
RUN apt-get update && apt-get upgrade -y
|
|
452
|
-
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
|
|
453
711
|
ARG TARGETARCH
|
|
454
712
|
${preRunScripts.join(`
|
|
455
713
|
`)}
|
|
@@ -469,16 +727,12 @@ ENV AKAN_PUBLIC_ENV=${this.baseDevEnv.env}
|
|
|
469
727
|
${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join(",")}` : ""}
|
|
470
728
|
ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
|
|
471
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}
|
|
472
732
|
ENV AKAN_PUBLIC_OPERATION_MODE=cloud
|
|
473
|
-
|
|
733
|
+
ENV AKAN_LOG_TO_FILE=0
|
|
734
|
+
${webEnvLines}
|
|
474
735
|
CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
475
|
-
return {
|
|
476
|
-
content,
|
|
477
|
-
image: imageScript,
|
|
478
|
-
preRuns: docker.preRuns ?? [],
|
|
479
|
-
postRuns: docker.postRuns ?? [],
|
|
480
|
-
command
|
|
481
|
-
};
|
|
482
736
|
}
|
|
483
737
|
static #importGeneration = 0;
|
|
484
738
|
static async importConfigModule(cwdPath, { bustImportCache = false } = {}) {
|
|
@@ -489,13 +743,28 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
489
743
|
static async from(app, { bustImportCache = false } = {}) {
|
|
490
744
|
const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
|
|
491
745
|
AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
|
|
492
|
-
WorkspaceExecutor.getBaseDevEnv(
|
|
746
|
+
WorkspaceExecutor.getBaseDevEnv(path2.join(app.workspace.workspaceRoot, ".env")),
|
|
493
747
|
app.workspace.getLibs(),
|
|
494
748
|
app.workspace.getPackageJson()
|
|
495
749
|
]);
|
|
496
750
|
const resolved = typeof configImp === "function" ? configImp(app) : configImp;
|
|
497
751
|
const { plugins, ...config } = resolved ?? {};
|
|
498
|
-
|
|
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
|
+
};
|
|
499
768
|
}
|
|
500
769
|
#resolveProductionDependencyVersion(lib) {
|
|
501
770
|
const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
|
|
@@ -505,13 +774,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
505
774
|
return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
506
775
|
}
|
|
507
776
|
#getProductionRuntimePackages() {
|
|
508
|
-
return [
|
|
509
|
-
...this.externalLibs,
|
|
510
|
-
...SSR_RUNTIME_PACKAGES,
|
|
511
|
-
...NATIVE_RUNTIME_PACKAGES,
|
|
512
|
-
...DEFAULT_BACKEND_RUNTIME_PACKAGES,
|
|
513
|
-
...this.getDatabaseModeRuntimePackages()
|
|
514
|
-
];
|
|
777
|
+
return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
|
|
515
778
|
}
|
|
516
779
|
getDatabaseModeRuntimePackages(databaseMode = this.defaultDatabaseMode) {
|
|
517
780
|
return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
|
|
@@ -560,11 +823,11 @@ var akanPackageJson = null;
|
|
|
560
823
|
function getAkanPackageJson() {
|
|
561
824
|
if (akanPackageJson)
|
|
562
825
|
return akanPackageJson;
|
|
563
|
-
const sourceDir =
|
|
826
|
+
const sourceDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
564
827
|
const packageJsonPaths = [
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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")
|
|
568
831
|
];
|
|
569
832
|
try {
|
|
570
833
|
packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
|
|
@@ -599,14 +862,18 @@ function mergeImageConfig(config = {}) {
|
|
|
599
862
|
class AkanLibConfig {
|
|
600
863
|
lib;
|
|
601
864
|
externalLibs;
|
|
865
|
+
docker;
|
|
866
|
+
assets;
|
|
602
867
|
plugins;
|
|
603
868
|
constructor(lib, config, plugins = []) {
|
|
604
869
|
this.lib = lib;
|
|
605
870
|
this.externalLibs = config?.externalLibs ?? [];
|
|
871
|
+
this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
|
|
872
|
+
this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
|
|
606
873
|
this.plugins = plugins;
|
|
607
874
|
}
|
|
608
|
-
static async from(lib) {
|
|
609
|
-
const
|
|
875
|
+
static async from(lib, { bustImportCache = false } = {}) {
|
|
876
|
+
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
610
877
|
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
611
878
|
const { plugins, ...config } = resolved ?? {};
|
|
612
879
|
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
@@ -615,54 +882,448 @@ class AkanLibConfig {
|
|
|
615
882
|
//! need to refactor
|
|
616
883
|
var increaseBuildNum = async (app) => {
|
|
617
884
|
const appConfig = await AkanAppConfig.from(app);
|
|
618
|
-
const akanConfigPath =
|
|
885
|
+
const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
|
|
619
886
|
const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
|
|
620
887
|
const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum + 1}`);
|
|
621
888
|
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
622
889
|
};
|
|
623
890
|
var decreaseBuildNum = async (app) => {
|
|
624
891
|
const appConfig = await AkanAppConfig.from(app);
|
|
625
|
-
const akanConfigPath =
|
|
892
|
+
const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
|
|
626
893
|
const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
|
|
627
894
|
const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
|
|
628
895
|
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
629
896
|
};
|
|
630
|
-
// pkgs/@akanjs/devkit/
|
|
631
|
-
import
|
|
632
|
-
import
|
|
633
|
-
|
|
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
|
+
}
|
|
634
1238
|
|
|
635
1239
|
// pkgs/@akanjs/devkit/linter.ts
|
|
636
1240
|
import { spawn } from "child_process";
|
|
637
1241
|
import { existsSync, readFileSync } from "fs";
|
|
638
|
-
import
|
|
1242
|
+
import path5 from "path";
|
|
639
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"];
|
|
640
1292
|
|
|
641
1293
|
class Linter {
|
|
642
1294
|
lintRoot;
|
|
1295
|
+
configPath;
|
|
643
1296
|
#biomeBin;
|
|
644
1297
|
constructor(cwdPath) {
|
|
645
1298
|
this.lintRoot = this.#findBiomeRootPath(cwdPath);
|
|
646
|
-
|
|
1299
|
+
this.configPath = Linter.#configPathIn(this.lintRoot) ?? path5.join(this.lintRoot, "biome.json");
|
|
1300
|
+
const localBiomeBin = path5.join(this.lintRoot, "node_modules/.bin/biome");
|
|
647
1301
|
this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
|
|
648
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
|
+
}
|
|
649
1311
|
#findBiomeRootPath(dir) {
|
|
650
|
-
|
|
651
|
-
if (existsSync(configPath))
|
|
1312
|
+
if (Linter.#configPathIn(dir))
|
|
652
1313
|
return dir;
|
|
653
|
-
const parentDir =
|
|
1314
|
+
const parentDir = path5.dirname(dir);
|
|
654
1315
|
if (parentDir === dir)
|
|
655
1316
|
throw new Error(`biome.json not found from ${dir}`);
|
|
656
1317
|
return this.#findBiomeRootPath(parentDir);
|
|
657
1318
|
}
|
|
658
1319
|
#toBiomePath(filePath) {
|
|
659
|
-
const relativePath =
|
|
660
|
-
if (!relativePath.startsWith("..") && !
|
|
1320
|
+
const relativePath = path5.relative(this.lintRoot, filePath);
|
|
1321
|
+
if (!relativePath.startsWith("..") && !path5.isAbsolute(relativePath))
|
|
661
1322
|
return relativePath;
|
|
662
1323
|
return filePath;
|
|
663
1324
|
}
|
|
664
1325
|
#resolveFilePath(filePath) {
|
|
665
|
-
return
|
|
1326
|
+
return path5.isAbsolute(filePath) ? filePath : path5.join(this.lintRoot, filePath);
|
|
666
1327
|
}
|
|
667
1328
|
async#runBiome(args, input) {
|
|
668
1329
|
return await new Promise((resolve, reject) => {
|
|
@@ -683,18 +1344,11 @@ class Linter {
|
|
|
683
1344
|
proc.stdin.end(input);
|
|
684
1345
|
});
|
|
685
1346
|
}
|
|
686
|
-
#parseBiomeReport(output) {
|
|
687
|
-
const jsonStart = output.indexOf("{");
|
|
688
|
-
const jsonEnd = output.lastIndexOf("}");
|
|
689
|
-
if (jsonStart === -1 || jsonEnd === -1 || jsonEnd < jsonStart)
|
|
690
|
-
throw new Error(output.trim() || "No Biome JSON output");
|
|
691
|
-
return JSON.parse(output.slice(jsonStart, jsonEnd + 1));
|
|
692
|
-
}
|
|
693
1347
|
#diagnosticFilePath(diagnostic, fallbackFilePath) {
|
|
694
1348
|
const diagnosticPath = diagnostic.location?.path;
|
|
695
1349
|
if (!diagnosticPath)
|
|
696
1350
|
return fallbackFilePath;
|
|
697
|
-
return
|
|
1351
|
+
return path5.isAbsolute(diagnosticPath) ? diagnosticPath : path5.join(this.lintRoot, diagnosticPath);
|
|
698
1352
|
}
|
|
699
1353
|
#createLintMessage(diagnostic) {
|
|
700
1354
|
const start = diagnostic.location?.start;
|
|
@@ -759,10 +1413,10 @@ class Linter {
|
|
|
759
1413
|
"--max-diagnostics=none",
|
|
760
1414
|
"--no-errors-on-unmatched",
|
|
761
1415
|
"--config-path",
|
|
762
|
-
|
|
1416
|
+
this.configPath,
|
|
763
1417
|
this.#toBiomePath(filePath)
|
|
764
1418
|
]);
|
|
765
|
-
const report =
|
|
1419
|
+
const report = parseBiomeReport(stdout || stderr);
|
|
766
1420
|
const results = this.#toLintResults(report, filePath);
|
|
767
1421
|
const { errors, warnings } = this.#splitMessages(results);
|
|
768
1422
|
const output = write && existsSync(filePath) ? readFileSync(filePath, "utf8") : undefined;
|
|
@@ -880,22 +1534,34 @@ ${errorText}, ${warningText} found`];
|
|
|
880
1534
|
if (!dryRun)
|
|
881
1535
|
return await this.#checkFile(resolvedFilePath, { write: true });
|
|
882
1536
|
const source = readFileSync(resolvedFilePath, "utf8");
|
|
883
|
-
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([
|
|
884
1547
|
"check",
|
|
885
1548
|
"--write",
|
|
1549
|
+
"--reporter=json",
|
|
1550
|
+
"--max-diagnostics=none",
|
|
1551
|
+
"--no-errors-on-unmatched",
|
|
886
1552
|
"--config-path",
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
+
};
|
|
893
1559
|
}
|
|
894
1560
|
async getConfigForFile(filePath) {
|
|
895
1561
|
const resolvedFilePath = this.#resolveFilePath(filePath);
|
|
896
1562
|
if (!existsSync(resolvedFilePath))
|
|
897
1563
|
throw new Error(`File not found: ${filePath}`);
|
|
898
|
-
return JSON.parse(readFileSync(
|
|
1564
|
+
return JSON.parse(readFileSync(this.configPath, "utf8"));
|
|
899
1565
|
}
|
|
900
1566
|
async getProblematicRules(filePath) {
|
|
901
1567
|
const { results } = await this.lintFile(filePath);
|
|
@@ -910,22 +1576,36 @@ ${errorText}, ${warningText} found`];
|
|
|
910
1576
|
}
|
|
911
1577
|
}
|
|
912
1578
|
|
|
1579
|
+
// pkgs/@akanjs/devkit/repoIdentity.ts
|
|
1580
|
+
import { execFileSync } from "child_process";
|
|
1581
|
+
import path6 from "path";
|
|
1582
|
+
var resolved = new Map;
|
|
1583
|
+
var readRemoteName = (workspaceRoot) => {
|
|
1584
|
+
try {
|
|
1585
|
+
const url = execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
1586
|
+
cwd: workspaceRoot,
|
|
1587
|
+
encoding: "utf-8",
|
|
1588
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1589
|
+
}).trim();
|
|
1590
|
+
return url.replace(/\.git$/, "").split(/[/:]/).pop() || null;
|
|
1591
|
+
} catch {
|
|
1592
|
+
return null;
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
var resolveRepoName = (workspaceRoot) => {
|
|
1596
|
+
const cached = resolved.get(workspaceRoot);
|
|
1597
|
+
if (cached)
|
|
1598
|
+
return cached;
|
|
1599
|
+
const repoName = readRemoteName(workspaceRoot) ?? path6.basename(workspaceRoot);
|
|
1600
|
+
resolved.set(workspaceRoot, repoName);
|
|
1601
|
+
return repoName;
|
|
1602
|
+
};
|
|
1603
|
+
|
|
913
1604
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
914
|
-
import { rm } from "fs/promises";
|
|
915
|
-
import
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
"dictionary",
|
|
919
|
-
"document",
|
|
920
|
-
"service",
|
|
921
|
-
"signal",
|
|
922
|
-
"store",
|
|
923
|
-
"template",
|
|
924
|
-
"unit",
|
|
925
|
-
"util",
|
|
926
|
-
"view",
|
|
927
|
-
"zone"
|
|
928
|
-
];
|
|
1605
|
+
import { rm as rm2 } from "fs/promises";
|
|
1606
|
+
import path7 from "path";
|
|
1607
|
+
|
|
1608
|
+
// pkgs/@akanjs/devkit/workspaceLayout.ts
|
|
929
1609
|
var appRootAllowedFiles = new Set([
|
|
930
1610
|
"AGENTS.md",
|
|
931
1611
|
"CLAUDE.md",
|
|
@@ -939,25 +1619,53 @@ var appRootAllowedFiles = new Set([
|
|
|
939
1619
|
"tsconfig.json",
|
|
940
1620
|
"tsconfig.tsbuildinfo"
|
|
941
1621
|
]);
|
|
942
|
-
var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
|
|
943
1622
|
var appRootAllowedDirs = new Set([
|
|
944
1623
|
".akan",
|
|
945
1624
|
"android",
|
|
1625
|
+
"common",
|
|
946
1626
|
"env",
|
|
947
1627
|
"ios",
|
|
948
1628
|
"lib",
|
|
949
1629
|
"mobile",
|
|
950
1630
|
"page",
|
|
1631
|
+
"plugin",
|
|
951
1632
|
"private",
|
|
952
1633
|
"public",
|
|
953
1634
|
"script",
|
|
954
|
-
"
|
|
1635
|
+
"secrets",
|
|
955
1636
|
"srvkit",
|
|
956
|
-
"
|
|
957
|
-
"
|
|
958
|
-
"secrets"
|
|
1637
|
+
"ui",
|
|
1638
|
+
"webkit"
|
|
959
1639
|
]);
|
|
960
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 };
|
|
1668
|
+
var libFacetRootAllowedFiles = new Set([
|
|
961
1669
|
"cnst.ts",
|
|
962
1670
|
"db.ts",
|
|
963
1671
|
"dict.ts",
|
|
@@ -968,6 +1676,25 @@ var libRootAllowedFiles = new Set([
|
|
|
968
1676
|
"useClient.ts",
|
|
969
1677
|
"useServer.ts"
|
|
970
1678
|
]);
|
|
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);
|
|
1682
|
+
|
|
1683
|
+
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
1684
|
+
var databaseFileTypes = [
|
|
1685
|
+
"constant",
|
|
1686
|
+
"dictionary",
|
|
1687
|
+
"document",
|
|
1688
|
+
"service",
|
|
1689
|
+
"signal",
|
|
1690
|
+
"store",
|
|
1691
|
+
"template",
|
|
1692
|
+
"unit",
|
|
1693
|
+
"util",
|
|
1694
|
+
"view",
|
|
1695
|
+
"zone"
|
|
1696
|
+
];
|
|
1697
|
+
var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
|
|
971
1698
|
var internalLibDirs = new Set(["__lib", "__scalar"]);
|
|
972
1699
|
var moduleNonUiFileTypes = {
|
|
973
1700
|
database: new Set(["constant", "dictionary", "document", "service", "signal", "store"]),
|
|
@@ -980,18 +1707,16 @@ var moduleUiFileTypes = {
|
|
|
980
1707
|
scalar: new Set(["Template", "Unit"])
|
|
981
1708
|
};
|
|
982
1709
|
var testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
|
|
983
|
-
var
|
|
984
|
-
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);
|
|
985
1711
|
var isAllowedTestFile = (filename) => testFilePattern.test(filename);
|
|
986
|
-
var
|
|
987
|
-
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("/"));
|
|
988
1713
|
async function clearGeneratedRootCapacitorConfigs(exec) {
|
|
989
1714
|
if (exec.type !== "app")
|
|
990
1715
|
return;
|
|
991
|
-
await Promise.all(generatedRootCapacitorConfigFiles.map((filename) =>
|
|
1716
|
+
await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm2(exec.getPath(filename), { force: true })));
|
|
992
1717
|
}
|
|
993
1718
|
var getModuleNameFromPath = (kind, modulePath) => {
|
|
994
|
-
const dirname =
|
|
1719
|
+
const dirname = path7.basename(modulePath);
|
|
995
1720
|
return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
|
|
996
1721
|
};
|
|
997
1722
|
async function assertScanConvention(exec, libRoot) {
|
|
@@ -1000,28 +1725,31 @@ async function assertScanConvention(exec, libRoot) {
|
|
|
1000
1725
|
const addViolation = (relativePath, reason) => {
|
|
1001
1726
|
violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
|
|
1002
1727
|
};
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
});
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
|
|
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");
|
|
1014
1742
|
});
|
|
1015
1743
|
libRoot.dirs.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname)).forEach((dirname) => {
|
|
1016
|
-
addViolation(
|
|
1744
|
+
addViolation(path7.join("lib", dirname), "unsupported internal lib folder");
|
|
1017
1745
|
});
|
|
1018
1746
|
const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
|
|
1019
1747
|
const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
|
|
1020
1748
|
const scalarDirs = await exec.readdir("lib/__scalar");
|
|
1021
1749
|
await Promise.all([
|
|
1022
|
-
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database",
|
|
1023
|
-
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service",
|
|
1024
|
-
...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)))
|
|
1025
1753
|
]);
|
|
1026
1754
|
if (violations.length > 0) {
|
|
1027
1755
|
throw new Error(`[scan-convention]
|
|
@@ -1033,11 +1761,11 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
|
|
|
1033
1761
|
const { files, dirs } = await exec.getFilesAndDirs(modulePath);
|
|
1034
1762
|
const moduleName = getModuleNameFromPath(kind, modulePath);
|
|
1035
1763
|
dirs.forEach((dirname) => {
|
|
1036
|
-
violations.push(`${getScanPath(exec,
|
|
1764
|
+
violations.push(`${getScanPath(exec, path7.join(modulePath, dirname))}: unsupported module folder`);
|
|
1037
1765
|
});
|
|
1038
1766
|
const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
|
|
1039
1767
|
files.forEach((filename) => {
|
|
1040
|
-
const filePath =
|
|
1768
|
+
const filePath = path7.join(modulePath, filename);
|
|
1041
1769
|
if (filename === "index.ts" || filename === "index.tsx" || isAllowedTestFile(filename))
|
|
1042
1770
|
return;
|
|
1043
1771
|
if (filename === `${moduleName}.abstract.md`)
|
|
@@ -1081,7 +1809,7 @@ class ScanInfo {
|
|
|
1081
1809
|
{ all: new Set, databases: new Set, services: new Set, scalars: new Set }
|
|
1082
1810
|
]));
|
|
1083
1811
|
static async getScanResult(exec) {
|
|
1084
|
-
const [
|
|
1812
|
+
const [akanConfig, scanner, pkgs, libs] = await Promise.all([
|
|
1085
1813
|
exec.getConfig(),
|
|
1086
1814
|
createDependencyScanner(exec),
|
|
1087
1815
|
exec.workspace.getPkgs(),
|
|
@@ -1117,7 +1845,7 @@ class ScanInfo {
|
|
|
1117
1845
|
});
|
|
1118
1846
|
await Promise.all([
|
|
1119
1847
|
...databaseDirs.map(async (name) => {
|
|
1120
|
-
const filenames = await exec.readdir(
|
|
1848
|
+
const filenames = await exec.readdir(path7.join("lib", name));
|
|
1121
1849
|
filenames.forEach((filename) => {
|
|
1122
1850
|
if (filename.endsWith(".constant.ts"))
|
|
1123
1851
|
files.constant.databases.push(name);
|
|
@@ -1145,7 +1873,7 @@ class ScanInfo {
|
|
|
1145
1873
|
}),
|
|
1146
1874
|
...serviceDirs.map(async (dirname) => {
|
|
1147
1875
|
const name = dirname.slice(1);
|
|
1148
|
-
const filenames = await exec.readdir(
|
|
1876
|
+
const filenames = await exec.readdir(path7.join("lib", dirname));
|
|
1149
1877
|
filenames.forEach((filename) => {
|
|
1150
1878
|
if (filename.endsWith(".dictionary.ts"))
|
|
1151
1879
|
files.dictionary.services.push(name);
|
|
@@ -1168,7 +1896,7 @@ class ScanInfo {
|
|
|
1168
1896
|
});
|
|
1169
1897
|
}),
|
|
1170
1898
|
...scalarDirs.map(async (name) => {
|
|
1171
|
-
const filenames = await exec.readdir(
|
|
1899
|
+
const filenames = await exec.readdir(path7.join("lib/__scalar", name));
|
|
1172
1900
|
filenames.forEach((filename) => {
|
|
1173
1901
|
if (filename.endsWith(".constant.ts"))
|
|
1174
1902
|
files.constant.scalars.push(name);
|
|
@@ -1190,12 +1918,11 @@ class ScanInfo {
|
|
|
1190
1918
|
})
|
|
1191
1919
|
]);
|
|
1192
1920
|
const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
|
|
1193
|
-
const
|
|
1921
|
+
const common = {
|
|
1194
1922
|
name: exec.name,
|
|
1195
1923
|
type: exec.type,
|
|
1196
1924
|
repoName: exec.workspace.repoName,
|
|
1197
|
-
serveDomain: WorkspaceExecutor.getBaseDevEnv(
|
|
1198
|
-
akanConfig: akanConfig2,
|
|
1925
|
+
serveDomain: WorkspaceExecutor.getBaseDevEnv(path7.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
|
|
1199
1926
|
files,
|
|
1200
1927
|
libDeps,
|
|
1201
1928
|
pkgDeps,
|
|
@@ -1203,6 +1930,7 @@ class ScanInfo {
|
|
|
1203
1930
|
devDependencies: npmDevDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
|
|
1204
1931
|
routes
|
|
1205
1932
|
};
|
|
1933
|
+
const scanResult = akanConfig instanceof AkanAppConfig ? { ...common, akanConfig } : { ...common, akanConfig };
|
|
1206
1934
|
return scanResult;
|
|
1207
1935
|
}
|
|
1208
1936
|
constructor(scanResult) {
|
|
@@ -1396,16 +2124,16 @@ class LibInfo extends ScanInfo {
|
|
|
1396
2124
|
class PkgInfo {
|
|
1397
2125
|
exec;
|
|
1398
2126
|
name;
|
|
1399
|
-
scanResult;
|
|
2127
|
+
#scanResult;
|
|
1400
2128
|
static async scanExecutor(exec) {
|
|
1401
2129
|
const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
|
|
1402
2130
|
const scanner = await createDependencyScanner(exec);
|
|
1403
2131
|
const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
|
|
1404
|
-
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("/*", "")));
|
|
1405
2133
|
const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
|
|
1406
|
-
const pkgDeps = [...pkgPathDepSet].map((
|
|
1407
|
-
const pathSplitLength =
|
|
1408
|
-
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("/");
|
|
1409
2137
|
}).filter((pkg) => pkg !== exec.name);
|
|
1410
2138
|
const pkgScanResult = {
|
|
1411
2139
|
name: exec.name,
|
|
@@ -1427,10 +2155,10 @@ class PkgInfo {
|
|
|
1427
2155
|
constructor(exec, scanResult) {
|
|
1428
2156
|
this.exec = exec;
|
|
1429
2157
|
this.name = exec.name;
|
|
1430
|
-
this
|
|
2158
|
+
this.#scanResult = scanResult;
|
|
1431
2159
|
}
|
|
1432
2160
|
getScanResult() {
|
|
1433
|
-
return this
|
|
2161
|
+
return this.#scanResult;
|
|
1434
2162
|
}
|
|
1435
2163
|
}
|
|
1436
2164
|
|
|
@@ -1591,16 +2319,16 @@ class Executor {
|
|
|
1591
2319
|
linter = null;
|
|
1592
2320
|
constructor(name, cwdPath) {
|
|
1593
2321
|
this.name = name;
|
|
1594
|
-
this.logger = new
|
|
2322
|
+
this.logger = new Logger2(name);
|
|
1595
2323
|
this.logs = [];
|
|
1596
2324
|
this.cwdPath = cwdPath;
|
|
1597
2325
|
}
|
|
1598
2326
|
#stdout(data) {
|
|
1599
2327
|
if (Executor.verbose)
|
|
1600
|
-
|
|
2328
|
+
Logger2.raw(chalk2.dim(data.toString()));
|
|
1601
2329
|
}
|
|
1602
2330
|
#stderr(data) {
|
|
1603
|
-
|
|
2331
|
+
Logger2.raw(chalk2.red(data.toString()));
|
|
1604
2332
|
}
|
|
1605
2333
|
exec(command, options = {}) {
|
|
1606
2334
|
const cwd = options.cwd?.toString() ?? this.cwdPath;
|
|
@@ -1628,7 +2356,7 @@ class Executor {
|
|
|
1628
2356
|
}));
|
|
1629
2357
|
});
|
|
1630
2358
|
proc.on("exit", (code, signal) => {
|
|
1631
|
-
if (
|
|
2359
|
+
if (code || signal)
|
|
1632
2360
|
reject(new CommandExecutionError({
|
|
1633
2361
|
command,
|
|
1634
2362
|
cwd,
|
|
@@ -1726,7 +2454,7 @@ class Executor {
|
|
|
1726
2454
|
}));
|
|
1727
2455
|
});
|
|
1728
2456
|
proc.on("exit", (code, signal) => {
|
|
1729
|
-
if (
|
|
2457
|
+
if (code || signal)
|
|
1730
2458
|
reject(new CommandExecutionError({
|
|
1731
2459
|
command: modulePath,
|
|
1732
2460
|
args,
|
|
@@ -1742,10 +2470,10 @@ class Executor {
|
|
|
1742
2470
|
});
|
|
1743
2471
|
}
|
|
1744
2472
|
getPath(filePath) {
|
|
1745
|
-
if (
|
|
2473
|
+
if (path8.isAbsolute(filePath))
|
|
1746
2474
|
return filePath;
|
|
1747
2475
|
if (filePath.startsWith("."))
|
|
1748
|
-
return
|
|
2476
|
+
return path8.join(this.cwdPath, filePath);
|
|
1749
2477
|
const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
|
|
1750
2478
|
const targetParts = filePath.split(/[\\/]/).filter(Boolean);
|
|
1751
2479
|
let overlapLength = 0;
|
|
@@ -1759,12 +2487,12 @@ class Executor {
|
|
|
1759
2487
|
if (isOverlap)
|
|
1760
2488
|
overlapLength = i;
|
|
1761
2489
|
}
|
|
1762
|
-
return
|
|
2490
|
+
return path8.join(this.cwdPath, ...targetParts.slice(overlapLength));
|
|
1763
2491
|
}
|
|
1764
2492
|
async mkdir(dirPath) {
|
|
1765
2493
|
const writePath = this.getPath(dirPath);
|
|
1766
2494
|
if (!await FileSys.dirExists(writePath))
|
|
1767
|
-
await
|
|
2495
|
+
await mkdir3(writePath, { recursive: true });
|
|
1768
2496
|
this.logger.verbose(`Make directory ${writePath}`);
|
|
1769
2497
|
return this;
|
|
1770
2498
|
}
|
|
@@ -1802,18 +2530,19 @@ class Executor {
|
|
|
1802
2530
|
return this;
|
|
1803
2531
|
}
|
|
1804
2532
|
async removeDir(dirPath) {
|
|
1805
|
-
const readPath =
|
|
2533
|
+
const readPath = path8.join(this.getPath(dirPath), ".");
|
|
1806
2534
|
if (await FileSys.entryExists(readPath))
|
|
1807
|
-
await
|
|
2535
|
+
await rm3(readPath, { recursive: true, force: true });
|
|
1808
2536
|
this.logger.verbose(`Remove directory ${readPath}`);
|
|
1809
2537
|
return this;
|
|
1810
2538
|
}
|
|
1811
2539
|
async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
|
|
1812
2540
|
const writePath = this.getPath(filePath);
|
|
1813
|
-
const dir =
|
|
2541
|
+
const dir = path8.dirname(writePath);
|
|
1814
2542
|
if (!await FileSys.dirExists(dir))
|
|
1815
|
-
await
|
|
1816
|
-
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
|
+
`;
|
|
1817
2546
|
if (await FileSys.fileExists(writePath)) {
|
|
1818
2547
|
const currentContent = await FileSys.readText(writePath);
|
|
1819
2548
|
if (currentContent === contentStr || !overwrite) {
|
|
@@ -1821,7 +2550,7 @@ class Executor {
|
|
|
1821
2550
|
contentStr = currentContent;
|
|
1822
2551
|
} else {
|
|
1823
2552
|
await FileSys.writeText(writePath, contentStr);
|
|
1824
|
-
if (
|
|
2553
|
+
if (Logger2.isVerbose())
|
|
1825
2554
|
this.logger.rawLog(chalk2.yellow(`File Update: ${filePath}`));
|
|
1826
2555
|
}
|
|
1827
2556
|
} else {
|
|
@@ -1835,7 +2564,7 @@ class Executor {
|
|
|
1835
2564
|
await this.writeFile(filePath, content);
|
|
1836
2565
|
}
|
|
1837
2566
|
async getLocalFile(targetPath) {
|
|
1838
|
-
const filePath =
|
|
2567
|
+
const filePath = path8.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
|
|
1839
2568
|
const content = await this.readFile(filePath);
|
|
1840
2569
|
return { filePath, content };
|
|
1841
2570
|
}
|
|
@@ -1852,9 +2581,9 @@ class Executor {
|
|
|
1852
2581
|
const dest = this.getPath(destPath);
|
|
1853
2582
|
if (!await FileSys.exists(src))
|
|
1854
2583
|
return;
|
|
1855
|
-
const isDirectory = (await
|
|
2584
|
+
const isDirectory = (await stat2(src)).isDirectory();
|
|
1856
2585
|
if (!await FileSys.exists(dest) && isDirectory)
|
|
1857
|
-
await
|
|
2586
|
+
await mkdir3(dest, { recursive: true });
|
|
1858
2587
|
if (dereference)
|
|
1859
2588
|
await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
|
|
1860
2589
|
else
|
|
@@ -1932,26 +2661,26 @@ class Executor {
|
|
|
1932
2661
|
const result = await getContent.default(scanInfo ?? null, dict, options);
|
|
1933
2662
|
if (result === null)
|
|
1934
2663
|
return null;
|
|
1935
|
-
const filename = typeof result === "object" ? result.filename :
|
|
2664
|
+
const filename = typeof result === "object" ? result.filename : path8.basename(targetPath).replace(".js", ".ts");
|
|
1936
2665
|
const content = typeof result === "object" ? result.content : result;
|
|
1937
|
-
const dirname =
|
|
1938
|
-
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}`);
|
|
1939
2668
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1940
2669
|
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
1941
2670
|
} else if (targetPath.endsWith(".template")) {
|
|
1942
2671
|
const content = await FileSys.readText(templatePath);
|
|
1943
|
-
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));
|
|
1944
2673
|
const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
|
|
1945
2674
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1946
2675
|
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
1947
2676
|
overwrite
|
|
1948
2677
|
});
|
|
1949
|
-
} else if (staticTemplateFileExtensions.has(
|
|
1950
|
-
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);
|
|
1951
2680
|
const writePath = this.getPath(convertedTargetPath);
|
|
1952
|
-
const dirname =
|
|
2681
|
+
const dirname = path8.dirname(writePath);
|
|
1953
2682
|
if (!await FileSys.dirExists(dirname))
|
|
1954
|
-
await
|
|
2683
|
+
await mkdir3(dirname, { recursive: true });
|
|
1955
2684
|
await copyFile(templatePath, writePath);
|
|
1956
2685
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1957
2686
|
return { filePath: writePath, content: "" };
|
|
@@ -1969,11 +2698,11 @@ class Executor {
|
|
|
1969
2698
|
const templateRoot = await this.#resolveTemplateRoot();
|
|
1970
2699
|
const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
|
|
1971
2700
|
const prefixTemplatePath = templatePath;
|
|
1972
|
-
if ((await
|
|
1973
|
-
const filename =
|
|
2701
|
+
if ((await stat2(prefixTemplatePath)).isFile()) {
|
|
2702
|
+
const filename = path8.basename(prefixTemplatePath);
|
|
1974
2703
|
const fileContent = await this.#applyTemplateFile({
|
|
1975
2704
|
templatePath: prefixTemplatePath,
|
|
1976
|
-
targetPath:
|
|
2705
|
+
targetPath: path8.join(basePath, filename),
|
|
1977
2706
|
scanInfo,
|
|
1978
2707
|
overwrite
|
|
1979
2708
|
}, dict, options);
|
|
@@ -1981,19 +2710,19 @@ class Executor {
|
|
|
1981
2710
|
} else {
|
|
1982
2711
|
const subdirs = await readDirEntries(templatePath);
|
|
1983
2712
|
const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
|
|
1984
|
-
const subpath =
|
|
1985
|
-
if ((await
|
|
2713
|
+
const subpath = path8.join(templatePath, subdir);
|
|
2714
|
+
if ((await stat2(subpath)).isFile()) {
|
|
1986
2715
|
const fileContent = await this.#applyTemplateFile({
|
|
1987
2716
|
templatePath: subpath,
|
|
1988
|
-
targetPath:
|
|
2717
|
+
targetPath: path8.join(basePath, subdir),
|
|
1989
2718
|
scanInfo,
|
|
1990
2719
|
overwrite
|
|
1991
2720
|
}, dict, options);
|
|
1992
2721
|
return fileContent ? [fileContent] : [];
|
|
1993
2722
|
} else
|
|
1994
2723
|
return await this._applyTemplate({
|
|
1995
|
-
basePath:
|
|
1996
|
-
template:
|
|
2724
|
+
basePath: path8.join(basePath, subdir),
|
|
2725
|
+
template: path8.join(template, subdir),
|
|
1997
2726
|
scanInfo,
|
|
1998
2727
|
dict,
|
|
1999
2728
|
overwrite,
|
|
@@ -2005,8 +2734,8 @@ class Executor {
|
|
|
2005
2734
|
}
|
|
2006
2735
|
async#resolveTemplateRoot() {
|
|
2007
2736
|
const candidates = [
|
|
2008
|
-
|
|
2009
|
-
|
|
2737
|
+
path8.resolve(getDirname2(import.meta.url), "templates"),
|
|
2738
|
+
path8.resolve(getDirname2(import.meta.url), "../cli/templates")
|
|
2010
2739
|
];
|
|
2011
2740
|
for (const candidate of candidates) {
|
|
2012
2741
|
if (await FileSys.dirExists(candidate))
|
|
@@ -2019,29 +2748,43 @@ class Executor {
|
|
|
2019
2748
|
...options.dict ?? {},
|
|
2020
2749
|
...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
|
|
2021
2750
|
};
|
|
2022
|
-
|
|
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
|
+
}
|
|
2023
2766
|
}
|
|
2024
2767
|
async getTypeChecker() {
|
|
2025
|
-
const { TypeChecker } = await import("./typeChecker-
|
|
2768
|
+
const { TypeChecker } = await import("./typeChecker-3xgrzc2v.js");
|
|
2026
2769
|
this.typeChecker ??= new TypeChecker(this);
|
|
2027
2770
|
return this.typeChecker;
|
|
2028
2771
|
}
|
|
2029
2772
|
async typeCheck(filePath) {
|
|
2030
|
-
const
|
|
2773
|
+
const path = this.getPath(filePath);
|
|
2031
2774
|
const typeChecker = await this.getTypeChecker();
|
|
2032
|
-
const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(
|
|
2775
|
+
const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path);
|
|
2033
2776
|
const message = typeChecker.formatDiagnostics(fileDiagnostics);
|
|
2034
2777
|
return { fileDiagnostics, fileErrors, fileWarnings, message };
|
|
2035
2778
|
}
|
|
2036
2779
|
async typeCheckAsync(filePath) {
|
|
2037
|
-
const
|
|
2780
|
+
const path = this.getPath(filePath);
|
|
2038
2781
|
const entry = await this.#resolveTypecheckWorkerEntry();
|
|
2039
2782
|
const proc = Bun.spawn([process.execPath, entry], {
|
|
2040
2783
|
cwd: this.cwdPath,
|
|
2041
2784
|
env: {
|
|
2042
2785
|
...process.env,
|
|
2043
2786
|
AKAN_TYPECHECK_CWD: this.cwdPath,
|
|
2044
|
-
AKAN_TYPECHECK_FILE:
|
|
2787
|
+
AKAN_TYPECHECK_FILE: path
|
|
2045
2788
|
},
|
|
2046
2789
|
stdout: "pipe",
|
|
2047
2790
|
stderr: "pipe"
|
|
@@ -2062,13 +2805,13 @@ class Executor {
|
|
|
2062
2805
|
};
|
|
2063
2806
|
}
|
|
2064
2807
|
async#resolveTypecheckWorkerEntry() {
|
|
2065
|
-
const dirname =
|
|
2808
|
+
const dirname = getDirname2(import.meta.url);
|
|
2066
2809
|
const candidates = [
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
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")
|
|
2072
2815
|
];
|
|
2073
2816
|
for (const candidate of candidates)
|
|
2074
2817
|
if (await Bun.file(candidate).exists())
|
|
@@ -2080,9 +2823,9 @@ class Executor {
|
|
|
2080
2823
|
return this.linter;
|
|
2081
2824
|
}
|
|
2082
2825
|
async lint(filePath, { fix = false, dryRun = false } = {}) {
|
|
2083
|
-
const
|
|
2826
|
+
const path = this.getPath(filePath);
|
|
2084
2827
|
const linter = this.getLinter();
|
|
2085
|
-
const { results, errors, warnings } = await linter.lint(
|
|
2828
|
+
const { results, errors, warnings } = await linter.lint(path, {
|
|
2086
2829
|
fix,
|
|
2087
2830
|
dryRun
|
|
2088
2831
|
});
|
|
@@ -2103,7 +2846,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
2103
2846
|
static #execs = new Map;
|
|
2104
2847
|
static fromRoot({
|
|
2105
2848
|
workspaceRoot = process.cwd(),
|
|
2106
|
-
repoName =
|
|
2849
|
+
repoName = resolveRepoName(workspaceRoot)
|
|
2107
2850
|
} = {}) {
|
|
2108
2851
|
return WorkspaceExecutor.#execs.get(repoName) ?? new WorkspaceExecutor({ workspaceRoot, repoName });
|
|
2109
2852
|
}
|
|
@@ -2195,8 +2938,8 @@ class WorkspaceExecutor extends Executor {
|
|
|
2195
2938
|
await Promise.all(dirs.map(async (dir) => {
|
|
2196
2939
|
if (dir.includes("_") || AVOID_DIRS.includes(dir))
|
|
2197
2940
|
return;
|
|
2198
|
-
const dirPath =
|
|
2199
|
-
if ((await
|
|
2941
|
+
const dirPath = path8.join(dirname, dir);
|
|
2942
|
+
if ((await stat2(dirPath)).isDirectory()) {
|
|
2200
2943
|
results.push(`${prefix}${dir}`);
|
|
2201
2944
|
if (maxDepth > 0)
|
|
2202
2945
|
await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
|
|
@@ -2208,10 +2951,20 @@ class WorkspaceExecutor extends Executor {
|
|
|
2208
2951
|
}
|
|
2209
2952
|
async commit(message, { init = false, add = true } = {}) {
|
|
2210
2953
|
if (init)
|
|
2211
|
-
await this.
|
|
2954
|
+
await this.spawn("git", ["init", "--quiet"]);
|
|
2212
2955
|
if (add)
|
|
2213
|
-
await this.
|
|
2214
|
-
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();
|
|
2215
2968
|
}
|
|
2216
2969
|
async#getDirHasFile(basePath, targetFilename) {
|
|
2217
2970
|
const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
|
|
@@ -2220,9 +2973,9 @@ class WorkspaceExecutor extends Executor {
|
|
|
2220
2973
|
await Promise.all(dirs.map(async (dir) => {
|
|
2221
2974
|
if (AVOID_DIRS.includes(dir))
|
|
2222
2975
|
return;
|
|
2223
|
-
const dirPath =
|
|
2976
|
+
const dirPath = path8.join(dirname, dir);
|
|
2224
2977
|
if (await FileSys.dirExists(dirPath)) {
|
|
2225
|
-
const hasTargetFile = await FileSys.fileExists(
|
|
2978
|
+
const hasTargetFile = await FileSys.fileExists(path8.join(dirPath, targetFilename));
|
|
2226
2979
|
if (hasTargetFile)
|
|
2227
2980
|
results.push(`${prefix}${dir}`);
|
|
2228
2981
|
if (maxDepth > 0)
|
|
@@ -2288,8 +3041,20 @@ class SysExecutor extends Executor {
|
|
|
2288
3041
|
return this.#akanConfig;
|
|
2289
3042
|
}
|
|
2290
3043
|
async getModules() {
|
|
2291
|
-
const
|
|
2292
|
-
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;
|
|
2293
3058
|
}
|
|
2294
3059
|
#scanInfo = null;
|
|
2295
3060
|
hasScanInfo() {
|
|
@@ -2347,18 +3112,19 @@ class SysExecutor extends Executor {
|
|
|
2347
3112
|
}) : await LibInfo.fromExecutor(this, {
|
|
2348
3113
|
refresh
|
|
2349
3114
|
});
|
|
2350
|
-
if (write)
|
|
2351
|
-
await
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
this
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
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
|
+
});
|
|
2362
3128
|
this.#scanInfo = scanInfo;
|
|
2363
3129
|
return scanInfo;
|
|
2364
3130
|
}
|
|
@@ -2393,7 +3159,7 @@ class SysExecutor extends Executor {
|
|
|
2393
3159
|
await this.setPackageJson(libPkgJsonWithDeps);
|
|
2394
3160
|
}
|
|
2395
3161
|
async getLocalFile(targetPath) {
|
|
2396
|
-
const filePath =
|
|
3162
|
+
const filePath = path8.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
|
|
2397
3163
|
const content = await this.workspace.readFile(filePath);
|
|
2398
3164
|
return { filePath, content };
|
|
2399
3165
|
}
|
|
@@ -2479,7 +3245,7 @@ class SysExecutor extends Executor {
|
|
|
2479
3245
|
class AppExecutor extends SysExecutor {
|
|
2480
3246
|
static #routeSourceValidator = null;
|
|
2481
3247
|
static async#getRouteSourceValidator() {
|
|
2482
|
-
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-
|
|
3248
|
+
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-ze6x894c.js")).RouteSourceValidator;
|
|
2483
3249
|
return AppExecutor.#routeSourceValidator;
|
|
2484
3250
|
}
|
|
2485
3251
|
dist;
|
|
@@ -2490,9 +3256,9 @@ class AppExecutor extends SysExecutor {
|
|
|
2490
3256
|
}
|
|
2491
3257
|
static #execs = new Map;
|
|
2492
3258
|
static from(executor, name) {
|
|
2493
|
-
const
|
|
2494
|
-
if (
|
|
2495
|
-
return
|
|
3259
|
+
const exec = AppExecutor.#execs.get(name);
|
|
3260
|
+
if (exec)
|
|
3261
|
+
return exec;
|
|
2496
3262
|
else if (executor instanceof WorkspaceExecutor)
|
|
2497
3263
|
return new AppExecutor({ workspace: executor, name });
|
|
2498
3264
|
else
|
|
@@ -2528,33 +3294,43 @@ class AppExecutor extends SysExecutor {
|
|
|
2528
3294
|
};
|
|
2529
3295
|
}
|
|
2530
3296
|
async prepareCommand(type) {
|
|
2531
|
-
const
|
|
2532
|
-
const databaseMode = process.env.AKAN_DATABASE_MODE ??
|
|
3297
|
+
const akanConfig = await this.getConfig();
|
|
3298
|
+
const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
|
|
2533
3299
|
const routeEnv = {
|
|
2534
|
-
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,
|
|
2535
3303
|
AKAN_DATABASE_MODE: databaseMode
|
|
2536
3304
|
};
|
|
2537
3305
|
Object.assign(process.env, routeEnv);
|
|
2538
3306
|
if (type === "build") {
|
|
2539
3307
|
this.#excludeDevOnlyPages = true;
|
|
2540
3308
|
this.#pageKeys = null;
|
|
2541
|
-
|
|
2542
|
-
await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
|
|
3309
|
+
await this.dist.removeDir(this.dist.cwdPath);
|
|
2543
3310
|
await Promise.all([this.dist.mkdir("private"), this.dist.mkdir("public")]);
|
|
2544
3311
|
await Promise.all([
|
|
2545
3312
|
this.cp("private", `${this.dist.cwdPath}/private`, { dereference: true }),
|
|
2546
|
-
this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })
|
|
3313
|
+
...akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []
|
|
2547
3314
|
]);
|
|
2548
|
-
} else
|
|
3315
|
+
} else {
|
|
2549
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";
|
|
2550
3322
|
const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
|
|
2551
3323
|
const env = this.getCommandEnv({
|
|
2552
3324
|
AKAN_COMMAND_TYPE: type,
|
|
2553
3325
|
...routeEnv,
|
|
2554
3326
|
...devPort ? { PORT: devPort, AKAN_PUBLIC_CLIENT_PORT: devPort, AKAN_PUBLIC_SERVER_PORT: devPort } : {}
|
|
2555
3327
|
});
|
|
2556
|
-
if (type === "build")
|
|
2557
|
-
|
|
3328
|
+
if (type === "build") {
|
|
3329
|
+
const buildEnv = { ...env };
|
|
3330
|
+
delete buildEnv.AKAN_PUBLIC_CLIENT_PORT;
|
|
3331
|
+
delete buildEnv.AKAN_PUBLIC_SERVER_PORT;
|
|
3332
|
+
Object.assign(process.env, buildEnv);
|
|
3333
|
+
}
|
|
2558
3334
|
return { env };
|
|
2559
3335
|
}
|
|
2560
3336
|
#publicEnv = null;
|
|
@@ -2594,7 +3370,7 @@ class AppExecutor extends SysExecutor {
|
|
|
2594
3370
|
async getPageKeys({ refresh } = {}) {
|
|
2595
3371
|
if (this.#pageKeys && !refresh)
|
|
2596
3372
|
return this.#pageKeys;
|
|
2597
|
-
const
|
|
3373
|
+
const akanConfig = await this.getConfig();
|
|
2598
3374
|
const glob = new Bun.Glob("**/*");
|
|
2599
3375
|
const pageKeys = [];
|
|
2600
3376
|
const owners = new Map;
|
|
@@ -2608,16 +3384,16 @@ class AppExecutor extends SysExecutor {
|
|
|
2608
3384
|
absolute: false,
|
|
2609
3385
|
onlyFiles: true
|
|
2610
3386
|
})) {
|
|
2611
|
-
const segments = rel.split(
|
|
3387
|
+
const segments = rel.split(path8.sep);
|
|
2612
3388
|
if (segments.some((s) => s === "node_modules"))
|
|
2613
3389
|
continue;
|
|
2614
3390
|
const posix = `${root.keyPrefix}${segments.join("/")}`;
|
|
2615
|
-
const absPath =
|
|
3391
|
+
const absPath = path8.join(root.dir, ...segments);
|
|
2616
3392
|
validatePageSourceFile(posix, { filePath: absPath });
|
|
2617
3393
|
if (!isRouteSourceFile(posix))
|
|
2618
3394
|
continue;
|
|
2619
3395
|
const key = `./${posix}`;
|
|
2620
|
-
validateSubRoutePageKey(key,
|
|
3396
|
+
validateSubRoutePageKey(key, akanConfig.basePaths, {
|
|
2621
3397
|
appName: this.name,
|
|
2622
3398
|
filePath: absPath
|
|
2623
3399
|
});
|
|
@@ -2635,7 +3411,8 @@ class AppExecutor extends SysExecutor {
|
|
|
2635
3411
|
}
|
|
2636
3412
|
if (!owner)
|
|
2637
3413
|
owners.set(routeId, { absPath, fromLib });
|
|
2638
|
-
const
|
|
3414
|
+
const layoutSegments = getRootBoundarySegments(key);
|
|
3415
|
+
const isRootLayout = layoutSegments !== null && isRootBoundarySegments(layoutSegments, akanConfig.basePaths);
|
|
2639
3416
|
const routeSource = await Bun.file(absPath).text();
|
|
2640
3417
|
const validator = await AppExecutor.#getRouteSourceValidator();
|
|
2641
3418
|
if (parsed.kind === "overrides")
|
|
@@ -2666,10 +3443,10 @@ class AppExecutor extends SysExecutor {
|
|
|
2666
3443
|
return pageKeys.filter((key) => !isDevOnly(key));
|
|
2667
3444
|
}
|
|
2668
3445
|
async getPageRoots() {
|
|
2669
|
-
const
|
|
3446
|
+
const akanConfig = await this.getConfig();
|
|
2670
3447
|
const pageDir = `${this.cwdPath}/page`;
|
|
2671
3448
|
const roots = [{ dir: pageDir, realDir: pageDir, keyPrefix: "" }];
|
|
2672
|
-
for (const parent of AppExecutor.#pageLibParents(
|
|
3449
|
+
for (const parent of AppExecutor.#pageLibParents(akanConfig.basePaths)) {
|
|
2673
3450
|
const libsDir = `${pageDir}/${parent}${AppExecutor.#pageLibsDir}`;
|
|
2674
3451
|
const entries = await readDirEntries(libsDir).catch(() => []);
|
|
2675
3452
|
for (const entry of entries.sort()) {
|
|
@@ -2690,9 +3467,9 @@ class AppExecutor extends SysExecutor {
|
|
|
2690
3467
|
return parents.length ? parents : [""];
|
|
2691
3468
|
}
|
|
2692
3469
|
async syncPages(libDeps) {
|
|
2693
|
-
const
|
|
2694
|
-
const parents = AppExecutor.#pageLibParents(
|
|
2695
|
-
const libs = await this.#resolvePageLibs(
|
|
3470
|
+
const akanConfig = await this.getConfig();
|
|
3471
|
+
const parents = AppExecutor.#pageLibParents(akanConfig.basePaths);
|
|
3472
|
+
const libs = await this.#resolvePageLibs(akanConfig.syncPageLibs, libDeps);
|
|
2696
3473
|
const linked = (await Promise.all(parents.map(async (parent) => {
|
|
2697
3474
|
const libsDir = `${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`;
|
|
2698
3475
|
const entries = await readDirEntries(libsDir).catch(() => []);
|
|
@@ -2750,12 +3527,12 @@ class AppExecutor extends SysExecutor {
|
|
|
2750
3527
|
static async#linkLibAsset(targetPath, linkPath) {
|
|
2751
3528
|
const isWindows = process.platform === "win32";
|
|
2752
3529
|
try {
|
|
2753
|
-
const target = isWindows ? targetPath :
|
|
3530
|
+
const target = isWindows ? targetPath : path8.relative(path8.dirname(linkPath), targetPath);
|
|
2754
3531
|
await symlink(target, linkPath, isWindows ? "junction" : "dir");
|
|
2755
3532
|
} catch (error) {
|
|
2756
3533
|
if (!isWindows)
|
|
2757
3534
|
throw error;
|
|
2758
|
-
await
|
|
3535
|
+
await mkdir3(linkPath, { recursive: true });
|
|
2759
3536
|
await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
|
|
2760
3537
|
}
|
|
2761
3538
|
}
|
|
@@ -2792,7 +3569,7 @@ class AppExecutor extends SysExecutor {
|
|
|
2792
3569
|
await this.writeFile(rel, content, opts);
|
|
2793
3570
|
},
|
|
2794
3571
|
readEnvClient: async () => {
|
|
2795
|
-
const envClientPath =
|
|
3572
|
+
const envClientPath = path8.join(this.cwdPath, "env", "env.client.ts");
|
|
2796
3573
|
if (!await FileSys.fileExists(envClientPath))
|
|
2797
3574
|
return null;
|
|
2798
3575
|
try {
|
|
@@ -2851,9 +3628,9 @@ class LibExecutor extends SysExecutor {
|
|
|
2851
3628
|
}
|
|
2852
3629
|
static #execs = new Map;
|
|
2853
3630
|
static from(executor, name) {
|
|
2854
|
-
const
|
|
2855
|
-
if (
|
|
2856
|
-
return
|
|
3631
|
+
const exec = LibExecutor.#execs.get(name);
|
|
3632
|
+
if (exec)
|
|
3633
|
+
return exec;
|
|
2857
3634
|
else if (executor instanceof WorkspaceExecutor)
|
|
2858
3635
|
return new LibExecutor({ workspace: executor, name });
|
|
2859
3636
|
else
|
|
@@ -2863,7 +3640,7 @@ class LibExecutor extends SysExecutor {
|
|
|
2863
3640
|
async getConfig({ refresh } = {}) {
|
|
2864
3641
|
if (this.#akanConfig && !refresh)
|
|
2865
3642
|
return this.#akanConfig;
|
|
2866
|
-
this.#akanConfig = await AkanLibConfig.from(this);
|
|
3643
|
+
this.#akanConfig = await AkanLibConfig.from(this, { bustImportCache: refresh });
|
|
2867
3644
|
return this.#akanConfig;
|
|
2868
3645
|
}
|
|
2869
3646
|
}
|
|
@@ -2902,7 +3679,7 @@ class PkgExecutor extends Executor {
|
|
|
2902
3679
|
return rootVersion;
|
|
2903
3680
|
try {
|
|
2904
3681
|
const packageJsonPath = `pkgs/${dep}/package.json`;
|
|
2905
|
-
if (!await Bun.file(
|
|
3682
|
+
if (!await Bun.file(path8.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
|
|
2906
3683
|
return;
|
|
2907
3684
|
const packageJson = await this.workspace.readJson(packageJsonPath);
|
|
2908
3685
|
if (packageJson.name === dep)
|
|
@@ -2960,7 +3737,7 @@ class PkgExecutor extends Executor {
|
|
|
2960
3737
|
default: "./index.ts"
|
|
2961
3738
|
}
|
|
2962
3739
|
},
|
|
2963
|
-
engines: { bun: ">=1.
|
|
3740
|
+
engines: { bun: ">=1.4.0" },
|
|
2964
3741
|
...dependencyMaps
|
|
2965
3742
|
};
|
|
2966
3743
|
await Promise.all([this.dist.writeJson("package.json", distPkgJson), this.writeJson("package.json", distPkgJson)]);
|
|
@@ -3003,4 +3780,4 @@ class ModuleExecutor extends Executor {
|
|
|
3003
3780
|
}
|
|
3004
3781
|
}
|
|
3005
3782
|
|
|
3006
|
-
export { AGENT_BLOCK_START, AGENT_BLOCK_END, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock,
|
|
3783
|
+
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 };
|