@akanjs/cli 3.0.0-alpha.8 → 3.0.0-alpha.80
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-pwc6d6yk.js} +3 -5
- package/{agent.command-pggr92jj.js → agent.command-180hh0ye.js} +11 -12
- package/{application.command-692yap1d.js → application.command-gmcgen9y.js} +59 -35
- package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-tavpx3fv.js} +22 -20
- package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-ktjt0erc.js} +8 -9
- package/buildBatch.proc.js +12 -13
- package/capacitorApp-kwa67kzs.js +12 -0
- package/{cloud.command-wqfsyzv2.js → cloud.command-gpq7qxrc.js} +15 -17
- package/commandManifest.json +1 -1
- package/{context.command-9bxtdn0w.js → context.command-dk2m2sjx.js} +19 -22
- package/dependencyScanner-c64m6fxp.js +8 -0
- package/fleet.command-3bkyyd3a.js +836 -0
- package/getDirname-dr3jz1xg.js +7 -0
- package/{guideline.command-nfqahxex.js → guideline.command-w6ddv13r.js} +16 -17
- package/guidelines/agentRule/agentRule.generate.json +31 -0
- package/guidelines/agentRule/agentRule.instruction.md +525 -0
- package/guidelines/conventions/conventions.generate.json +25 -0
- package/guidelines/conventions/conventions.instruction.md +847 -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/queryRule/queryRule.generate.json +31 -0
- package/guidelines/queryRule/queryRule.instruction.md +143 -0
- package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
- package/guidelines/runtimeRule/runtimeRule.instruction.md +176 -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 +21 -22
- package/{index-4hek879p.js → index-1kzsn94d.js} +1 -1
- package/{index-p3f4q532.js → index-3c9n3nt8.js} +31 -30
- package/index-3d9y066c.js +76 -0
- package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
- package/{index-gex7c8kd.js → index-5n0hf8r9.js} +65 -38
- package/{index-mssqn2sq.js → index-5y7qba1c.js} +79 -33
- package/index-6wwzs1bc.js +7 -0
- package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
- package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
- package/{index-azv0xjb5.js → index-a8h184wr.js} +15 -11
- package/{index-67546d0j.js → index-ayy5ecax.js} +1 -1
- package/{index-xhgtkekh.js → index-b2nt0w0k.js} +27 -27
- package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
- package/{index-bz2sfzzt.js → index-cd5x18qh.js} +32 -32
- package/{index-bjpxzr6s.js → index-ctphds8v.js} +1 -1
- package/{index-w6wbb612.js → index-dq6wq1tv.js} +94 -61
- package/{index-js368gk9.js → index-ee8z8adx.js} +13 -13
- package/{index-5dw57f0d.js → index-f7wewj2v.js} +155 -76
- package/{index-6rhabeev.js → index-ft1sfjz0.js} +36 -25
- package/{index-7myvn0f0.js → index-h7yw2z9n.js} +7 -7
- package/{index-5pmx7bkg.js → index-j9vnq8tr.js} +18 -18
- package/{index-vg0sj9nc.js → index-k1n0h7dk.js} +840 -201
- package/{index-vtj3zps1.js → index-k86zc21w.js} +57 -57
- package/{index-46tjzh6s.js → index-mfm35dhh.js} +29 -29
- package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
- package/{index-ztpw544k.js → index-qcracvc9.js} +8 -8
- package/{index-2c3e2xh8.js → index-saheq2bs.js} +4 -4
- package/{index-gmy45vz2.js → index-v0s24t8j.js} +14 -14
- package/{index-0cj2zxbm.js → index-wet2ytnr.js} +12 -11
- package/{index-j9cxndc0.js → index-x23688ar.js} +12 -7
- package/index-xnhf8tr9.js +109 -0
- package/{index-a1e0j63x.js → index-xxe90kqy.js} +58 -25
- package/{index-qb61h0k7.js → index-z3c9mmw4.js} +1098 -1360
- package/index.js +22 -24
- package/{library.command-md9715jz.js → library.command-wpedct6c.js} +11 -9
- package/{localRegistry.command-exkbamyj.js → localRegistry.command-pjdxa01g.js} +20 -22
- package/{module.command-w5aaddxk.js → module.command-8n4kct3e.js} +13 -15
- package/{package.command-yfkrcy0z.js → package.command-fcbd0b1d.js} +8 -9
- package/package.json +5 -7
- package/{page.command-echbavp4.js → page.command-x4379grq.js} +7 -8
- package/{primitive.command-9fbhspw2.js → primitive.command-8etmnqhq.js} +13 -15
- package/{quality.command-3pnt9yep.js → quality.command-hfx7z8yr.js} +226 -104
- package/recipeScanner-fpagtgc1.js +11 -0
- package/{repair.command-carj4vax.js → repair.command-j0fs5qkz.js} +12 -13
- package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-ze6x894c.js} +0 -2
- package/{scalar.command-51ryzypc.js → scalar.command-2emk89eh.js} +11 -13
- package/templates/app/env/env.client.debug.ts.template +1 -5
- package/templates/app/env/env.client.develop.ts.template +1 -5
- package/templates/app/env/env.client.local.ts.template +1 -5
- package/templates/app/env/env.client.main.ts.template +1 -5
- package/templates/app/env/env.client.testing.ts.template +1 -5
- package/templates/app/env/env.client.type.ts +2 -6
- package/templates/app/env/env.server.debug.ts.template +1 -5
- package/templates/app/env/env.server.develop.ts.template +1 -5
- package/templates/app/env/env.server.local.ts.template +1 -5
- package/templates/app/env/env.server.main.ts.template +1 -5
- package/templates/app/main.ts +3 -1
- package/templates/appSample/lib/task/task.signal.ts +11 -6
- package/templates/appSample/srvkit/AuthGuard.ts +9 -5
- package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
- package/templates/crudPages/[__model__Id]/edit/page.tsx +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 +1 -1
- package/templates/lib/dict.ts +1 -1
- package/templates/lib/sig.ts +1 -1
- package/templates/lib/srv.ts +6 -2
- package/templates/module/__Model__.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/AGENTS.md.template +4 -972
- package/templates/workspaceRoot/CLAUDE.md.template +19 -0
- package/templates/workspaceRoot/biome.json.template +4 -244
- package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
- package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
- package/templates/workspaceRoot/package.json.template +2 -4
- package/typeChecker-3xgrzc2v.js +7 -0
- package/typecheck.proc.js +4 -5
- package/{workflow.command-91wmvm8k.js → workflow.command-cvpmzwjg.js} +21 -23
- package/{workspace.command-wa10mfd9.js → workspace.command-mbb5s3wx.js} +108 -91
- package/capacitorApp-p0z5yab2.js +0 -60
- 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/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
- package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -34
- 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 -19
- package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -62
- 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 -99
- 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,74 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import {
|
|
3
|
-
Spinner
|
|
4
|
-
} from "./index-mxvakhsm.js";
|
|
5
2
|
import {
|
|
6
3
|
FileSys
|
|
7
|
-
} from "./index-
|
|
4
|
+
} from "./index-ayy5ecax.js";
|
|
8
5
|
import {
|
|
9
|
-
|
|
10
|
-
} from "./index-
|
|
6
|
+
getDirname2
|
|
7
|
+
} from "./index-6wwzs1bc.js";
|
|
8
|
+
|
|
9
|
+
// pkgs/@akanjs/devkit/spinner.ts
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
|
|
12
|
+
class Spinner {
|
|
13
|
+
static padding = 12;
|
|
14
|
+
static oraOptions = { discardStdin: false };
|
|
15
|
+
static canAnimate(stream = process.stderr) {
|
|
16
|
+
return !stream.isTTY || stream.columns > 0;
|
|
17
|
+
}
|
|
18
|
+
spinner;
|
|
19
|
+
stopWatch = null;
|
|
20
|
+
startAt = new Date;
|
|
21
|
+
prefix;
|
|
22
|
+
message;
|
|
23
|
+
enableSpin;
|
|
24
|
+
constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
|
|
25
|
+
Spinner.padding = Math.max(Spinner.padding, prefix.length);
|
|
26
|
+
this.prefix = prefix;
|
|
27
|
+
this.message = message;
|
|
28
|
+
this.spinner = ora({ ...Spinner.oraOptions, text: message });
|
|
29
|
+
this.spinner.prefixText = prefix.padStart(Spinner.padding, " ");
|
|
30
|
+
this.spinner.indent = indent;
|
|
31
|
+
this.enableSpin = enableSpin && Spinner.canAnimate();
|
|
32
|
+
}
|
|
33
|
+
start() {
|
|
34
|
+
this.startAt = new Date;
|
|
35
|
+
if (this.enableSpin) {
|
|
36
|
+
this.spinner.start();
|
|
37
|
+
this.stopWatch = setInterval(() => {
|
|
38
|
+
this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
|
|
39
|
+
this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
|
|
40
|
+
}, 1000);
|
|
41
|
+
} else
|
|
42
|
+
this.spinner.info();
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
succeed(message) {
|
|
46
|
+
this.spinner.succeed(`${message} (${this.#getElapsedTimeStr()})`);
|
|
47
|
+
this.#reset();
|
|
48
|
+
}
|
|
49
|
+
fail(message) {
|
|
50
|
+
this.spinner.fail(`${message} (${this.#getElapsedTimeStr()})`);
|
|
51
|
+
this.#reset();
|
|
52
|
+
}
|
|
53
|
+
isSpinning() {
|
|
54
|
+
return this.spinner.isSpinning;
|
|
55
|
+
}
|
|
56
|
+
#reset() {
|
|
57
|
+
if (this.stopWatch)
|
|
58
|
+
clearInterval(this.stopWatch);
|
|
59
|
+
this.stopWatch = null;
|
|
60
|
+
}
|
|
61
|
+
#getElapsedTimeStr({ floor = false } = {}) {
|
|
62
|
+
const ms = Date.now() - this.startAt.getTime();
|
|
63
|
+
if (ms < 1000)
|
|
64
|
+
return `${ms}ms`;
|
|
65
|
+
const s = Math.floor(ms / 1000);
|
|
66
|
+
if (s < 60)
|
|
67
|
+
return `${floor ? Math.floor(ms / 1000) : Math.floor(ms / 100) / 10}s`;
|
|
68
|
+
const m = Math.floor(s / 60);
|
|
69
|
+
return `${m}m ${s % 60}s`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
11
72
|
|
|
12
73
|
// pkgs/@akanjs/devkit/executors.ts
|
|
13
74
|
import {
|
|
@@ -19,17 +80,18 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
19
80
|
import {
|
|
20
81
|
copyFile,
|
|
21
82
|
cp as cpEntry,
|
|
22
|
-
mkdir,
|
|
83
|
+
mkdir as mkdir2,
|
|
23
84
|
readdir as readDirEntries,
|
|
24
85
|
realpath,
|
|
25
86
|
rm as rm2,
|
|
26
87
|
stat,
|
|
27
88
|
symlink
|
|
28
89
|
} from "fs/promises";
|
|
29
|
-
import
|
|
90
|
+
import path7 from "path";
|
|
30
91
|
import { pathToFileURL } from "url";
|
|
31
92
|
import {
|
|
32
93
|
capitalize,
|
|
94
|
+
getPageSourceFileViolation,
|
|
33
95
|
isRouteSourceFile,
|
|
34
96
|
Logger,
|
|
35
97
|
parseRouteModuleKey,
|
|
@@ -41,9 +103,24 @@ import chalk2 from "chalk";
|
|
|
41
103
|
|
|
42
104
|
// pkgs/@akanjs/devkit/agentsIndex.ts
|
|
43
105
|
var recipeScannerLoad = null;
|
|
44
|
-
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-
|
|
106
|
+
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-fpagtgc1.js");
|
|
45
107
|
var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
|
|
46
108
|
var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
|
|
109
|
+
var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
|
|
110
|
+
var stampBlockVersion = (block, version) => `${AGENT_VERSION_PREFIX}${version} -->
|
|
111
|
+
|
|
112
|
+
${block}`;
|
|
113
|
+
var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
|
|
114
|
+
var readDevkitVersion = async () => {
|
|
115
|
+
const { readFile } = await import("fs/promises");
|
|
116
|
+
const { getDirname } = await import("./getDirname-dr3jz1xg.js");
|
|
117
|
+
try {
|
|
118
|
+
const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
|
|
119
|
+
return JSON.parse(raw).version ?? null;
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
47
124
|
var upsertAgentBlock = (existing, block) => {
|
|
48
125
|
const managed = `${AGENT_BLOCK_START}
|
|
49
126
|
${block}
|
|
@@ -137,9 +214,29 @@ ${AGENT_BLOCK_START}
|
|
|
137
214
|
${block}
|
|
138
215
|
${AGENT_BLOCK_END}
|
|
139
216
|
`;
|
|
217
|
+
var CLAUDE_COMMENT_RULE = `## Comments \u2014 Overrides Your Default
|
|
218
|
+
|
|
219
|
+
Write **no comments** unless the comment passes the test below. This is the rule agents break most often here, so it
|
|
220
|
+
is repeated outside the guide: a diff that adds a comment the test rejects is a diff to redo.
|
|
221
|
+
|
|
222
|
+
Before typing \`//\`, \`/*\`, or a doc block, ask \u2014 **does this sentence carry a fact that is nowhere in the code?**
|
|
223
|
+
|
|
224
|
+
- Restates the identifier, the signature, or the line under it \u2192 delete it.
|
|
225
|
+
- Labels a section (\`// helpers\`, \`// state\`) or narrates a step (\`// fetch the user\`, \`// then save\`) \u2192 delete it.
|
|
226
|
+
- JSDoc on an ordinary function, or a why/how preamble on ordinary logic \u2192 delete it.
|
|
227
|
+
- Explains the edit you just made, for whoever reads the diff \u2192 say it in your reply, not in the file.
|
|
228
|
+
- Names a vendor or protocol quirk, an infrastructure constraint, a library gotcha, security reasoning, a math
|
|
229
|
+
derivation, a domain field's business meaning, a state transition, or why an obvious alternative was rejected \u2192
|
|
230
|
+
keep it, one line.
|
|
231
|
+
|
|
232
|
+
That keep-list is exact \u2014 \`Comments\` in the guide is the full version. "It aids readability" and "this logic is
|
|
233
|
+
subtle" are not on it: rename or split the code instead. When you edit an existing file, match its density; if the
|
|
234
|
+
surrounding code carries none, your diff carries none.`;
|
|
140
235
|
var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
|
|
141
236
|
|
|
142
237
|
@AGENTS.md
|
|
238
|
+
|
|
239
|
+
${CLAUDE_COMMENT_RULE}
|
|
143
240
|
`;
|
|
144
241
|
|
|
145
242
|
// pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
|
|
@@ -181,9 +278,8 @@ var DEFAULT_OPTIMIZE_IMPORTS = [
|
|
|
181
278
|
"react-icons/*"
|
|
182
279
|
];
|
|
183
280
|
var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
|
|
281
|
+
var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
|
|
184
282
|
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
283
|
var MOBILE_RUNTIME_PACKAGES = [
|
|
188
284
|
"@capacitor/cli",
|
|
189
285
|
"@capacitor/core",
|
|
@@ -212,8 +308,6 @@ var DATABASE_MODE_RUNTIME_PACKAGES = {
|
|
|
212
308
|
};
|
|
213
309
|
var AKAN_RUNTIME_PACKAGES = new Set([
|
|
214
310
|
...SSR_RUNTIME_PACKAGES,
|
|
215
|
-
...NATIVE_RUNTIME_PACKAGES,
|
|
216
|
-
...DEFAULT_BACKEND_RUNTIME_PACKAGES,
|
|
217
311
|
...MOBILE_RUNTIME_PACKAGES,
|
|
218
312
|
...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat()
|
|
219
313
|
]);
|
|
@@ -228,7 +322,8 @@ var DEFAULT_AKAN_IMAGE_CONFIG = {
|
|
|
228
322
|
dangerouslyAllowSVG: false,
|
|
229
323
|
maximumRedirects: 3,
|
|
230
324
|
fetchTimeoutMs: 7000,
|
|
231
|
-
maxRemoteBytes: 25 * 1024 * 1024
|
|
325
|
+
maxRemoteBytes: 25 * 1024 * 1024,
|
|
326
|
+
maxConcurrency: 0
|
|
232
327
|
};
|
|
233
328
|
var normalizeIndexPath = (indexPath) => {
|
|
234
329
|
const normalized = indexPath?.trim();
|
|
@@ -275,12 +370,28 @@ var normalizeDeepLinks = (deepLinks) => {
|
|
|
275
370
|
...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
|
|
276
371
|
};
|
|
277
372
|
};
|
|
373
|
+
var emptyLibContributions = () => ({
|
|
374
|
+
externalLibs: [],
|
|
375
|
+
docker: { preRuns: [], postRuns: [] },
|
|
376
|
+
keepFonts: []
|
|
377
|
+
});
|
|
378
|
+
var normalizeKeepFonts = (keepFonts) => [
|
|
379
|
+
...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean))
|
|
380
|
+
];
|
|
381
|
+
var dedupeDockerRuns = (runs) => {
|
|
382
|
+
const byKey = new Map;
|
|
383
|
+
for (const run of runs)
|
|
384
|
+
byKey.set(typeof run === "string" ? run : JSON.stringify(run), run);
|
|
385
|
+
return [...byKey.values()];
|
|
386
|
+
};
|
|
278
387
|
|
|
279
388
|
class AkanAppConfig {
|
|
280
389
|
app;
|
|
281
390
|
rootPackageJson;
|
|
282
391
|
docker;
|
|
392
|
+
dockerfile;
|
|
283
393
|
defaultDatabaseMode;
|
|
394
|
+
web;
|
|
284
395
|
externalLibs;
|
|
285
396
|
barrelImports;
|
|
286
397
|
optimizeImports;
|
|
@@ -290,6 +401,7 @@ class AkanAppConfig {
|
|
|
290
401
|
mobile;
|
|
291
402
|
hasMobileConfig;
|
|
292
403
|
secrets;
|
|
404
|
+
assets;
|
|
293
405
|
syncPageLibs;
|
|
294
406
|
baseDevEnv;
|
|
295
407
|
libs;
|
|
@@ -298,7 +410,7 @@ class AkanAppConfig {
|
|
|
298
410
|
subRoutes = new Map;
|
|
299
411
|
basePaths = new Set;
|
|
300
412
|
branches = new Set(["debug", "develop", "main"]);
|
|
301
|
-
constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = []) {
|
|
413
|
+
constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = [], libContributions = emptyLibContributions()) {
|
|
302
414
|
this.app = app;
|
|
303
415
|
this.rootPackageJson = rootPackageJson;
|
|
304
416
|
this.libs = libs;
|
|
@@ -306,7 +418,7 @@ class AkanAppConfig {
|
|
|
306
418
|
this.plugins = plugins;
|
|
307
419
|
this.#applyRoutes(config?.routes);
|
|
308
420
|
this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
|
|
309
|
-
this.externalLibs = config?.externalLibs ?? [];
|
|
421
|
+
this.externalLibs = [...new Set([...config?.externalLibs ?? [], ...libContributions.externalLibs])];
|
|
310
422
|
this.barrelImports = [
|
|
311
423
|
...DEFAULT_BARREL_IMPORTS,
|
|
312
424
|
...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
|
|
@@ -320,10 +432,25 @@ class AkanAppConfig {
|
|
|
320
432
|
process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
|
|
321
433
|
this.publicEnv = config?.publicEnv ?? [];
|
|
322
434
|
this.secrets = config?.secrets ?? [];
|
|
435
|
+
this.assets = {
|
|
436
|
+
pruneFonts: config?.assets?.pruneFonts ?? true,
|
|
437
|
+
keepFonts: [
|
|
438
|
+
...normalizeKeepFonts(config?.assets?.keepFonts),
|
|
439
|
+
...libContributions.keepFonts ?? []
|
|
440
|
+
]
|
|
441
|
+
};
|
|
323
442
|
this.syncPageLibs = config?.syncPageLibs ?? false;
|
|
324
443
|
this.hasMobileConfig = Boolean(config.mobile);
|
|
325
444
|
this.mobile = this.#resolveMobileConfig(config.mobile);
|
|
326
|
-
this.
|
|
445
|
+
this.web = this.#resolveWebConfig(config.web);
|
|
446
|
+
this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
|
|
447
|
+
this.dockerfile = this.#makeDockerfile();
|
|
448
|
+
}
|
|
449
|
+
#resolveWebConfig(web) {
|
|
450
|
+
const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
|
|
451
|
+
if (!resolved.csr && this.hasMobileConfig)
|
|
452
|
+
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.`);
|
|
453
|
+
return resolved;
|
|
327
454
|
}
|
|
328
455
|
#resolveMobileConfig(mobile) {
|
|
329
456
|
const {
|
|
@@ -433,23 +560,31 @@ class AkanAppConfig {
|
|
|
433
560
|
return archs.map((arch) => `FROM ${image[arch] ?? defaultImage} AS ${arch}`).join(`
|
|
434
561
|
`);
|
|
435
562
|
}
|
|
436
|
-
#
|
|
437
|
-
if (docker
|
|
438
|
-
return
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const
|
|
563
|
+
static #resolveDocker(docker, libDocker) {
|
|
564
|
+
if (typeof docker === "string")
|
|
565
|
+
return docker;
|
|
566
|
+
return {
|
|
567
|
+
image: docker?.image ?? DEFAULT_DOCKER_IMAGE,
|
|
568
|
+
preRuns: dedupeDockerRuns([...libDocker.preRuns, ...docker?.preRuns ?? []]),
|
|
569
|
+
postRuns: dedupeDockerRuns([...libDocker.postRuns, ...docker?.postRuns ?? []]),
|
|
570
|
+
command: docker?.command ?? ["bun", "main.js"]
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
#makeDockerfile() {
|
|
574
|
+
if (typeof this.docker === "string")
|
|
575
|
+
return this.docker;
|
|
576
|
+
const { image, preRuns, postRuns, command } = this.docker;
|
|
577
|
+
const preRunScripts = this.#getDockerRunScripts(preRuns);
|
|
578
|
+
const postRunScripts = this.#getDockerRunScripts(postRuns);
|
|
579
|
+
const imageScript = this.#getDockerImageScript(image, DEFAULT_DOCKER_IMAGE);
|
|
580
|
+
const webEnvLines = [
|
|
581
|
+
...this.web.ssr ? [] : ["ENV AKAN_SSR=false"],
|
|
582
|
+
...this.web.csr ? [] : ["ENV AKAN_CSR=false"]
|
|
583
|
+
].join(`
|
|
584
|
+
`);
|
|
585
|
+
return `${imageScript}
|
|
586
|
+
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
587
|
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
588
|
ARG TARGETARCH
|
|
454
589
|
${preRunScripts.join(`
|
|
455
590
|
`)}
|
|
@@ -470,15 +605,9 @@ ${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join("
|
|
|
470
605
|
ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
|
|
471
606
|
ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
|
|
472
607
|
ENV AKAN_PUBLIC_OPERATION_MODE=cloud
|
|
473
|
-
|
|
608
|
+
ENV AKAN_LOG_TO_FILE=0
|
|
609
|
+
${webEnvLines}
|
|
474
610
|
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
611
|
}
|
|
483
612
|
static #importGeneration = 0;
|
|
484
613
|
static async importConfigModule(cwdPath, { bustImportCache = false } = {}) {
|
|
@@ -495,7 +624,22 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
495
624
|
]);
|
|
496
625
|
const resolved = typeof configImp === "function" ? configImp(app) : configImp;
|
|
497
626
|
const { plugins, ...config } = resolved ?? {};
|
|
498
|
-
|
|
627
|
+
const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
|
|
628
|
+
return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
|
|
629
|
+
}
|
|
630
|
+
static async#collectLibContributions(app, libs, bustImportCache) {
|
|
631
|
+
const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
|
|
632
|
+
app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
|
|
633
|
+
return null;
|
|
634
|
+
})));
|
|
635
|
+
return {
|
|
636
|
+
externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
|
|
637
|
+
docker: {
|
|
638
|
+
preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
|
|
639
|
+
postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? [])
|
|
640
|
+
},
|
|
641
|
+
keepFonts: libConfigs.flatMap((libConfig) => (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`))
|
|
642
|
+
};
|
|
499
643
|
}
|
|
500
644
|
#resolveProductionDependencyVersion(lib) {
|
|
501
645
|
const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
|
|
@@ -505,13 +649,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
505
649
|
return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
506
650
|
}
|
|
507
651
|
#getProductionRuntimePackages() {
|
|
508
|
-
return [
|
|
509
|
-
...this.externalLibs,
|
|
510
|
-
...SSR_RUNTIME_PACKAGES,
|
|
511
|
-
...NATIVE_RUNTIME_PACKAGES,
|
|
512
|
-
...DEFAULT_BACKEND_RUNTIME_PACKAGES,
|
|
513
|
-
...this.getDatabaseModeRuntimePackages()
|
|
514
|
-
];
|
|
652
|
+
return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
|
|
515
653
|
}
|
|
516
654
|
getDatabaseModeRuntimePackages(databaseMode = this.defaultDatabaseMode) {
|
|
517
655
|
return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
|
|
@@ -599,14 +737,18 @@ function mergeImageConfig(config = {}) {
|
|
|
599
737
|
class AkanLibConfig {
|
|
600
738
|
lib;
|
|
601
739
|
externalLibs;
|
|
740
|
+
docker;
|
|
741
|
+
assets;
|
|
602
742
|
plugins;
|
|
603
743
|
constructor(lib, config, plugins = []) {
|
|
604
744
|
this.lib = lib;
|
|
605
745
|
this.externalLibs = config?.externalLibs ?? [];
|
|
746
|
+
this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
|
|
747
|
+
this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
|
|
606
748
|
this.plugins = plugins;
|
|
607
749
|
}
|
|
608
|
-
static async from(lib) {
|
|
609
|
-
const
|
|
750
|
+
static async from(lib, { bustImportCache = false } = {}) {
|
|
751
|
+
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
610
752
|
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
611
753
|
const { plugins, ...config } = resolved ?? {};
|
|
612
754
|
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
@@ -627,42 +769,436 @@ var decreaseBuildNum = async (app) => {
|
|
|
627
769
|
const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
|
|
628
770
|
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
629
771
|
};
|
|
630
|
-
// pkgs/@akanjs/devkit/
|
|
772
|
+
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
773
|
+
import { mkdir } from "fs/promises";
|
|
774
|
+
import path3 from "path";
|
|
775
|
+
|
|
776
|
+
// pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
|
|
777
|
+
import fs2 from "fs";
|
|
631
778
|
import path2 from "path";
|
|
632
|
-
|
|
633
|
-
|
|
779
|
+
|
|
780
|
+
class AsyncDefaultExportDetector {
|
|
781
|
+
static #typescriptLoad;
|
|
782
|
+
static #loadTypescript() {
|
|
783
|
+
AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
|
|
784
|
+
return AsyncDefaultExportDetector.#typescriptLoad;
|
|
785
|
+
}
|
|
786
|
+
static async detect(moduleAbsPath) {
|
|
787
|
+
try {
|
|
788
|
+
const typescript = await AsyncDefaultExportDetector.#loadTypescript();
|
|
789
|
+
const source = fs2.readFileSync(path2.resolve(moduleAbsPath), "utf8");
|
|
790
|
+
const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, AsyncDefaultExportDetector.#scriptKind(typescript, moduleAbsPath));
|
|
791
|
+
return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
|
|
792
|
+
} catch {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
#ts;
|
|
797
|
+
constructor(typescript) {
|
|
798
|
+
this.#ts = typescript;
|
|
799
|
+
}
|
|
800
|
+
detectInSourceFile(sourceFile) {
|
|
801
|
+
const ts = this.#ts;
|
|
802
|
+
const asyncBindings = new Map;
|
|
803
|
+
let defaultIdentifier = null;
|
|
804
|
+
for (const statement of sourceFile.statements) {
|
|
805
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
806
|
+
if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
|
|
807
|
+
return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
|
|
808
|
+
}
|
|
809
|
+
if (statement.name) {
|
|
810
|
+
asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
|
|
811
|
+
}
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
if (ts.isVariableStatement(statement)) {
|
|
815
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
816
|
+
if (!ts.isIdentifier(declaration.name))
|
|
817
|
+
continue;
|
|
818
|
+
asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
|
|
819
|
+
}
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
if (ts.isExportAssignment(statement)) {
|
|
823
|
+
if (this.#isAsyncFunctionExpression(statement.expression))
|
|
824
|
+
return true;
|
|
825
|
+
if (ts.isIdentifier(statement.expression))
|
|
826
|
+
defaultIdentifier = statement.expression.text;
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
830
|
+
const exportClause = statement.exportClause;
|
|
831
|
+
for (const specifier of exportClause.elements) {
|
|
832
|
+
if (specifier.name.text !== "default")
|
|
833
|
+
continue;
|
|
834
|
+
defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
|
|
839
|
+
}
|
|
840
|
+
#hasModifier(node, kind) {
|
|
841
|
+
const ts = this.#ts;
|
|
842
|
+
return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
|
|
843
|
+
}
|
|
844
|
+
#isAsyncFunctionExpression(node) {
|
|
845
|
+
const ts = this.#ts;
|
|
846
|
+
return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
|
|
847
|
+
}
|
|
848
|
+
static #scriptKind(typescript, moduleAbsPath) {
|
|
849
|
+
return moduleAbsPath.endsWith(".tsx") || moduleAbsPath.endsWith(".jsx") ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
854
|
+
var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
855
|
+
async function appHasStModule(appCwdPath) {
|
|
856
|
+
return Bun.file(path3.join(appCwdPath, "lib", "st.ts")).exists();
|
|
857
|
+
}
|
|
858
|
+
var IMPLICIT_LAYOUT_DIR = path3.join(".akan", "generated", "root-layouts");
|
|
859
|
+
var IMPLICIT_DICT_DIR = path3.join(".akan", "generated", "dict");
|
|
860
|
+
var IMPLICIT_OVERRIDES_DIR = path3.join(".akan", "generated", "overrides");
|
|
861
|
+
var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
862
|
+
async function writeGeneratedOverridesLayoutFile(opts) {
|
|
863
|
+
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
864
|
+
const absPath = path3.join(path3.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
865
|
+
const userRel = path3.relative(path3.dirname(absPath), opts.userAbsPath).split(path3.sep).join("/");
|
|
866
|
+
const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
|
|
867
|
+
const source = `"use client";
|
|
868
|
+
import { UiOverrideProvider } from "akanjs/ui";
|
|
869
|
+
import { createElement, type ReactNode } from "react";
|
|
870
|
+
import value from ${JSON.stringify(userSpecifier)};
|
|
871
|
+
|
|
872
|
+
export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
|
|
873
|
+
return createElement(UiOverrideProvider, { value }, children);
|
|
874
|
+
}
|
|
875
|
+
`;
|
|
876
|
+
await Bun.write(absPath, source);
|
|
877
|
+
return absPath;
|
|
878
|
+
}
|
|
879
|
+
function getRootBoundarySegments(key) {
|
|
880
|
+
const match = LAYOUT_KEY_RE.exec(key);
|
|
881
|
+
if (!match)
|
|
882
|
+
return null;
|
|
883
|
+
const prefix = match[1]?.replace(/\/$/, "");
|
|
884
|
+
if (!prefix)
|
|
885
|
+
return [];
|
|
886
|
+
return prefix.split("/").filter(Boolean);
|
|
887
|
+
}
|
|
888
|
+
function implicitRootLayoutKey(segments) {
|
|
889
|
+
return `./${[...segments, "__root_layout"].join("/")}.tsx`;
|
|
890
|
+
}
|
|
891
|
+
function implicitRootLayoutAbsPath(appCwdPath, segments) {
|
|
892
|
+
const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
|
|
893
|
+
return path3.join(path3.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
894
|
+
}
|
|
895
|
+
function implicitDictionaryMacroAbsPath(appCwdPath) {
|
|
896
|
+
return path3.join(path3.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
897
|
+
}
|
|
898
|
+
function isRootBoundarySegments(segments, basePaths) {
|
|
899
|
+
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
900
|
+
if (firstVisibleIndex === -1)
|
|
901
|
+
return segments.length <= 1;
|
|
902
|
+
if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
|
|
903
|
+
return false;
|
|
904
|
+
const visible = segments.slice(firstVisibleIndex);
|
|
905
|
+
const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
|
|
906
|
+
return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
|
|
907
|
+
}
|
|
908
|
+
function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
|
|
909
|
+
const boundaries = new Map;
|
|
910
|
+
for (const key of pageKeys) {
|
|
911
|
+
const segments = getRootBoundarySegments(key);
|
|
912
|
+
if (!segments)
|
|
913
|
+
continue;
|
|
914
|
+
if (!isRootBoundarySegments(segments, basePaths))
|
|
915
|
+
continue;
|
|
916
|
+
const id = segments.join("/");
|
|
917
|
+
boundaries.set(id, {
|
|
918
|
+
sourceKey: key,
|
|
919
|
+
sourceAbsPath: path3.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
|
|
920
|
+
segments
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
const hasExplicitRootBoundary = [...boundaries.values()].some((boundary) => boundary.segments.length === 0);
|
|
924
|
+
if (!hasExplicitRootBoundary && boundaries.size === 0) {
|
|
925
|
+
boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
926
|
+
}
|
|
927
|
+
return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
|
|
928
|
+
}
|
|
929
|
+
function hasAncestorRootBoundary(boundary, boundaries) {
|
|
930
|
+
return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
|
|
931
|
+
}
|
|
932
|
+
function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
|
|
933
|
+
const rootLayoutKey = pageKeys.find((key) => {
|
|
934
|
+
const segments = getRootBoundarySegments(key);
|
|
935
|
+
return segments !== null && segments.length === 0;
|
|
936
|
+
});
|
|
937
|
+
return rootLayoutKey ? path3.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
938
|
+
}
|
|
939
|
+
function routePrefixForSegments(segments) {
|
|
940
|
+
const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
|
|
941
|
+
return visible[0] ?? null;
|
|
942
|
+
}
|
|
943
|
+
async function assertEnvClientConvention(appCwdPath, appName) {
|
|
944
|
+
const envPath = path3.join(appCwdPath, "env", "env.client.ts");
|
|
945
|
+
if (!await Bun.file(envPath).exists()) {
|
|
946
|
+
throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
|
|
950
|
+
const absPath = implicitDictionaryMacroAbsPath(appCwdPath);
|
|
951
|
+
await mkdir(path3.dirname(absPath), { recursive: true });
|
|
952
|
+
await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
953
|
+
|
|
954
|
+
export const allDictionary = getAllDictionary();
|
|
955
|
+
`);
|
|
956
|
+
return absPath;
|
|
957
|
+
}
|
|
958
|
+
async function writeGeneratedRootLayoutFile(opts) {
|
|
959
|
+
await assertEnvClientConvention(opts.appCwdPath, opts.appName);
|
|
960
|
+
const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
|
|
961
|
+
const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
|
|
962
|
+
await mkdir(path3.dirname(absPath), { recursive: true });
|
|
963
|
+
const dictMacroRel = dictMacroAbsPath ? path3.relative(path3.dirname(absPath), dictMacroAbsPath).split(path3.sep).join("/") : null;
|
|
964
|
+
const dictMacroSpecifier = dictMacroRel ? dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}` : null;
|
|
965
|
+
const sourceRel = opts.boundary.sourceAbsPath ? path3.relative(path3.dirname(absPath), opts.boundary.sourceAbsPath).split(path3.sep).join("/") : null;
|
|
966
|
+
const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
|
|
967
|
+
const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
|
|
968
|
+
const inheritedSourceRel = inheritedSourceAbsPath ? path3.relative(path3.dirname(absPath), inheritedSourceAbsPath).split(path3.sep).join("/") : null;
|
|
969
|
+
const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
|
|
970
|
+
const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
|
|
971
|
+
void st;
|
|
972
|
+
` : `import "@apps/${opts.appName}/client";
|
|
973
|
+
`;
|
|
974
|
+
const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedLayout from ${JSON.stringify(inheritedSourceSpecifier)};
|
|
975
|
+
` : `const inheritedLayout = {};
|
|
976
|
+
`;
|
|
977
|
+
const prefix = routePrefixForSegments(opts.boundary.segments);
|
|
978
|
+
const userImport = sourceSpecifier ? `import UserLayout, * as userLayout from ${JSON.stringify(sourceSpecifier)};
|
|
979
|
+
` : `const UserLayout = ({ children }) => children;
|
|
980
|
+
const userLayout = {};
|
|
981
|
+
`;
|
|
982
|
+
const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
|
|
983
|
+
const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
|
|
984
|
+
const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
|
|
985
|
+
const layoutBinding = isAsyncUserLayout ? ` const layout =
|
|
986
|
+
process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
|
|
987
|
+
? await UserLayout({ params, searchParams, children })
|
|
988
|
+
: ${userLayoutElement};
|
|
989
|
+
` : "";
|
|
990
|
+
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
991
|
+
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
992
|
+
const source = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
|
|
993
|
+
import { loadFonts } from "akanjs/client";
|
|
994
|
+
import { System } from "akanjs/ui";
|
|
995
|
+
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
996
|
+
import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
|
|
997
|
+
${clientImport}${inheritedImport}${userImport}
|
|
998
|
+
const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
|
|
999
|
+
const defaultFonts = userFonts.filter((font) => font.default);
|
|
1000
|
+
if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
|
|
1001
|
+
const defaultFont = defaultFonts[0];
|
|
1002
|
+
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
1003
|
+
|
|
1004
|
+
export async function generateHead(props: PageProps) {
|
|
1005
|
+
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
1006
|
+
if (userLayout.head !== undefined) return userLayout.head;
|
|
1007
|
+
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
1008
|
+
return inheritedLayout.head;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
export async function generateMetadata(props: PageProps) {
|
|
1012
|
+
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
1013
|
+
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
1014
|
+
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
1015
|
+
return inheritedLayout.metadata;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
1019
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
1020
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
1021
|
+
|
|
1022
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
1023
|
+
${layoutBinding} return (
|
|
1024
|
+
<System.Provider
|
|
1025
|
+
of={GeneratedLayout as never}
|
|
1026
|
+
appName=${JSON.stringify(opts.appName)}
|
|
1027
|
+
${prefix ? `prefix=${JSON.stringify(prefix)}
|
|
1028
|
+
` : ""}params={params}
|
|
1029
|
+
manifest={userLayout.manifest ?? inheritedLayout.manifest}
|
|
1030
|
+
env={env}
|
|
1031
|
+
theme={userLayout.theme ?? inheritedLayout.theme}
|
|
1032
|
+
fonts={loadFonts(userFonts)}
|
|
1033
|
+
className={defaultFontClassName}
|
|
1034
|
+
gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
|
|
1035
|
+
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
1036
|
+
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
1037
|
+
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
|
|
1038
|
+
allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
|
|
1039
|
+
>
|
|
1040
|
+
${layoutChild}
|
|
1041
|
+
</System.Provider>
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
` : `import type { LayoutProps, PageProps } from "akanjs/client";
|
|
1045
|
+
${inheritedImport}${userImport}
|
|
1046
|
+
export async function generateHead(props: PageProps) {
|
|
1047
|
+
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
1048
|
+
if (userLayout.head !== undefined) return userLayout.head;
|
|
1049
|
+
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
1050
|
+
return inheritedLayout.head;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
export async function generateMetadata(props: PageProps) {
|
|
1054
|
+
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
1055
|
+
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
1056
|
+
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
1057
|
+
return inheritedLayout.metadata;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
1061
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
1062
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
1063
|
+
|
|
1064
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
1065
|
+
${layoutBinding} return ${layoutReturn};
|
|
1066
|
+
}
|
|
1067
|
+
`;
|
|
1068
|
+
await Bun.write(absPath, source);
|
|
1069
|
+
return absPath;
|
|
1070
|
+
}
|
|
1071
|
+
async function resolveSsrPageEntries(opts) {
|
|
1072
|
+
const absPageDir = path3.resolve(opts.appCwdPath, "page");
|
|
1073
|
+
const hasSt = await appHasStModule(opts.appCwdPath);
|
|
1074
|
+
const basePaths = opts.basePaths ?? [];
|
|
1075
|
+
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
1076
|
+
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
1077
|
+
const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
|
|
1078
|
+
const segments = getRootBoundarySegments(key);
|
|
1079
|
+
return segments !== null && isRootBoundarySegments(segments, basePaths);
|
|
1080
|
+
}));
|
|
1081
|
+
const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
|
|
1082
|
+
const userAbsPath = path3.resolve(absPageDir, key);
|
|
1083
|
+
if (OVERRIDES_KEY_RE.test(key)) {
|
|
1084
|
+
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
1085
|
+
appCwdPath: opts.appCwdPath,
|
|
1086
|
+
key,
|
|
1087
|
+
userAbsPath
|
|
1088
|
+
});
|
|
1089
|
+
return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
|
|
1090
|
+
}
|
|
1091
|
+
return { key, moduleAbsPath: userAbsPath };
|
|
1092
|
+
}));
|
|
1093
|
+
const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
|
|
1094
|
+
key: implicitRootLayoutKey(boundary.segments),
|
|
1095
|
+
moduleAbsPath: await writeGeneratedRootLayoutFile({
|
|
1096
|
+
appCwdPath: opts.appCwdPath,
|
|
1097
|
+
appName: opts.appName,
|
|
1098
|
+
boundary,
|
|
1099
|
+
rootSourceAbsPath,
|
|
1100
|
+
includeStInit: hasSt && boundary.segments.length === 0,
|
|
1101
|
+
includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
|
|
1102
|
+
}),
|
|
1103
|
+
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
|
|
1104
|
+
})));
|
|
1105
|
+
const entries = [...base, ...generated];
|
|
1106
|
+
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
1107
|
+
return entries;
|
|
1108
|
+
}
|
|
1109
|
+
async function resolveSsrPageEntriesForApp(app, pageKeys) {
|
|
1110
|
+
const config = await app.getConfig();
|
|
1111
|
+
return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
|
|
1112
|
+
}
|
|
634
1113
|
|
|
635
1114
|
// pkgs/@akanjs/devkit/linter.ts
|
|
636
1115
|
import { spawn } from "child_process";
|
|
637
1116
|
import { existsSync, readFileSync } from "fs";
|
|
638
|
-
import
|
|
1117
|
+
import path4 from "path";
|
|
639
1118
|
import chalk from "chalk";
|
|
1119
|
+
var parseBiomeReport = (output) => {
|
|
1120
|
+
for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
|
|
1121
|
+
const end = objectEndAt(output, start);
|
|
1122
|
+
if (end === -1)
|
|
1123
|
+
break;
|
|
1124
|
+
const report = asBiomeReport(output.slice(start, end + 1));
|
|
1125
|
+
if (report)
|
|
1126
|
+
return report;
|
|
1127
|
+
}
|
|
1128
|
+
throw new Error(output.trim() || "No Biome JSON output");
|
|
1129
|
+
};
|
|
1130
|
+
var objectEndAt = (output, start) => {
|
|
1131
|
+
let depth = 0;
|
|
1132
|
+
let inString = false;
|
|
1133
|
+
let escaped = false;
|
|
1134
|
+
for (let idx = start;idx < output.length; idx += 1) {
|
|
1135
|
+
const char = output[idx];
|
|
1136
|
+
if (inString) {
|
|
1137
|
+
if (escaped)
|
|
1138
|
+
escaped = false;
|
|
1139
|
+
else if (char === "\\")
|
|
1140
|
+
escaped = true;
|
|
1141
|
+
else if (char === '"')
|
|
1142
|
+
inString = false;
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
if (char === '"')
|
|
1146
|
+
inString = true;
|
|
1147
|
+
else if (char === "{")
|
|
1148
|
+
depth += 1;
|
|
1149
|
+
else if (char === "}" && --depth === 0)
|
|
1150
|
+
return idx;
|
|
1151
|
+
}
|
|
1152
|
+
return -1;
|
|
1153
|
+
};
|
|
1154
|
+
var asBiomeReport = (candidate) => {
|
|
1155
|
+
try {
|
|
1156
|
+
const parsed = JSON.parse(candidate);
|
|
1157
|
+
if (!parsed || typeof parsed !== "object")
|
|
1158
|
+
return null;
|
|
1159
|
+
if (!("diagnostics" in parsed) && !("summary" in parsed))
|
|
1160
|
+
return null;
|
|
1161
|
+
return parsed;
|
|
1162
|
+
} catch {
|
|
1163
|
+
return null;
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
var BIOME_CONFIG_FILES = ["biome.json", "biome.jsonc"];
|
|
640
1167
|
|
|
641
1168
|
class Linter {
|
|
642
1169
|
lintRoot;
|
|
1170
|
+
configPath;
|
|
643
1171
|
#biomeBin;
|
|
644
1172
|
constructor(cwdPath) {
|
|
645
1173
|
this.lintRoot = this.#findBiomeRootPath(cwdPath);
|
|
646
|
-
|
|
1174
|
+
this.configPath = Linter.#configPathIn(this.lintRoot) ?? path4.join(this.lintRoot, "biome.json");
|
|
1175
|
+
const localBiomeBin = path4.join(this.lintRoot, "node_modules/.bin/biome");
|
|
647
1176
|
this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
|
|
648
1177
|
}
|
|
1178
|
+
static #configPathIn(dir) {
|
|
1179
|
+
for (const fileName of BIOME_CONFIG_FILES) {
|
|
1180
|
+
const configPath = path4.join(dir, fileName);
|
|
1181
|
+
if (existsSync(configPath))
|
|
1182
|
+
return configPath;
|
|
1183
|
+
}
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
649
1186
|
#findBiomeRootPath(dir) {
|
|
650
|
-
|
|
651
|
-
if (existsSync(configPath))
|
|
1187
|
+
if (Linter.#configPathIn(dir))
|
|
652
1188
|
return dir;
|
|
653
|
-
const parentDir =
|
|
1189
|
+
const parentDir = path4.dirname(dir);
|
|
654
1190
|
if (parentDir === dir)
|
|
655
1191
|
throw new Error(`biome.json not found from ${dir}`);
|
|
656
1192
|
return this.#findBiomeRootPath(parentDir);
|
|
657
1193
|
}
|
|
658
1194
|
#toBiomePath(filePath) {
|
|
659
|
-
const relativePath =
|
|
660
|
-
if (!relativePath.startsWith("..") && !
|
|
1195
|
+
const relativePath = path4.relative(this.lintRoot, filePath);
|
|
1196
|
+
if (!relativePath.startsWith("..") && !path4.isAbsolute(relativePath))
|
|
661
1197
|
return relativePath;
|
|
662
1198
|
return filePath;
|
|
663
1199
|
}
|
|
664
1200
|
#resolveFilePath(filePath) {
|
|
665
|
-
return
|
|
1201
|
+
return path4.isAbsolute(filePath) ? filePath : path4.join(this.lintRoot, filePath);
|
|
666
1202
|
}
|
|
667
1203
|
async#runBiome(args, input) {
|
|
668
1204
|
return await new Promise((resolve, reject) => {
|
|
@@ -683,18 +1219,11 @@ class Linter {
|
|
|
683
1219
|
proc.stdin.end(input);
|
|
684
1220
|
});
|
|
685
1221
|
}
|
|
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
1222
|
#diagnosticFilePath(diagnostic, fallbackFilePath) {
|
|
694
1223
|
const diagnosticPath = diagnostic.location?.path;
|
|
695
1224
|
if (!diagnosticPath)
|
|
696
1225
|
return fallbackFilePath;
|
|
697
|
-
return
|
|
1226
|
+
return path4.isAbsolute(diagnosticPath) ? diagnosticPath : path4.join(this.lintRoot, diagnosticPath);
|
|
698
1227
|
}
|
|
699
1228
|
#createLintMessage(diagnostic) {
|
|
700
1229
|
const start = diagnostic.location?.start;
|
|
@@ -759,10 +1288,10 @@ class Linter {
|
|
|
759
1288
|
"--max-diagnostics=none",
|
|
760
1289
|
"--no-errors-on-unmatched",
|
|
761
1290
|
"--config-path",
|
|
762
|
-
|
|
1291
|
+
this.configPath,
|
|
763
1292
|
this.#toBiomePath(filePath)
|
|
764
1293
|
]);
|
|
765
|
-
const report =
|
|
1294
|
+
const report = parseBiomeReport(stdout || stderr);
|
|
766
1295
|
const results = this.#toLintResults(report, filePath);
|
|
767
1296
|
const { errors, warnings } = this.#splitMessages(results);
|
|
768
1297
|
const output = write && existsSync(filePath) ? readFileSync(filePath, "utf8") : undefined;
|
|
@@ -880,22 +1409,34 @@ ${errorText}, ${warningText} found`];
|
|
|
880
1409
|
if (!dryRun)
|
|
881
1410
|
return await this.#checkFile(resolvedFilePath, { write: true });
|
|
882
1411
|
const source = readFileSync(resolvedFilePath, "utf8");
|
|
883
|
-
const { stdout } = await this.#runBiome([
|
|
1412
|
+
const { stdout } = await this.#runBiome(["check", "--write", "--config-path", this.configPath, "--stdin-file-path", this.#toBiomePath(resolvedFilePath)], source);
|
|
1413
|
+
const lintResult = await this.lintFile(resolvedFilePath);
|
|
1414
|
+
return { ...lintResult, fixed: stdout !== source, output: stdout };
|
|
1415
|
+
}
|
|
1416
|
+
async fixFiles(filePaths) {
|
|
1417
|
+
const resolved = filePaths.map((filePath) => this.#resolveFilePath(filePath)).filter(existsSync);
|
|
1418
|
+
if (resolved.length === 0)
|
|
1419
|
+
return { fixed: [] };
|
|
1420
|
+
const before = new Map(resolved.map((filePath) => [filePath, readFileSync(filePath, "utf8")]));
|
|
1421
|
+
await this.#runBiome([
|
|
884
1422
|
"check",
|
|
885
1423
|
"--write",
|
|
1424
|
+
"--reporter=json",
|
|
1425
|
+
"--max-diagnostics=none",
|
|
1426
|
+
"--no-errors-on-unmatched",
|
|
886
1427
|
"--config-path",
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1428
|
+
this.configPath,
|
|
1429
|
+
...resolved.map((filePath) => this.#toBiomePath(filePath))
|
|
1430
|
+
]);
|
|
1431
|
+
return {
|
|
1432
|
+
fixed: resolved.filter((filePath) => readFileSync(filePath, "utf8") !== before.get(filePath))
|
|
1433
|
+
};
|
|
893
1434
|
}
|
|
894
1435
|
async getConfigForFile(filePath) {
|
|
895
1436
|
const resolvedFilePath = this.#resolveFilePath(filePath);
|
|
896
1437
|
if (!existsSync(resolvedFilePath))
|
|
897
1438
|
throw new Error(`File not found: ${filePath}`);
|
|
898
|
-
return JSON.parse(readFileSync(
|
|
1439
|
+
return JSON.parse(readFileSync(this.configPath, "utf8"));
|
|
899
1440
|
}
|
|
900
1441
|
async getProblematicRules(filePath) {
|
|
901
1442
|
const { results } = await this.lintFile(filePath);
|
|
@@ -910,9 +1451,34 @@ ${errorText}, ${warningText} found`];
|
|
|
910
1451
|
}
|
|
911
1452
|
}
|
|
912
1453
|
|
|
1454
|
+
// pkgs/@akanjs/devkit/repoIdentity.ts
|
|
1455
|
+
import { execFileSync } from "child_process";
|
|
1456
|
+
import path5 from "path";
|
|
1457
|
+
var resolved = new Map;
|
|
1458
|
+
var readRemoteName = (workspaceRoot) => {
|
|
1459
|
+
try {
|
|
1460
|
+
const url = execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
1461
|
+
cwd: workspaceRoot,
|
|
1462
|
+
encoding: "utf-8",
|
|
1463
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1464
|
+
}).trim();
|
|
1465
|
+
return url.replace(/\.git$/, "").split(/[/:]/).pop() || null;
|
|
1466
|
+
} catch {
|
|
1467
|
+
return null;
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
var resolveRepoName = (workspaceRoot) => {
|
|
1471
|
+
const cached = resolved.get(workspaceRoot);
|
|
1472
|
+
if (cached)
|
|
1473
|
+
return cached;
|
|
1474
|
+
const repoName = readRemoteName(workspaceRoot) ?? path5.basename(workspaceRoot);
|
|
1475
|
+
resolved.set(workspaceRoot, repoName);
|
|
1476
|
+
return repoName;
|
|
1477
|
+
};
|
|
1478
|
+
|
|
913
1479
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
914
1480
|
import { rm } from "fs/promises";
|
|
915
|
-
import
|
|
1481
|
+
import path6 from "path";
|
|
916
1482
|
|
|
917
1483
|
// pkgs/@akanjs/devkit/workspaceLayout.ts
|
|
918
1484
|
var appRootAllowedFiles = new Set([
|
|
@@ -946,6 +1512,34 @@ var appRootAllowedDirs = new Set([
|
|
|
946
1512
|
"ui",
|
|
947
1513
|
"webkit"
|
|
948
1514
|
]);
|
|
1515
|
+
var libRootAllowedFiles = new Set([
|
|
1516
|
+
"AGENTS.md",
|
|
1517
|
+
"CLAUDE.md",
|
|
1518
|
+
"README.md",
|
|
1519
|
+
"akan.config.ts",
|
|
1520
|
+
"akan.lib.json",
|
|
1521
|
+
"client.ts",
|
|
1522
|
+
"index.ts",
|
|
1523
|
+
"package.json",
|
|
1524
|
+
"server.ts",
|
|
1525
|
+
"tsconfig.json",
|
|
1526
|
+
"tsconfig.spec.json",
|
|
1527
|
+
"tsconfig.tsbuildinfo"
|
|
1528
|
+
]);
|
|
1529
|
+
var libRootAllowedDirs = new Set([
|
|
1530
|
+
"common",
|
|
1531
|
+
"env",
|
|
1532
|
+
"lib",
|
|
1533
|
+
"page",
|
|
1534
|
+
"plugin",
|
|
1535
|
+
"private",
|
|
1536
|
+
"public",
|
|
1537
|
+
"srvkit",
|
|
1538
|
+
"ui",
|
|
1539
|
+
"webkit"
|
|
1540
|
+
]);
|
|
1541
|
+
var rootAllowedFiles = { app: appRootAllowedFiles, lib: libRootAllowedFiles };
|
|
1542
|
+
var rootAllowedDirs = { app: appRootAllowedDirs, lib: libRootAllowedDirs };
|
|
949
1543
|
var libFacetRootAllowedFiles = new Set([
|
|
950
1544
|
"cnst.ts",
|
|
951
1545
|
"db.ts",
|
|
@@ -957,7 +1551,9 @@ var libFacetRootAllowedFiles = new Set([
|
|
|
957
1551
|
"useClient.ts",
|
|
958
1552
|
"useServer.ts"
|
|
959
1553
|
]);
|
|
960
|
-
var
|
|
1554
|
+
var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
|
|
1555
|
+
var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
|
|
1556
|
+
var isScannedRootEntry = (type, name) => !name.startsWith(".") || rootAllowedDirs[type].has(name);
|
|
961
1557
|
|
|
962
1558
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
963
1559
|
var databaseFileTypes = [
|
|
@@ -986,18 +1582,16 @@ var moduleUiFileTypes = {
|
|
|
986
1582
|
scalar: new Set(["Template", "Unit"])
|
|
987
1583
|
};
|
|
988
1584
|
var testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
|
|
989
|
-
var
|
|
990
|
-
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-f6grrep3.js")).TypeScriptDependencyScanner.from(exec);
|
|
1585
|
+
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-c64m6fxp.js")).TypeScriptDependencyScanner.from(exec);
|
|
991
1586
|
var isAllowedTestFile = (filename) => testFilePattern.test(filename);
|
|
992
|
-
var
|
|
993
|
-
var getScanPath = (exec, relativePath) => path4.posix.join(`${exec.type}s`, exec.name, relativePath.split(path4.sep).join("/"));
|
|
1587
|
+
var getScanPath = (exec, relativePath) => path6.posix.join(`${exec.type}s`, exec.name, relativePath.split(path6.sep).join("/"));
|
|
994
1588
|
async function clearGeneratedRootCapacitorConfigs(exec) {
|
|
995
1589
|
if (exec.type !== "app")
|
|
996
1590
|
return;
|
|
997
1591
|
await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm(exec.getPath(filename), { force: true })));
|
|
998
1592
|
}
|
|
999
1593
|
var getModuleNameFromPath = (kind, modulePath) => {
|
|
1000
|
-
const dirname =
|
|
1594
|
+
const dirname = path6.basename(modulePath);
|
|
1001
1595
|
return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
|
|
1002
1596
|
};
|
|
1003
1597
|
async function assertScanConvention(exec, libRoot) {
|
|
@@ -1006,28 +1600,31 @@ async function assertScanConvention(exec, libRoot) {
|
|
|
1006
1600
|
const addViolation = (relativePath, reason) => {
|
|
1007
1601
|
violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
|
|
1008
1602
|
};
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
});
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1603
|
+
const allowedRootFiles = rootAllowedFiles[exec.type];
|
|
1604
|
+
const allowedRootDirs = rootAllowedDirs[exec.type];
|
|
1605
|
+
const { files, dirs } = await exec.getFilesAndDirs(".");
|
|
1606
|
+
files.filter((filename) => !allowedRootFiles.has(filename)).forEach((filename) => {
|
|
1607
|
+
addViolation(filename, `unsupported ${exec.type} root file`);
|
|
1608
|
+
});
|
|
1609
|
+
dirs.filter((dirname) => !allowedRootDirs.has(dirname)).forEach((dirname) => {
|
|
1610
|
+
addViolation(dirname, `unsupported ${exec.type} root folder`);
|
|
1611
|
+
});
|
|
1612
|
+
if (exec.type === "lib")
|
|
1613
|
+
for (const { relativePath, reason } of await exec.getPageConventionViolations())
|
|
1614
|
+
addViolation(relativePath, reason);
|
|
1615
|
+
libRoot.files.filter((filename) => !isAllowedLibFacetRootFile(filename)).forEach((filename) => {
|
|
1616
|
+
addViolation(path6.join("lib", filename), "unsupported lib facet root file");
|
|
1020
1617
|
});
|
|
1021
1618
|
libRoot.dirs.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname)).forEach((dirname) => {
|
|
1022
|
-
addViolation(
|
|
1619
|
+
addViolation(path6.join("lib", dirname), "unsupported internal lib folder");
|
|
1023
1620
|
});
|
|
1024
1621
|
const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
|
|
1025
1622
|
const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
|
|
1026
1623
|
const scalarDirs = await exec.readdir("lib/__scalar");
|
|
1027
1624
|
await Promise.all([
|
|
1028
|
-
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database",
|
|
1029
|
-
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service",
|
|
1030
|
-
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar",
|
|
1625
|
+
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path6.join("lib", dirname))),
|
|
1626
|
+
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path6.join("lib", dirname))),
|
|
1627
|
+
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path6.join("lib/__scalar", dirname)))
|
|
1031
1628
|
]);
|
|
1032
1629
|
if (violations.length > 0) {
|
|
1033
1630
|
throw new Error(`[scan-convention]
|
|
@@ -1039,11 +1636,11 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
|
|
|
1039
1636
|
const { files, dirs } = await exec.getFilesAndDirs(modulePath);
|
|
1040
1637
|
const moduleName = getModuleNameFromPath(kind, modulePath);
|
|
1041
1638
|
dirs.forEach((dirname) => {
|
|
1042
|
-
violations.push(`${getScanPath(exec,
|
|
1639
|
+
violations.push(`${getScanPath(exec, path6.join(modulePath, dirname))}: unsupported module folder`);
|
|
1043
1640
|
});
|
|
1044
1641
|
const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
|
|
1045
1642
|
files.forEach((filename) => {
|
|
1046
|
-
const filePath =
|
|
1643
|
+
const filePath = path6.join(modulePath, filename);
|
|
1047
1644
|
if (filename === "index.ts" || filename === "index.tsx" || isAllowedTestFile(filename))
|
|
1048
1645
|
return;
|
|
1049
1646
|
if (filename === `${moduleName}.abstract.md`)
|
|
@@ -1087,7 +1684,7 @@ class ScanInfo {
|
|
|
1087
1684
|
{ all: new Set, databases: new Set, services: new Set, scalars: new Set }
|
|
1088
1685
|
]));
|
|
1089
1686
|
static async getScanResult(exec) {
|
|
1090
|
-
const [
|
|
1687
|
+
const [akanConfig, scanner, pkgs, libs] = await Promise.all([
|
|
1091
1688
|
exec.getConfig(),
|
|
1092
1689
|
createDependencyScanner(exec),
|
|
1093
1690
|
exec.workspace.getPkgs(),
|
|
@@ -1123,7 +1720,7 @@ class ScanInfo {
|
|
|
1123
1720
|
});
|
|
1124
1721
|
await Promise.all([
|
|
1125
1722
|
...databaseDirs.map(async (name) => {
|
|
1126
|
-
const filenames = await exec.readdir(
|
|
1723
|
+
const filenames = await exec.readdir(path6.join("lib", name));
|
|
1127
1724
|
filenames.forEach((filename) => {
|
|
1128
1725
|
if (filename.endsWith(".constant.ts"))
|
|
1129
1726
|
files.constant.databases.push(name);
|
|
@@ -1151,7 +1748,7 @@ class ScanInfo {
|
|
|
1151
1748
|
}),
|
|
1152
1749
|
...serviceDirs.map(async (dirname) => {
|
|
1153
1750
|
const name = dirname.slice(1);
|
|
1154
|
-
const filenames = await exec.readdir(
|
|
1751
|
+
const filenames = await exec.readdir(path6.join("lib", dirname));
|
|
1155
1752
|
filenames.forEach((filename) => {
|
|
1156
1753
|
if (filename.endsWith(".dictionary.ts"))
|
|
1157
1754
|
files.dictionary.services.push(name);
|
|
@@ -1174,7 +1771,7 @@ class ScanInfo {
|
|
|
1174
1771
|
});
|
|
1175
1772
|
}),
|
|
1176
1773
|
...scalarDirs.map(async (name) => {
|
|
1177
|
-
const filenames = await exec.readdir(
|
|
1774
|
+
const filenames = await exec.readdir(path6.join("lib/__scalar", name));
|
|
1178
1775
|
filenames.forEach((filename) => {
|
|
1179
1776
|
if (filename.endsWith(".constant.ts"))
|
|
1180
1777
|
files.constant.scalars.push(name);
|
|
@@ -1196,12 +1793,11 @@ class ScanInfo {
|
|
|
1196
1793
|
})
|
|
1197
1794
|
]);
|
|
1198
1795
|
const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
|
|
1199
|
-
const
|
|
1796
|
+
const common = {
|
|
1200
1797
|
name: exec.name,
|
|
1201
1798
|
type: exec.type,
|
|
1202
1799
|
repoName: exec.workspace.repoName,
|
|
1203
|
-
serveDomain: WorkspaceExecutor.getBaseDevEnv(
|
|
1204
|
-
akanConfig: akanConfig2,
|
|
1800
|
+
serveDomain: WorkspaceExecutor.getBaseDevEnv(path6.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
|
|
1205
1801
|
files,
|
|
1206
1802
|
libDeps,
|
|
1207
1803
|
pkgDeps,
|
|
@@ -1209,6 +1805,7 @@ class ScanInfo {
|
|
|
1209
1805
|
devDependencies: npmDevDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
|
|
1210
1806
|
routes
|
|
1211
1807
|
};
|
|
1808
|
+
const scanResult = akanConfig instanceof AkanAppConfig ? { ...common, akanConfig } : { ...common, akanConfig };
|
|
1212
1809
|
return scanResult;
|
|
1213
1810
|
}
|
|
1214
1811
|
constructor(scanResult) {
|
|
@@ -1402,16 +1999,16 @@ class LibInfo extends ScanInfo {
|
|
|
1402
1999
|
class PkgInfo {
|
|
1403
2000
|
exec;
|
|
1404
2001
|
name;
|
|
1405
|
-
scanResult;
|
|
2002
|
+
#scanResult;
|
|
1406
2003
|
static async scanExecutor(exec) {
|
|
1407
2004
|
const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
|
|
1408
2005
|
const scanner = await createDependencyScanner(exec);
|
|
1409
2006
|
const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
|
|
1410
|
-
const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((
|
|
2007
|
+
const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path) => tsconfig.compilerOptions.paths?.[path]?.some((resolve) => resolve.startsWith("pkgs/"))).map((path) => path.replace("/*", "")));
|
|
1411
2008
|
const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
|
|
1412
|
-
const pkgDeps = [...pkgPathDepSet].map((
|
|
1413
|
-
const pathSplitLength =
|
|
1414
|
-
return (tsconfig.compilerOptions.paths?.[
|
|
2009
|
+
const pkgDeps = [...pkgPathDepSet].map((path) => {
|
|
2010
|
+
const pathSplitLength = path.split("/").length;
|
|
2011
|
+
return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
|
|
1415
2012
|
}).filter((pkg) => pkg !== exec.name);
|
|
1416
2013
|
const pkgScanResult = {
|
|
1417
2014
|
name: exec.name,
|
|
@@ -1433,10 +2030,10 @@ class PkgInfo {
|
|
|
1433
2030
|
constructor(exec, scanResult) {
|
|
1434
2031
|
this.exec = exec;
|
|
1435
2032
|
this.name = exec.name;
|
|
1436
|
-
this
|
|
2033
|
+
this.#scanResult = scanResult;
|
|
1437
2034
|
}
|
|
1438
2035
|
getScanResult() {
|
|
1439
|
-
return this
|
|
2036
|
+
return this.#scanResult;
|
|
1440
2037
|
}
|
|
1441
2038
|
}
|
|
1442
2039
|
|
|
@@ -1634,7 +2231,7 @@ class Executor {
|
|
|
1634
2231
|
}));
|
|
1635
2232
|
});
|
|
1636
2233
|
proc.on("exit", (code, signal) => {
|
|
1637
|
-
if (
|
|
2234
|
+
if (code || signal)
|
|
1638
2235
|
reject(new CommandExecutionError({
|
|
1639
2236
|
command,
|
|
1640
2237
|
cwd,
|
|
@@ -1732,7 +2329,7 @@ class Executor {
|
|
|
1732
2329
|
}));
|
|
1733
2330
|
});
|
|
1734
2331
|
proc.on("exit", (code, signal) => {
|
|
1735
|
-
if (
|
|
2332
|
+
if (code || signal)
|
|
1736
2333
|
reject(new CommandExecutionError({
|
|
1737
2334
|
command: modulePath,
|
|
1738
2335
|
args,
|
|
@@ -1748,10 +2345,10 @@ class Executor {
|
|
|
1748
2345
|
});
|
|
1749
2346
|
}
|
|
1750
2347
|
getPath(filePath) {
|
|
1751
|
-
if (
|
|
2348
|
+
if (path7.isAbsolute(filePath))
|
|
1752
2349
|
return filePath;
|
|
1753
2350
|
if (filePath.startsWith("."))
|
|
1754
|
-
return
|
|
2351
|
+
return path7.join(this.cwdPath, filePath);
|
|
1755
2352
|
const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
|
|
1756
2353
|
const targetParts = filePath.split(/[\\/]/).filter(Boolean);
|
|
1757
2354
|
let overlapLength = 0;
|
|
@@ -1765,12 +2362,12 @@ class Executor {
|
|
|
1765
2362
|
if (isOverlap)
|
|
1766
2363
|
overlapLength = i;
|
|
1767
2364
|
}
|
|
1768
|
-
return
|
|
2365
|
+
return path7.join(this.cwdPath, ...targetParts.slice(overlapLength));
|
|
1769
2366
|
}
|
|
1770
2367
|
async mkdir(dirPath) {
|
|
1771
2368
|
const writePath = this.getPath(dirPath);
|
|
1772
2369
|
if (!await FileSys.dirExists(writePath))
|
|
1773
|
-
await
|
|
2370
|
+
await mkdir2(writePath, { recursive: true });
|
|
1774
2371
|
this.logger.verbose(`Make directory ${writePath}`);
|
|
1775
2372
|
return this;
|
|
1776
2373
|
}
|
|
@@ -1808,7 +2405,7 @@ class Executor {
|
|
|
1808
2405
|
return this;
|
|
1809
2406
|
}
|
|
1810
2407
|
async removeDir(dirPath) {
|
|
1811
|
-
const readPath =
|
|
2408
|
+
const readPath = path7.join(this.getPath(dirPath), ".");
|
|
1812
2409
|
if (await FileSys.entryExists(readPath))
|
|
1813
2410
|
await rm2(readPath, { recursive: true, force: true });
|
|
1814
2411
|
this.logger.verbose(`Remove directory ${readPath}`);
|
|
@@ -1816,10 +2413,11 @@ class Executor {
|
|
|
1816
2413
|
}
|
|
1817
2414
|
async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
|
|
1818
2415
|
const writePath = this.getPath(filePath);
|
|
1819
|
-
const dir =
|
|
2416
|
+
const dir = path7.dirname(writePath);
|
|
1820
2417
|
if (!await FileSys.dirExists(dir))
|
|
1821
|
-
await
|
|
1822
|
-
let contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2)
|
|
2418
|
+
await mkdir2(dir, { recursive: true });
|
|
2419
|
+
let contentStr = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}
|
|
2420
|
+
`;
|
|
1823
2421
|
if (await FileSys.fileExists(writePath)) {
|
|
1824
2422
|
const currentContent = await FileSys.readText(writePath);
|
|
1825
2423
|
if (currentContent === contentStr || !overwrite) {
|
|
@@ -1841,7 +2439,7 @@ class Executor {
|
|
|
1841
2439
|
await this.writeFile(filePath, content);
|
|
1842
2440
|
}
|
|
1843
2441
|
async getLocalFile(targetPath) {
|
|
1844
|
-
const filePath =
|
|
2442
|
+
const filePath = path7.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
|
|
1845
2443
|
const content = await this.readFile(filePath);
|
|
1846
2444
|
return { filePath, content };
|
|
1847
2445
|
}
|
|
@@ -1860,7 +2458,7 @@ class Executor {
|
|
|
1860
2458
|
return;
|
|
1861
2459
|
const isDirectory = (await stat(src)).isDirectory();
|
|
1862
2460
|
if (!await FileSys.exists(dest) && isDirectory)
|
|
1863
|
-
await
|
|
2461
|
+
await mkdir2(dest, { recursive: true });
|
|
1864
2462
|
if (dereference)
|
|
1865
2463
|
await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
|
|
1866
2464
|
else
|
|
@@ -1938,26 +2536,26 @@ class Executor {
|
|
|
1938
2536
|
const result = await getContent.default(scanInfo ?? null, dict, options);
|
|
1939
2537
|
if (result === null)
|
|
1940
2538
|
return null;
|
|
1941
|
-
const filename = typeof result === "object" ? result.filename :
|
|
2539
|
+
const filename = typeof result === "object" ? result.filename : path7.basename(targetPath).replace(".js", ".ts");
|
|
1942
2540
|
const content = typeof result === "object" ? result.content : result;
|
|
1943
|
-
const dirname =
|
|
1944
|
-
const convertedTargetPath = Object.entries(dict).reduce((
|
|
2541
|
+
const dirname = path7.dirname(targetPath);
|
|
2542
|
+
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), `${dirname}/${filename}`);
|
|
1945
2543
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1946
2544
|
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
1947
2545
|
} else if (targetPath.endsWith(".template")) {
|
|
1948
2546
|
const content = await FileSys.readText(templatePath);
|
|
1949
|
-
const convertedTargetPath = Object.entries(dict).reduce((
|
|
2547
|
+
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
|
|
1950
2548
|
const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
|
|
1951
2549
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1952
2550
|
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
1953
2551
|
overwrite
|
|
1954
2552
|
});
|
|
1955
|
-
} else if (staticTemplateFileExtensions.has(
|
|
1956
|
-
const convertedTargetPath = Object.entries(dict).reduce((
|
|
2553
|
+
} else if (staticTemplateFileExtensions.has(path7.extname(targetPath).toLowerCase())) {
|
|
2554
|
+
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
|
|
1957
2555
|
const writePath = this.getPath(convertedTargetPath);
|
|
1958
|
-
const dirname =
|
|
2556
|
+
const dirname = path7.dirname(writePath);
|
|
1959
2557
|
if (!await FileSys.dirExists(dirname))
|
|
1960
|
-
await
|
|
2558
|
+
await mkdir2(dirname, { recursive: true });
|
|
1961
2559
|
await copyFile(templatePath, writePath);
|
|
1962
2560
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
1963
2561
|
return { filePath: writePath, content: "" };
|
|
@@ -1976,10 +2574,10 @@ class Executor {
|
|
|
1976
2574
|
const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
|
|
1977
2575
|
const prefixTemplatePath = templatePath;
|
|
1978
2576
|
if ((await stat(prefixTemplatePath)).isFile()) {
|
|
1979
|
-
const filename =
|
|
2577
|
+
const filename = path7.basename(prefixTemplatePath);
|
|
1980
2578
|
const fileContent = await this.#applyTemplateFile({
|
|
1981
2579
|
templatePath: prefixTemplatePath,
|
|
1982
|
-
targetPath:
|
|
2580
|
+
targetPath: path7.join(basePath, filename),
|
|
1983
2581
|
scanInfo,
|
|
1984
2582
|
overwrite
|
|
1985
2583
|
}, dict, options);
|
|
@@ -1987,19 +2585,19 @@ class Executor {
|
|
|
1987
2585
|
} else {
|
|
1988
2586
|
const subdirs = await readDirEntries(templatePath);
|
|
1989
2587
|
const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
|
|
1990
|
-
const subpath =
|
|
2588
|
+
const subpath = path7.join(templatePath, subdir);
|
|
1991
2589
|
if ((await stat(subpath)).isFile()) {
|
|
1992
2590
|
const fileContent = await this.#applyTemplateFile({
|
|
1993
2591
|
templatePath: subpath,
|
|
1994
|
-
targetPath:
|
|
2592
|
+
targetPath: path7.join(basePath, subdir),
|
|
1995
2593
|
scanInfo,
|
|
1996
2594
|
overwrite
|
|
1997
2595
|
}, dict, options);
|
|
1998
2596
|
return fileContent ? [fileContent] : [];
|
|
1999
2597
|
} else
|
|
2000
2598
|
return await this._applyTemplate({
|
|
2001
|
-
basePath:
|
|
2002
|
-
template:
|
|
2599
|
+
basePath: path7.join(basePath, subdir),
|
|
2600
|
+
template: path7.join(template, subdir),
|
|
2003
2601
|
scanInfo,
|
|
2004
2602
|
dict,
|
|
2005
2603
|
overwrite,
|
|
@@ -2011,8 +2609,8 @@ class Executor {
|
|
|
2011
2609
|
}
|
|
2012
2610
|
async#resolveTemplateRoot() {
|
|
2013
2611
|
const candidates = [
|
|
2014
|
-
|
|
2015
|
-
|
|
2612
|
+
path7.resolve(getDirname2(import.meta.url), "templates"),
|
|
2613
|
+
path7.resolve(getDirname2(import.meta.url), "../cli/templates")
|
|
2016
2614
|
];
|
|
2017
2615
|
for (const candidate of candidates) {
|
|
2018
2616
|
if (await FileSys.dirExists(candidate))
|
|
@@ -2025,29 +2623,43 @@ class Executor {
|
|
|
2025
2623
|
...options.dict ?? {},
|
|
2026
2624
|
...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
|
|
2027
2625
|
};
|
|
2028
|
-
|
|
2626
|
+
const fileContents = await this._applyTemplate({ ...options, dict });
|
|
2627
|
+
await this.#formatAppliedTemplate(fileContents);
|
|
2628
|
+
return fileContents;
|
|
2629
|
+
}
|
|
2630
|
+
async#formatAppliedTemplate(fileContents) {
|
|
2631
|
+
const filePaths = fileContents.map((fileContent) => fileContent.filePath).filter((filePath) => filePath.endsWith(".ts") || filePath.endsWith(".tsx"));
|
|
2632
|
+
if (filePaths.length === 0)
|
|
2633
|
+
return;
|
|
2634
|
+
try {
|
|
2635
|
+
const { fixed } = await this.getLinter().fixFiles(filePaths);
|
|
2636
|
+
if (fixed.length > 0)
|
|
2637
|
+
this.logger.verbose(`Formatted ${fixed.length} scaffolded file(s)`);
|
|
2638
|
+
} catch (err) {
|
|
2639
|
+
this.logger.verbose(`Skipped formatting scaffolded files: ${err instanceof Error ? err.message : String(err)}`);
|
|
2640
|
+
}
|
|
2029
2641
|
}
|
|
2030
2642
|
async getTypeChecker() {
|
|
2031
|
-
const { TypeChecker } = await import("./typeChecker-
|
|
2643
|
+
const { TypeChecker } = await import("./typeChecker-3xgrzc2v.js");
|
|
2032
2644
|
this.typeChecker ??= new TypeChecker(this);
|
|
2033
2645
|
return this.typeChecker;
|
|
2034
2646
|
}
|
|
2035
2647
|
async typeCheck(filePath) {
|
|
2036
|
-
const
|
|
2648
|
+
const path = this.getPath(filePath);
|
|
2037
2649
|
const typeChecker = await this.getTypeChecker();
|
|
2038
|
-
const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(
|
|
2650
|
+
const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path);
|
|
2039
2651
|
const message = typeChecker.formatDiagnostics(fileDiagnostics);
|
|
2040
2652
|
return { fileDiagnostics, fileErrors, fileWarnings, message };
|
|
2041
2653
|
}
|
|
2042
2654
|
async typeCheckAsync(filePath) {
|
|
2043
|
-
const
|
|
2655
|
+
const path = this.getPath(filePath);
|
|
2044
2656
|
const entry = await this.#resolveTypecheckWorkerEntry();
|
|
2045
2657
|
const proc = Bun.spawn([process.execPath, entry], {
|
|
2046
2658
|
cwd: this.cwdPath,
|
|
2047
2659
|
env: {
|
|
2048
2660
|
...process.env,
|
|
2049
2661
|
AKAN_TYPECHECK_CWD: this.cwdPath,
|
|
2050
|
-
AKAN_TYPECHECK_FILE:
|
|
2662
|
+
AKAN_TYPECHECK_FILE: path
|
|
2051
2663
|
},
|
|
2052
2664
|
stdout: "pipe",
|
|
2053
2665
|
stderr: "pipe"
|
|
@@ -2068,13 +2680,13 @@ class Executor {
|
|
|
2068
2680
|
};
|
|
2069
2681
|
}
|
|
2070
2682
|
async#resolveTypecheckWorkerEntry() {
|
|
2071
|
-
const dirname =
|
|
2683
|
+
const dirname = getDirname2(import.meta.url);
|
|
2072
2684
|
const candidates = [
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2685
|
+
path7.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2686
|
+
path7.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2687
|
+
path7.join(dirname, "typecheck/typecheck.proc.ts"),
|
|
2688
|
+
path7.join(dirname, "typecheck.proc.js"),
|
|
2689
|
+
path7.join(dirname, "typecheck.proc.ts")
|
|
2078
2690
|
];
|
|
2079
2691
|
for (const candidate of candidates)
|
|
2080
2692
|
if (await Bun.file(candidate).exists())
|
|
@@ -2086,9 +2698,9 @@ class Executor {
|
|
|
2086
2698
|
return this.linter;
|
|
2087
2699
|
}
|
|
2088
2700
|
async lint(filePath, { fix = false, dryRun = false } = {}) {
|
|
2089
|
-
const
|
|
2701
|
+
const path = this.getPath(filePath);
|
|
2090
2702
|
const linter = this.getLinter();
|
|
2091
|
-
const { results, errors, warnings } = await linter.lint(
|
|
2703
|
+
const { results, errors, warnings } = await linter.lint(path, {
|
|
2092
2704
|
fix,
|
|
2093
2705
|
dryRun
|
|
2094
2706
|
});
|
|
@@ -2109,7 +2721,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
2109
2721
|
static #execs = new Map;
|
|
2110
2722
|
static fromRoot({
|
|
2111
2723
|
workspaceRoot = process.cwd(),
|
|
2112
|
-
repoName =
|
|
2724
|
+
repoName = resolveRepoName(workspaceRoot)
|
|
2113
2725
|
} = {}) {
|
|
2114
2726
|
return WorkspaceExecutor.#execs.get(repoName) ?? new WorkspaceExecutor({ workspaceRoot, repoName });
|
|
2115
2727
|
}
|
|
@@ -2201,7 +2813,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
2201
2813
|
await Promise.all(dirs.map(async (dir) => {
|
|
2202
2814
|
if (dir.includes("_") || AVOID_DIRS.includes(dir))
|
|
2203
2815
|
return;
|
|
2204
|
-
const dirPath =
|
|
2816
|
+
const dirPath = path7.join(dirname, dir);
|
|
2205
2817
|
if ((await stat(dirPath)).isDirectory()) {
|
|
2206
2818
|
results.push(`${prefix}${dir}`);
|
|
2207
2819
|
if (maxDepth > 0)
|
|
@@ -2214,10 +2826,20 @@ class WorkspaceExecutor extends Executor {
|
|
|
2214
2826
|
}
|
|
2215
2827
|
async commit(message, { init = false, add = true } = {}) {
|
|
2216
2828
|
if (init)
|
|
2217
|
-
await this.
|
|
2829
|
+
await this.spawn("git", ["init", "--quiet"]);
|
|
2218
2830
|
if (add)
|
|
2219
|
-
await this.
|
|
2220
|
-
await this.
|
|
2831
|
+
await this.spawn("git", ["add", "."]);
|
|
2832
|
+
await this.spawn("git", ["commit", "--quiet", "-m", message]);
|
|
2833
|
+
}
|
|
2834
|
+
async hasChanges() {
|
|
2835
|
+
return !!(await this.spawn("git", ["status", "--porcelain"])).trim();
|
|
2836
|
+
}
|
|
2837
|
+
async listGitFiles(paths, { untracked = false } = {}) {
|
|
2838
|
+
if (!paths.length)
|
|
2839
|
+
return [];
|
|
2840
|
+
const mode = untracked ? ["--cached", "--others", "--exclude-standard"] : ["--cached"];
|
|
2841
|
+
const stdout = await this.spawn("git", ["ls-files", "-z", ...mode, "--", ...paths]);
|
|
2842
|
+
return stdout.split("\x00").filter((file) => !!file).sort();
|
|
2221
2843
|
}
|
|
2222
2844
|
async#getDirHasFile(basePath, targetFilename) {
|
|
2223
2845
|
const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
|
|
@@ -2226,9 +2848,9 @@ class WorkspaceExecutor extends Executor {
|
|
|
2226
2848
|
await Promise.all(dirs.map(async (dir) => {
|
|
2227
2849
|
if (AVOID_DIRS.includes(dir))
|
|
2228
2850
|
return;
|
|
2229
|
-
const dirPath =
|
|
2851
|
+
const dirPath = path7.join(dirname, dir);
|
|
2230
2852
|
if (await FileSys.dirExists(dirPath)) {
|
|
2231
|
-
const hasTargetFile = await FileSys.fileExists(
|
|
2853
|
+
const hasTargetFile = await FileSys.fileExists(path7.join(dirPath, targetFilename));
|
|
2232
2854
|
if (hasTargetFile)
|
|
2233
2855
|
results.push(`${prefix}${dir}`);
|
|
2234
2856
|
if (maxDepth > 0)
|
|
@@ -2294,8 +2916,20 @@ class SysExecutor extends Executor {
|
|
|
2294
2916
|
return this.#akanConfig;
|
|
2295
2917
|
}
|
|
2296
2918
|
async getModules() {
|
|
2297
|
-
const
|
|
2298
|
-
return await this.workspace.getDirInModule(
|
|
2919
|
+
const path = this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
|
|
2920
|
+
return await this.workspace.getDirInModule(path, this.name);
|
|
2921
|
+
}
|
|
2922
|
+
async getPageConventionViolations() {
|
|
2923
|
+
if (!await this.exists("page"))
|
|
2924
|
+
return [];
|
|
2925
|
+
const violations = [];
|
|
2926
|
+
for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
|
|
2927
|
+
const relativePath = entry.split(path7.sep).join("/");
|
|
2928
|
+
const reason = getPageSourceFileViolation(relativePath);
|
|
2929
|
+
if (reason)
|
|
2930
|
+
violations.push({ relativePath: `page/${relativePath}`, reason });
|
|
2931
|
+
}
|
|
2932
|
+
return violations;
|
|
2299
2933
|
}
|
|
2300
2934
|
#scanInfo = null;
|
|
2301
2935
|
hasScanInfo() {
|
|
@@ -2399,7 +3033,7 @@ class SysExecutor extends Executor {
|
|
|
2399
3033
|
await this.setPackageJson(libPkgJsonWithDeps);
|
|
2400
3034
|
}
|
|
2401
3035
|
async getLocalFile(targetPath) {
|
|
2402
|
-
const filePath =
|
|
3036
|
+
const filePath = path7.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
|
|
2403
3037
|
const content = await this.workspace.readFile(filePath);
|
|
2404
3038
|
return { filePath, content };
|
|
2405
3039
|
}
|
|
@@ -2485,7 +3119,7 @@ class SysExecutor extends Executor {
|
|
|
2485
3119
|
class AppExecutor extends SysExecutor {
|
|
2486
3120
|
static #routeSourceValidator = null;
|
|
2487
3121
|
static async#getRouteSourceValidator() {
|
|
2488
|
-
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-
|
|
3122
|
+
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-ze6x894c.js")).RouteSourceValidator;
|
|
2489
3123
|
return AppExecutor.#routeSourceValidator;
|
|
2490
3124
|
}
|
|
2491
3125
|
dist;
|
|
@@ -2496,9 +3130,9 @@ class AppExecutor extends SysExecutor {
|
|
|
2496
3130
|
}
|
|
2497
3131
|
static #execs = new Map;
|
|
2498
3132
|
static from(executor, name) {
|
|
2499
|
-
const
|
|
2500
|
-
if (
|
|
2501
|
-
return
|
|
3133
|
+
const exec = AppExecutor.#execs.get(name);
|
|
3134
|
+
if (exec)
|
|
3135
|
+
return exec;
|
|
2502
3136
|
else if (executor instanceof WorkspaceExecutor)
|
|
2503
3137
|
return new AppExecutor({ workspace: executor, name });
|
|
2504
3138
|
else
|
|
@@ -2534,25 +3168,29 @@ class AppExecutor extends SysExecutor {
|
|
|
2534
3168
|
};
|
|
2535
3169
|
}
|
|
2536
3170
|
async prepareCommand(type) {
|
|
2537
|
-
const
|
|
2538
|
-
const databaseMode = process.env.AKAN_DATABASE_MODE ??
|
|
3171
|
+
const akanConfig = await this.getConfig();
|
|
3172
|
+
const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
|
|
2539
3173
|
const routeEnv = {
|
|
2540
|
-
AKAN_PUBLIC_BASE_PATHS: [...
|
|
3174
|
+
AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
|
|
2541
3175
|
AKAN_DATABASE_MODE: databaseMode
|
|
2542
3176
|
};
|
|
2543
3177
|
Object.assign(process.env, routeEnv);
|
|
2544
3178
|
if (type === "build") {
|
|
2545
3179
|
this.#excludeDevOnlyPages = true;
|
|
2546
3180
|
this.#pageKeys = null;
|
|
2547
|
-
|
|
2548
|
-
await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
|
|
3181
|
+
await this.dist.removeDir(this.dist.cwdPath);
|
|
2549
3182
|
await Promise.all([this.dist.mkdir("private"), this.dist.mkdir("public")]);
|
|
2550
3183
|
await Promise.all([
|
|
2551
3184
|
this.cp("private", `${this.dist.cwdPath}/private`, { dereference: true }),
|
|
2552
|
-
this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })
|
|
3185
|
+
...akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []
|
|
2553
3186
|
]);
|
|
2554
|
-
} else
|
|
3187
|
+
} else {
|
|
2555
3188
|
await this.removeDir(".akan");
|
|
3189
|
+
if (!akanConfig.web.ssr || !akanConfig.web.csr)
|
|
3190
|
+
this.logger.verbose(`akan.config.ts disables web.${!akanConfig.web.ssr ? "ssr" : "csr"}; \`akan start\` still serves it, \`akan build\` will not`);
|
|
3191
|
+
}
|
|
3192
|
+
if (type === "start")
|
|
3193
|
+
process.env.NODE_ENV = "development";
|
|
2556
3194
|
const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
|
|
2557
3195
|
const env = this.getCommandEnv({
|
|
2558
3196
|
AKAN_COMMAND_TYPE: type,
|
|
@@ -2600,7 +3238,7 @@ class AppExecutor extends SysExecutor {
|
|
|
2600
3238
|
async getPageKeys({ refresh } = {}) {
|
|
2601
3239
|
if (this.#pageKeys && !refresh)
|
|
2602
3240
|
return this.#pageKeys;
|
|
2603
|
-
const
|
|
3241
|
+
const akanConfig = await this.getConfig();
|
|
2604
3242
|
const glob = new Bun.Glob("**/*");
|
|
2605
3243
|
const pageKeys = [];
|
|
2606
3244
|
const owners = new Map;
|
|
@@ -2614,16 +3252,16 @@ class AppExecutor extends SysExecutor {
|
|
|
2614
3252
|
absolute: false,
|
|
2615
3253
|
onlyFiles: true
|
|
2616
3254
|
})) {
|
|
2617
|
-
const segments = rel.split(
|
|
3255
|
+
const segments = rel.split(path7.sep);
|
|
2618
3256
|
if (segments.some((s) => s === "node_modules"))
|
|
2619
3257
|
continue;
|
|
2620
3258
|
const posix = `${root.keyPrefix}${segments.join("/")}`;
|
|
2621
|
-
const absPath =
|
|
3259
|
+
const absPath = path7.join(root.dir, ...segments);
|
|
2622
3260
|
validatePageSourceFile(posix, { filePath: absPath });
|
|
2623
3261
|
if (!isRouteSourceFile(posix))
|
|
2624
3262
|
continue;
|
|
2625
3263
|
const key = `./${posix}`;
|
|
2626
|
-
validateSubRoutePageKey(key,
|
|
3264
|
+
validateSubRoutePageKey(key, akanConfig.basePaths, {
|
|
2627
3265
|
appName: this.name,
|
|
2628
3266
|
filePath: absPath
|
|
2629
3267
|
});
|
|
@@ -2641,7 +3279,8 @@ class AppExecutor extends SysExecutor {
|
|
|
2641
3279
|
}
|
|
2642
3280
|
if (!owner)
|
|
2643
3281
|
owners.set(routeId, { absPath, fromLib });
|
|
2644
|
-
const
|
|
3282
|
+
const layoutSegments = getRootBoundarySegments(key);
|
|
3283
|
+
const isRootLayout = layoutSegments !== null && isRootBoundarySegments(layoutSegments, akanConfig.basePaths);
|
|
2645
3284
|
const routeSource = await Bun.file(absPath).text();
|
|
2646
3285
|
const validator = await AppExecutor.#getRouteSourceValidator();
|
|
2647
3286
|
if (parsed.kind === "overrides")
|
|
@@ -2672,10 +3311,10 @@ class AppExecutor extends SysExecutor {
|
|
|
2672
3311
|
return pageKeys.filter((key) => !isDevOnly(key));
|
|
2673
3312
|
}
|
|
2674
3313
|
async getPageRoots() {
|
|
2675
|
-
const
|
|
3314
|
+
const akanConfig = await this.getConfig();
|
|
2676
3315
|
const pageDir = `${this.cwdPath}/page`;
|
|
2677
3316
|
const roots = [{ dir: pageDir, realDir: pageDir, keyPrefix: "" }];
|
|
2678
|
-
for (const parent of AppExecutor.#pageLibParents(
|
|
3317
|
+
for (const parent of AppExecutor.#pageLibParents(akanConfig.basePaths)) {
|
|
2679
3318
|
const libsDir = `${pageDir}/${parent}${AppExecutor.#pageLibsDir}`;
|
|
2680
3319
|
const entries = await readDirEntries(libsDir).catch(() => []);
|
|
2681
3320
|
for (const entry of entries.sort()) {
|
|
@@ -2696,9 +3335,9 @@ class AppExecutor extends SysExecutor {
|
|
|
2696
3335
|
return parents.length ? parents : [""];
|
|
2697
3336
|
}
|
|
2698
3337
|
async syncPages(libDeps) {
|
|
2699
|
-
const
|
|
2700
|
-
const parents = AppExecutor.#pageLibParents(
|
|
2701
|
-
const libs = await this.#resolvePageLibs(
|
|
3338
|
+
const akanConfig = await this.getConfig();
|
|
3339
|
+
const parents = AppExecutor.#pageLibParents(akanConfig.basePaths);
|
|
3340
|
+
const libs = await this.#resolvePageLibs(akanConfig.syncPageLibs, libDeps);
|
|
2702
3341
|
const linked = (await Promise.all(parents.map(async (parent) => {
|
|
2703
3342
|
const libsDir = `${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`;
|
|
2704
3343
|
const entries = await readDirEntries(libsDir).catch(() => []);
|
|
@@ -2756,12 +3395,12 @@ class AppExecutor extends SysExecutor {
|
|
|
2756
3395
|
static async#linkLibAsset(targetPath, linkPath) {
|
|
2757
3396
|
const isWindows = process.platform === "win32";
|
|
2758
3397
|
try {
|
|
2759
|
-
const target = isWindows ? targetPath :
|
|
3398
|
+
const target = isWindows ? targetPath : path7.relative(path7.dirname(linkPath), targetPath);
|
|
2760
3399
|
await symlink(target, linkPath, isWindows ? "junction" : "dir");
|
|
2761
3400
|
} catch (error) {
|
|
2762
3401
|
if (!isWindows)
|
|
2763
3402
|
throw error;
|
|
2764
|
-
await
|
|
3403
|
+
await mkdir2(linkPath, { recursive: true });
|
|
2765
3404
|
await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
|
|
2766
3405
|
}
|
|
2767
3406
|
}
|
|
@@ -2798,7 +3437,7 @@ class AppExecutor extends SysExecutor {
|
|
|
2798
3437
|
await this.writeFile(rel, content, opts);
|
|
2799
3438
|
},
|
|
2800
3439
|
readEnvClient: async () => {
|
|
2801
|
-
const envClientPath =
|
|
3440
|
+
const envClientPath = path7.join(this.cwdPath, "env", "env.client.ts");
|
|
2802
3441
|
if (!await FileSys.fileExists(envClientPath))
|
|
2803
3442
|
return null;
|
|
2804
3443
|
try {
|
|
@@ -2857,9 +3496,9 @@ class LibExecutor extends SysExecutor {
|
|
|
2857
3496
|
}
|
|
2858
3497
|
static #execs = new Map;
|
|
2859
3498
|
static from(executor, name) {
|
|
2860
|
-
const
|
|
2861
|
-
if (
|
|
2862
|
-
return
|
|
3499
|
+
const exec = LibExecutor.#execs.get(name);
|
|
3500
|
+
if (exec)
|
|
3501
|
+
return exec;
|
|
2863
3502
|
else if (executor instanceof WorkspaceExecutor)
|
|
2864
3503
|
return new LibExecutor({ workspace: executor, name });
|
|
2865
3504
|
else
|
|
@@ -2869,7 +3508,7 @@ class LibExecutor extends SysExecutor {
|
|
|
2869
3508
|
async getConfig({ refresh } = {}) {
|
|
2870
3509
|
if (this.#akanConfig && !refresh)
|
|
2871
3510
|
return this.#akanConfig;
|
|
2872
|
-
this.#akanConfig = await AkanLibConfig.from(this);
|
|
3511
|
+
this.#akanConfig = await AkanLibConfig.from(this, { bustImportCache: refresh });
|
|
2873
3512
|
return this.#akanConfig;
|
|
2874
3513
|
}
|
|
2875
3514
|
}
|
|
@@ -2908,7 +3547,7 @@ class PkgExecutor extends Executor {
|
|
|
2908
3547
|
return rootVersion;
|
|
2909
3548
|
try {
|
|
2910
3549
|
const packageJsonPath = `pkgs/${dep}/package.json`;
|
|
2911
|
-
if (!await Bun.file(
|
|
3550
|
+
if (!await Bun.file(path7.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
|
|
2912
3551
|
return;
|
|
2913
3552
|
const packageJson = await this.workspace.readJson(packageJsonPath);
|
|
2914
3553
|
if (packageJson.name === dep)
|
|
@@ -2966,7 +3605,7 @@ class PkgExecutor extends Executor {
|
|
|
2966
3605
|
default: "./index.ts"
|
|
2967
3606
|
}
|
|
2968
3607
|
},
|
|
2969
|
-
engines: { bun: ">=1.
|
|
3608
|
+
engines: { bun: ">=1.4.0" },
|
|
2970
3609
|
...dependencyMaps
|
|
2971
3610
|
};
|
|
2972
3611
|
await Promise.all([this.dist.writeJson("package.json", distPkgJson), this.writeJson("package.json", distPkgJson)]);
|
|
@@ -3009,4 +3648,4 @@ class ModuleExecutor extends Executor {
|
|
|
3009
3648
|
}
|
|
3010
3649
|
}
|
|
3011
3650
|
|
|
3012
|
-
export { AGENT_BLOCK_START, AGENT_BLOCK_END, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock,
|
|
3651
|
+
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, isScannedRootEntry, AppInfo, Spinner, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
|