@akanjs/cli 3.0.0-beta.9 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.build-stamp +1 -1
- package/CodeAgent-zfab7pkn.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-0zhnr0pw.js +1457 -0
- package/README.ko.md +83 -28
- package/README.md +70 -27
- package/TunnelShare-cwh9ezsm.js +9 -0
- package/{agent.command-0q17pnrs.js → agent.command-aja6z9mn.js} +8 -9
- package/akanCode.js +102 -0
- package/application.command-049ehgx3.js +246 -0
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-75n5rpwp.js} +121 -77
- package/buildBatch.proc.js +120 -32
- package/cloud.command-gw4kqxt8.js +95 -0
- package/cloud.script-c723cpcb.js +20 -0
- package/code.command-x9t6z5nw.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-yfbe1rtd.js +48 -0
- package/context.runner-erfe68j5.js +21 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
- package/{devTui-m1atgkc9.js → devTui-q7k7bakd.js} +122 -40
- package/fileSys-wzzrntkw.js +9 -0
- package/guideline.command-kq9gp690.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +154 -26
- package/guidelines/conventions/conventions.instruction.md +138 -48
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/guidelines/queryRule/queryRule.instruction.md +21 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +291 -6
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +10 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
- package/incrementalBuilder.proc.js +564 -109
- package/{index-2tv3fdx4.js → index-150s07n8.js} +1230 -1459
- package/{index-vyvre9en.js → index-25fvrxmy.js} +6 -2
- package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
- package/index-2wc41k5w.js +49 -0
- package/index-3hx1z96c.js +87 -0
- package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
- package/{index-04bk599g.js → index-5g4rkrvt.js} +10 -17
- package/index-5pvxf7z0.js +53 -0
- package/index-5q14pwzd.js +69 -0
- package/{index-vxtad5bd.js → index-66yspb67.js} +5 -8
- package/index-7md2g9vw.js +6 -0
- package/index-7p4c0yer.js +1058 -0
- package/{index-d43r08tf.js → index-8b639zes.js} +77 -150
- package/{index-ktb5njgy.js → index-8kvy8qb4.js} +49 -99
- package/index-94s080qm.js +266 -0
- package/index-9dp031by.js +12 -0
- package/{index-0g7r8xz3.js → index-a37by9hn.js} +114 -232
- package/{index-k2j1hc4r.js → index-b95yhqjr.js} +12 -12
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/{index-2xp0kfkt.js → index-br8pkrk1.js} +37 -25
- package/index-c14nsvnv.js +700 -0
- package/index-c6eggbee.js +123 -0
- package/index-d2242ksy.js +99 -0
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-a64esmtb.js → index-fkvb79qe.js} +523 -1022
- package/index-h54yj9wr.js +1119 -0
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/{index-y6n8nmny.js → index-kn5d4jmg.js} +75 -203
- package/index-m3xsb1ms.js +4300 -0
- package/{index-gyd220p2.js → index-mb59b8x0.js} +554 -577
- package/{index-j9yhg404.js → index-mzjb6zp4.js} +4 -7
- package/index-p6pbz9n5.js +25 -0
- package/{index-sj5vxr8f.js → index-qe71g4ze.js} +983 -898
- package/index-rfcz04w9.js +187 -0
- package/{index-hwkygn9s.js → index-senscydg.js} +331 -36
- package/{index-ffvk10zz.js → index-vs8mefhy.js} +107 -241
- package/index-wa5jbxj1.js +760 -0
- package/index-y5ga5w9t.js +2729 -0
- package/{index-9077wzd2.js → index-y7hx1ygz.js} +43 -26
- package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
- package/index.js +23 -22
- package/{library.command-qzzbj6d8.js → library.command-keqjevpp.js} +4 -5
- package/{localRegistry.command-vyrgg4xw.js → localRegistry.command-pydeq5zf.js} +23 -43
- package/{module.command-rq5hq3ax.js → module.command-dkrnqx3v.js} +8 -12
- package/{package.command-d9hd6v9n.js → package.command-wmc0ys5y.js} +6 -7
- package/package.json +15 -7
- package/{page.command-126d56te.js → page.command-tf3erhrv.js} +4 -5
- package/{primitive.command-taxd1538.js → primitive.command-7hqpegj4.js} +9 -23
- package/prompter-76k56b37.js +8 -0
- package/{quality.command-6gsna0a3.js → quality.command-8122axq3.js} +205 -377
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-tq2dgy72.js → repair.command-mcy12h0n.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-3bcmrrkv.js → scalar.command-vvvbzttc.js} +7 -11
- package/skills/akan-code/SKILL.md +130 -0
- package/skills/akan-debug/SKILL.md +42 -0
- package/skills/akan-deps/SKILL.md +50 -0
- package/skills/akan-err/SKILL.md +39 -0
- package/skills/akan-module/SKILL.md +45 -0
- package/skills/akan-scaffold/SKILL.md +76 -0
- package/skills/akan-scalar/SKILL.md +52 -0
- package/skills/akan-service/SKILL.md +34 -0
- package/skills/akan-store/SKILL.md +67 -0
- package/skills/akan-ui/SKILL.md +63 -0
- package/skills/akan-validate/SKILL.md +44 -0
- package/{subspace.command-eddzqpx3.js → subspace.command-pzvqfy5h.js} +53 -62
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/app/page/_index.tsx +257 -258
- package/templates/app/page/_layout.tsx +5 -1
- package/templates/app/page/_overrides.tsx +12 -0
- package/templates/app/page/styles.css.template +316 -14
- package/templates/app/public/apple-touch-icon.png +0 -0
- package/templates/app/public/favicon.ico +0 -0
- package/templates/app/public/jelly/cloud.webp +0 -0
- package/templates/app/public/jelly/comet.webp +0 -0
- package/templates/app/public/jelly/moon.webp +0 -0
- package/templates/app/public/jelly/planet.webp +0 -0
- package/templates/app/public/jelly/rocket.webp +0 -0
- package/templates/app/public/jelly/star.webp +0 -0
- package/templates/app/public/jelly/sugar.webp +0 -0
- package/templates/app/public/logo.png +0 -0
- package/templates/app/ui/Recipe/index.ts +9 -0
- package/templates/app/ui/Recipe/jellyButton.ts +63 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/Task.Unit.tsx +1 -1
- package/templates/appSample/lib/task/Task.Util.tsx +10 -13
- package/templates/appSample/lib/task/Task.View.tsx +2 -2
- package/templates/appSample/lib/task/Task.Zone.tsx +5 -2
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +1 -15
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +10 -8
- package/templates/appSample/page/task/[taskId]/_index.tsx +6 -6
- package/templates/appSample/page/task/[taskId]/edit.tsx +5 -4
- package/templates/appSample/page/task/_index.tsx +4 -3
- package/templates/appSample/page/task/_layout.tsx +1 -1
- package/templates/appSample/page/task/new.tsx +5 -4
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
- package/templates/appSample/ui/GlobalLoading.tsx +2 -1
- package/templates/appSample/ui/QuantityControl.tsx +3 -3
- package/templates/client.ts +7 -30
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/server.ts +1 -3
- package/templates/service/__model__.abstract.md.template +8 -0
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/AGENTS.md.template +6 -0
- package/templates/workspaceRoot/package.json.template +1 -3
- package/tunnel.command-82zz5xnq.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-y8x3kgjv.js +79 -0
- package/{workspace.command-06wvstgr.js → workspace.command-jvsb7brc.js} +79 -109
- package/abstractCompactor-gmkc0z8j.js +0 -102
- package/application.command-18y0s485.js +0 -204
- package/applicationReleasePackager-4yasxwv9.js +0 -244
- package/capacitorApp-36rbr858.js +0 -12
- package/cloud.command-av97ndh6.js +0 -94
- package/cloud.script-cm11xjdz.js +0 -20
- package/context.command-5v37ef3n.js +0 -81
- package/devTuiView-0hc2emhe.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/guideline.command-4pz9g1rt.js +0 -360
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
- package/index-47pj3aee.js +0 -76
- package/index-5ystnsd5.js +0 -841
- package/index-6wwzs1bc.js +0 -7
- package/index-a17x49vg.js +0 -229
- package/index-m0xccmmm.js +0 -228
- package/index-meh558jy.js +0 -65
- package/index-rtxxan88.js +0 -471
- package/index-te54x9zm.js +0 -338
- package/index-w8kmx1b6.js +0 -2176
- package/index-wt7qrtd4.js +0 -1516
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
- package/workflow.command-gtdnzvbz.js +0 -108
|
@@ -1,161 +1,475 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
getDirname2
|
|
7
|
-
} from "./index-6wwzs1bc.js";
|
|
3
|
+
getDirname2,
|
|
4
|
+
FileSys2
|
|
5
|
+
} from "./index-52w0fvh8.js";
|
|
8
6
|
|
|
9
|
-
// pkgs/@akanjs/devkit/
|
|
10
|
-
import {
|
|
7
|
+
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
8
|
+
import { readdir, rm } from "fs/promises";
|
|
9
|
+
import path2 from "path";
|
|
10
|
+
|
|
11
|
+
// pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
|
|
12
|
+
import fs from "fs";
|
|
11
13
|
import path from "path";
|
|
12
|
-
import { Logger } from "akanjs/common";
|
|
13
14
|
|
|
14
|
-
class
|
|
15
|
-
static
|
|
16
|
-
static
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
static #logger = new Logger("CodegenLock");
|
|
20
|
-
static #queue = Promise.resolve();
|
|
21
|
-
static pathIn(workspaceRoot) {
|
|
22
|
-
return path.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
|
|
23
|
-
}
|
|
24
|
-
static async run(workspaceRoot, label, fn) {
|
|
25
|
-
const ahead = CodegenLock.#queue;
|
|
26
|
-
let done;
|
|
27
|
-
CodegenLock.#queue = new Promise((resolve) => {
|
|
28
|
-
done = resolve;
|
|
29
|
-
});
|
|
30
|
-
try {
|
|
31
|
-
await CodegenLock.#waitForQueue(ahead, label);
|
|
32
|
-
return await CodegenLock.#withFileLock(workspaceRoot, label, fn);
|
|
33
|
-
} finally {
|
|
34
|
-
done();
|
|
35
|
-
}
|
|
15
|
+
class AsyncDefaultExportDetector {
|
|
16
|
+
static #typescriptLoad;
|
|
17
|
+
static #loadTypescript() {
|
|
18
|
+
AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
|
|
19
|
+
return AsyncDefaultExportDetector.#typescriptLoad;
|
|
36
20
|
}
|
|
37
|
-
static async
|
|
38
|
-
let timer = null;
|
|
39
|
-
const expired = new Promise((resolve) => {
|
|
40
|
-
timer = setTimeout(() => resolve("expired"), CodegenLock.waitTimeoutMs);
|
|
41
|
-
});
|
|
21
|
+
static async detect(moduleAbsPath) {
|
|
42
22
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
23
|
+
const typescript = await AsyncDefaultExportDetector.#loadTypescript();
|
|
24
|
+
const source = fs.readFileSync(path.resolve(moduleAbsPath), "utf8");
|
|
25
|
+
const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, /\.[tj]sx$/.test(moduleAbsPath) ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS);
|
|
26
|
+
return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
48
29
|
}
|
|
49
30
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return;
|
|
54
|
-
});
|
|
55
|
-
const held = await CodegenLock.#acquire(lockPath, label);
|
|
56
|
-
try {
|
|
57
|
-
return await fn();
|
|
58
|
-
} finally {
|
|
59
|
-
if (held)
|
|
60
|
-
await rm(lockPath, { force: true }).catch(() => {
|
|
61
|
-
return;
|
|
62
|
-
});
|
|
63
|
-
}
|
|
31
|
+
#ts;
|
|
32
|
+
constructor(typescript) {
|
|
33
|
+
this.#ts = typescript;
|
|
64
34
|
}
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
35
|
+
detectInSourceFile(sourceFile) {
|
|
36
|
+
const ts = this.#ts;
|
|
37
|
+
const asyncBindings = new Map;
|
|
38
|
+
let defaultIdentifier = null;
|
|
39
|
+
for (const statement of sourceFile.statements) {
|
|
40
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
41
|
+
if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
|
|
42
|
+
return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
|
|
43
|
+
}
|
|
44
|
+
if (statement.name) {
|
|
45
|
+
asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
77
48
|
}
|
|
78
|
-
if (
|
|
49
|
+
if (ts.isVariableStatement(statement)) {
|
|
50
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
51
|
+
if (!ts.isIdentifier(declaration.name))
|
|
52
|
+
continue;
|
|
53
|
+
asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
|
|
54
|
+
}
|
|
79
55
|
continue;
|
|
80
|
-
if (Date.now() >= deadline) {
|
|
81
|
-
CodegenLock.#logger.warn(`codegen lock at ${lockPath} held past ${CodegenLock.waitTimeoutMs}ms; continuing without it (${label})`);
|
|
82
|
-
return false;
|
|
83
56
|
}
|
|
84
|
-
|
|
57
|
+
if (ts.isExportAssignment(statement)) {
|
|
58
|
+
if (this.#isAsyncFunctionExpression(statement.expression))
|
|
59
|
+
return true;
|
|
60
|
+
if (ts.isCallExpression(statement.expression))
|
|
61
|
+
return true;
|
|
62
|
+
if (ts.isIdentifier(statement.expression))
|
|
63
|
+
defaultIdentifier = statement.expression.text;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
67
|
+
for (const specifier of statement.exportClause.elements) {
|
|
68
|
+
if (specifier.name.text !== "default")
|
|
69
|
+
continue;
|
|
70
|
+
defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
85
73
|
}
|
|
74
|
+
return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
|
|
86
75
|
}
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
return true;
|
|
91
|
-
const holder = CodegenLock.#parseHolder(await readFile(lockPath, "utf8").catch(() => ""));
|
|
92
|
-
if (holder) {
|
|
93
|
-
if (CodegenLock.#isAlive(holder.pid))
|
|
94
|
-
return false;
|
|
95
|
-
} else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
|
|
96
|
-
return false;
|
|
97
|
-
await rm(lockPath, { force: true }).catch(() => {
|
|
98
|
-
return;
|
|
99
|
-
});
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
static #parseHolder(raw) {
|
|
103
|
-
try {
|
|
104
|
-
const parsed = JSON.parse(raw);
|
|
105
|
-
if (typeof parsed.pid !== "number" || typeof parsed.at !== "number")
|
|
106
|
-
return null;
|
|
107
|
-
return { pid: parsed.pid, at: parsed.at, label: typeof parsed.label === "string" ? parsed.label : "" };
|
|
108
|
-
} catch {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
76
|
+
#hasModifier(node, kind) {
|
|
77
|
+
const ts = this.#ts;
|
|
78
|
+
return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
|
|
111
79
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
process.kill(pid, 0);
|
|
117
|
-
return true;
|
|
118
|
-
} catch (error) {
|
|
119
|
-
return error.code === "EPERM";
|
|
120
|
-
}
|
|
80
|
+
#isAsyncFunctionExpression(node) {
|
|
81
|
+
const ts = this.#ts;
|
|
82
|
+
return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
|
|
121
83
|
}
|
|
122
84
|
}
|
|
123
85
|
|
|
124
|
-
// pkgs/@akanjs/devkit/
|
|
125
|
-
|
|
86
|
+
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
87
|
+
var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
88
|
+
var IMPLICIT_LAYOUT_DIR = path2.join(".akan", "generated", "root-layouts");
|
|
89
|
+
var IMPLICIT_DICT_DIR = path2.join(".akan", "generated", "dict");
|
|
90
|
+
var IMPLICIT_OVERRIDES_DIR = path2.join(".akan", "generated", "overrides");
|
|
91
|
+
var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
92
|
+
var importSpecifier = (fromAbsPath, toAbsPath) => {
|
|
93
|
+
const rel = path2.relative(path2.dirname(fromAbsPath), toAbsPath).split(path2.sep).join("/");
|
|
94
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
95
|
+
};
|
|
96
|
+
async function writeGeneratedOverridesLayoutFile(opts) {
|
|
97
|
+
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
98
|
+
const absPath = path2.join(path2.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
99
|
+
const source = `"use client";
|
|
100
|
+
import { UiOverrideProvider } from "akanjs/ui";
|
|
101
|
+
import { createElement, type ReactNode } from "react";
|
|
102
|
+
import value from ${JSON.stringify(importSpecifier(absPath, opts.userAbsPath))};
|
|
126
103
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
104
|
+
export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
|
|
105
|
+
return createElement(UiOverrideProvider, { value }, children);
|
|
106
|
+
}
|
|
107
|
+
`;
|
|
108
|
+
await Bun.write(absPath, source);
|
|
109
|
+
return absPath;
|
|
110
|
+
}
|
|
111
|
+
async function pruneGeneratedFiles(dirAbsPath, keepAbsPaths) {
|
|
112
|
+
const keep = new Set(keepAbsPaths);
|
|
113
|
+
const names = await readdir(dirAbsPath).catch(() => []);
|
|
114
|
+
await Promise.all(names.map((name) => path2.join(dirAbsPath, name)).filter((absPath) => !keep.has(absPath)).map((absPath) => rm(absPath, { force: true })));
|
|
115
|
+
}
|
|
116
|
+
function getRootBoundarySegments(key) {
|
|
117
|
+
const match = LAYOUT_KEY_RE.exec(key);
|
|
118
|
+
if (!match)
|
|
119
|
+
return null;
|
|
120
|
+
const prefix = match[1]?.replace(/\/$/, "");
|
|
121
|
+
if (!prefix)
|
|
122
|
+
return [];
|
|
123
|
+
return prefix.split("/").filter(Boolean);
|
|
124
|
+
}
|
|
125
|
+
function implicitRootLayoutKey(segments) {
|
|
126
|
+
return `./${[...segments, "__root_layout"].join("/")}.tsx`;
|
|
127
|
+
}
|
|
128
|
+
function implicitRootLayoutAbsPath(appCwdPath, segments) {
|
|
129
|
+
const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
|
|
130
|
+
return path2.join(path2.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
131
|
+
}
|
|
132
|
+
function isRootBoundarySegments(segments, basePaths) {
|
|
133
|
+
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
134
|
+
if (firstVisibleIndex === -1)
|
|
135
|
+
return segments.length <= 1;
|
|
136
|
+
if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
|
|
137
|
+
return false;
|
|
138
|
+
const visible = segments.slice(firstVisibleIndex);
|
|
139
|
+
const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
|
|
140
|
+
return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
|
|
141
|
+
}
|
|
142
|
+
function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
|
|
143
|
+
const boundaries = new Map;
|
|
144
|
+
for (const key of pageKeys) {
|
|
145
|
+
const segments = getRootBoundarySegments(key);
|
|
146
|
+
if (!segments)
|
|
147
|
+
continue;
|
|
148
|
+
if (!isRootBoundarySegments(segments, basePaths))
|
|
149
|
+
continue;
|
|
150
|
+
const id = segments.join("/");
|
|
151
|
+
boundaries.set(id, {
|
|
152
|
+
sourceKey: key,
|
|
153
|
+
sourceAbsPath: path2.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
|
|
154
|
+
segments
|
|
155
|
+
});
|
|
132
156
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
if (boundaries.size === 0)
|
|
158
|
+
boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
159
|
+
return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
|
|
160
|
+
}
|
|
161
|
+
function hasAncestorRootBoundary(boundary, boundaries) {
|
|
162
|
+
return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
|
|
163
|
+
}
|
|
164
|
+
function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
|
|
165
|
+
const rootLayoutKey = pageKeys.find((key) => {
|
|
166
|
+
const segments = getRootBoundarySegments(key);
|
|
167
|
+
return segments !== null && segments.length === 0;
|
|
168
|
+
});
|
|
169
|
+
return rootLayoutKey ? path2.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
170
|
+
}
|
|
171
|
+
async function assertEnvClientConvention(appCwdPath, appName) {
|
|
172
|
+
const envPath = path2.join(appCwdPath, "env", "env.client.ts");
|
|
173
|
+
if (!await Bun.file(envPath).exists()) {
|
|
174
|
+
throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
|
|
147
175
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
176
|
+
}
|
|
177
|
+
async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
|
|
178
|
+
const absPath = path2.join(path2.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
179
|
+
await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
180
|
+
|
|
181
|
+
export const allDictionary = getAllDictionary();
|
|
182
|
+
`);
|
|
183
|
+
return absPath;
|
|
184
|
+
}
|
|
185
|
+
async function writeGeneratedRootLayoutFile(opts) {
|
|
186
|
+
await assertEnvClientConvention(opts.appCwdPath, opts.appName);
|
|
187
|
+
const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
|
|
188
|
+
const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
|
|
189
|
+
const dictMacroSpecifier = dictMacroAbsPath ? importSpecifier(absPath, dictMacroAbsPath) : null;
|
|
190
|
+
const sourceSpecifier = opts.boundary.sourceAbsPath ? importSpecifier(absPath, opts.boundary.sourceAbsPath) : null;
|
|
191
|
+
const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
|
|
192
|
+
const inheritedSourceSpecifier = inheritedSourceAbsPath ? importSpecifier(absPath, inheritedSourceAbsPath) : null;
|
|
193
|
+
const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
|
|
194
|
+
void st;
|
|
195
|
+
` : `import "@apps/${opts.appName}/client";
|
|
196
|
+
`;
|
|
197
|
+
const inheritedLabel = inheritedSourceAbsPath ? path2.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
|
|
198
|
+
const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
|
|
199
|
+
const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
|
|
200
|
+
` : `const inheritedLayout: LayoutModule = {};
|
|
201
|
+
`;
|
|
202
|
+
const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
|
|
203
|
+
const userLabel = opts.boundary.sourceAbsPath ? path2.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
|
|
204
|
+
const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
|
|
205
|
+
const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
|
|
206
|
+
const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
|
|
207
|
+
` : `const UserLayout = ({ children }: LayoutProps) => children;
|
|
208
|
+
const userLayout: LayoutModule = {};
|
|
209
|
+
`;
|
|
210
|
+
const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
|
|
211
|
+
const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
|
|
212
|
+
const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
|
|
213
|
+
const layoutBinding = isAsyncUserLayout ? ` const layout =
|
|
214
|
+
process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
|
|
215
|
+
? await UserLayout({ params, searchParams, children })
|
|
216
|
+
: ${userLayoutElement};
|
|
217
|
+
` : "";
|
|
218
|
+
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
219
|
+
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
220
|
+
const layoutExports = `export async function generateHead(props: PageProps) {
|
|
221
|
+
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
222
|
+
if (userLayout.head !== undefined) return userLayout.head;
|
|
223
|
+
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
224
|
+
return inheritedLayout.head;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
228
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
229
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
230
|
+
|
|
231
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
232
|
+
${layoutBinding} return `;
|
|
233
|
+
const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
234
|
+
import { loadFonts, resolveRouteModule } from "akanjs/client";
|
|
235
|
+
import type { ReactNode } from "react";
|
|
236
|
+
import { System } from "akanjs/ui";
|
|
237
|
+
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
238
|
+
import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
|
|
239
|
+
${clientImport}${inheritedImport}${userImport}
|
|
240
|
+
const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
|
|
241
|
+
const defaultFonts = userFonts.filter((font) => font.default);
|
|
242
|
+
if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
|
|
243
|
+
const defaultFont = defaultFonts[0];
|
|
244
|
+
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
245
|
+
export const theme = userLayout.theme ?? inheritedLayout.theme;
|
|
246
|
+
|
|
247
|
+
${layoutExports}(
|
|
248
|
+
<System.Provider
|
|
249
|
+
of={GeneratedLayout as never}
|
|
250
|
+
appName=${JSON.stringify(opts.appName)}
|
|
251
|
+
${prefix ? `prefix=${JSON.stringify(prefix)}
|
|
252
|
+
` : ""}params={params}
|
|
253
|
+
manifest={userLayout.manifest ?? inheritedLayout.manifest}
|
|
254
|
+
env={env}
|
|
255
|
+
theme={theme}
|
|
256
|
+
fonts={loadFonts(userFonts)}
|
|
257
|
+
className={defaultFontClassName}
|
|
258
|
+
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
259
|
+
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
260
|
+
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
|
|
261
|
+
allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
|
|
262
|
+
>
|
|
263
|
+
${layoutChild}
|
|
264
|
+
</System.Provider>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
268
|
+
import { resolveRouteModule } from "akanjs/client";
|
|
269
|
+
import type { ReactNode } from "react";
|
|
270
|
+
${inheritedImport}${userImport}
|
|
271
|
+
${layoutExports}${layoutReturn};
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
await Bun.write(absPath, source);
|
|
275
|
+
return absPath;
|
|
276
|
+
}
|
|
277
|
+
async function resolveSsrPageEntries(opts) {
|
|
278
|
+
const absPageDir = path2.resolve(opts.appCwdPath, "page");
|
|
279
|
+
const hasSt = await Bun.file(path2.join(opts.appCwdPath, "lib", "st.ts")).exists();
|
|
280
|
+
const basePaths = opts.basePaths ?? [];
|
|
281
|
+
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
282
|
+
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
283
|
+
const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
|
|
284
|
+
const segments = getRootBoundarySegments(key);
|
|
285
|
+
return segments !== null && isRootBoundarySegments(segments, basePaths);
|
|
286
|
+
}));
|
|
287
|
+
const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
|
|
288
|
+
const userAbsPath = path2.resolve(absPageDir, key);
|
|
289
|
+
if (OVERRIDES_KEY_RE.test(key)) {
|
|
290
|
+
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
291
|
+
appCwdPath: opts.appCwdPath,
|
|
292
|
+
key,
|
|
293
|
+
userAbsPath
|
|
294
|
+
});
|
|
295
|
+
return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
|
|
296
|
+
}
|
|
297
|
+
return { key, moduleAbsPath: userAbsPath };
|
|
298
|
+
}));
|
|
299
|
+
const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
|
|
300
|
+
key: implicitRootLayoutKey(boundary.segments),
|
|
301
|
+
moduleAbsPath: await writeGeneratedRootLayoutFile({
|
|
302
|
+
appCwdPath: opts.appCwdPath,
|
|
303
|
+
appName: opts.appName,
|
|
304
|
+
boundary,
|
|
305
|
+
rootSourceAbsPath,
|
|
306
|
+
includeStInit: hasSt && boundary.segments.length === 0,
|
|
307
|
+
includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
|
|
308
|
+
}),
|
|
309
|
+
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
|
|
310
|
+
})));
|
|
311
|
+
const appCwdAbsPath = path2.resolve(opts.appCwdPath);
|
|
312
|
+
await Promise.all([
|
|
313
|
+
pruneGeneratedFiles(path2.join(appCwdAbsPath, IMPLICIT_OVERRIDES_DIR), base.filter(({ key }) => OVERRIDES_KEY_RE.test(key)).map(({ moduleAbsPath }) => moduleAbsPath)),
|
|
314
|
+
pruneGeneratedFiles(path2.join(appCwdAbsPath, IMPLICIT_LAYOUT_DIR), generated.map(({ moduleAbsPath }) => moduleAbsPath))
|
|
315
|
+
]);
|
|
316
|
+
return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
|
|
317
|
+
}
|
|
318
|
+
async function resolveSsrPageEntriesForApp(app, pageKeys) {
|
|
319
|
+
const config = await app.getConfig();
|
|
320
|
+
return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// pkgs/@akanjs/devkit/codegenLock.ts
|
|
324
|
+
import { mkdir, open, readFile, rm as rm2, stat } from "fs/promises";
|
|
325
|
+
import path3 from "path";
|
|
326
|
+
import { Logger } from "akanjs/common";
|
|
327
|
+
|
|
328
|
+
class CodegenLock {
|
|
329
|
+
static fileName = "codegen.lock";
|
|
330
|
+
static waitTimeoutMs = 1e4;
|
|
331
|
+
static unknownHolderStaleMs = 60000;
|
|
332
|
+
static #pollMs = 25;
|
|
333
|
+
static #logger = new Logger("CodegenLock");
|
|
334
|
+
static #queue = Promise.resolve();
|
|
335
|
+
static pathIn(workspaceRoot) {
|
|
336
|
+
return path3.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
|
|
337
|
+
}
|
|
338
|
+
static async run(workspaceRoot, label, fn) {
|
|
339
|
+
const ahead = CodegenLock.#queue;
|
|
340
|
+
let done;
|
|
341
|
+
CodegenLock.#queue = new Promise((resolve) => {
|
|
342
|
+
done = resolve;
|
|
343
|
+
});
|
|
344
|
+
try {
|
|
345
|
+
await CodegenLock.#waitForQueue(ahead, label);
|
|
346
|
+
return await CodegenLock.#withFileLock(workspaceRoot, label, fn);
|
|
347
|
+
} finally {
|
|
348
|
+
done();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
static async#waitForQueue(ahead, label) {
|
|
352
|
+
let timer = null;
|
|
353
|
+
const expired = new Promise((resolve) => {
|
|
354
|
+
timer = setTimeout(() => resolve("expired"), CodegenLock.waitTimeoutMs);
|
|
355
|
+
});
|
|
356
|
+
try {
|
|
357
|
+
if (await Promise.race([ahead.then(() => "done"), expired]) === "expired")
|
|
358
|
+
CodegenLock.#logger.warn(`codegen lock queued past ${CodegenLock.waitTimeoutMs}ms in this process; continuing without waiting (${label})`);
|
|
359
|
+
} finally {
|
|
360
|
+
if (timer)
|
|
361
|
+
clearTimeout(timer);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
static async#withFileLock(workspaceRoot, label, fn) {
|
|
365
|
+
const lockPath = CodegenLock.pathIn(workspaceRoot);
|
|
366
|
+
await mkdir(path3.dirname(lockPath), { recursive: true }).catch(() => {
|
|
367
|
+
return;
|
|
368
|
+
});
|
|
369
|
+
const held = await CodegenLock.#acquire(lockPath, label);
|
|
370
|
+
try {
|
|
371
|
+
return await fn();
|
|
372
|
+
} finally {
|
|
373
|
+
if (held)
|
|
374
|
+
await rm2(lockPath, { force: true }).catch(() => {
|
|
375
|
+
return;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
static async#acquire(lockPath, label) {
|
|
380
|
+
const deadline = Date.now() + CodegenLock.waitTimeoutMs;
|
|
381
|
+
for (;; ) {
|
|
382
|
+
const handle = await open(lockPath, "wx").catch(() => null);
|
|
383
|
+
if (handle) {
|
|
384
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, at: Date.now(), label })).catch(() => {
|
|
385
|
+
return;
|
|
386
|
+
});
|
|
387
|
+
await handle.close().catch(() => {
|
|
388
|
+
return;
|
|
389
|
+
});
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
if (await CodegenLock.#reclaimIfAbandoned(lockPath))
|
|
393
|
+
continue;
|
|
394
|
+
if (Date.now() >= deadline) {
|
|
395
|
+
CodegenLock.#logger.warn(`codegen lock at ${lockPath} held past ${CodegenLock.waitTimeoutMs}ms; continuing without it (${label})`);
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
await Bun.sleep(CodegenLock.#pollMs);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
static async#reclaimIfAbandoned(lockPath) {
|
|
402
|
+
const info = await stat(lockPath).catch(() => null);
|
|
403
|
+
if (!info)
|
|
404
|
+
return true;
|
|
405
|
+
const holder = CodegenLock.#parseHolder(await readFile(lockPath, "utf8").catch(() => ""));
|
|
406
|
+
if (holder) {
|
|
407
|
+
if (CodegenLock.#isAlive(holder.pid))
|
|
408
|
+
return false;
|
|
409
|
+
} else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
|
|
410
|
+
return false;
|
|
411
|
+
await rm2(lockPath, { force: true }).catch(() => {
|
|
412
|
+
return;
|
|
413
|
+
});
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
static #parseHolder(raw) {
|
|
417
|
+
try {
|
|
418
|
+
const parsed = JSON.parse(raw);
|
|
419
|
+
if (typeof parsed.pid !== "number" || typeof parsed.at !== "number")
|
|
420
|
+
return null;
|
|
421
|
+
return { pid: parsed.pid, at: parsed.at, label: typeof parsed.label === "string" ? parsed.label : "" };
|
|
422
|
+
} catch {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
static #isAlive(pid) {
|
|
427
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
428
|
+
return false;
|
|
429
|
+
try {
|
|
430
|
+
process.kill(pid, 0);
|
|
431
|
+
return true;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
return error.code === "EPERM";
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// pkgs/@akanjs/devkit/spinner.ts
|
|
439
|
+
import ora from "ora";
|
|
440
|
+
|
|
441
|
+
class Spinner {
|
|
442
|
+
static padding = 12;
|
|
443
|
+
static oraOptions = { discardStdin: false };
|
|
444
|
+
static canAnimate(stream = process.stderr) {
|
|
445
|
+
return !stream.isTTY || stream.columns > 0;
|
|
446
|
+
}
|
|
447
|
+
spinner;
|
|
448
|
+
stopWatch = null;
|
|
449
|
+
startAt = new Date;
|
|
450
|
+
prefix;
|
|
451
|
+
message;
|
|
452
|
+
enableSpin;
|
|
453
|
+
constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
|
|
454
|
+
Spinner.padding = Math.max(Spinner.padding, prefix.length);
|
|
455
|
+
this.prefix = prefix;
|
|
456
|
+
this.message = message;
|
|
457
|
+
this.spinner = ora({ ...Spinner.oraOptions, text: message });
|
|
458
|
+
this.spinner.prefixText = prefix.padStart(Spinner.padding, " ");
|
|
459
|
+
this.spinner.indent = indent;
|
|
460
|
+
this.enableSpin = enableSpin && Spinner.canAnimate();
|
|
461
|
+
}
|
|
462
|
+
start() {
|
|
463
|
+
this.startAt = new Date;
|
|
464
|
+
if (this.enableSpin) {
|
|
465
|
+
this.spinner.start();
|
|
466
|
+
this.stopWatch = setInterval(() => {
|
|
467
|
+
this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
|
|
468
|
+
this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
|
|
469
|
+
}, 1000);
|
|
470
|
+
} else
|
|
471
|
+
this.spinner.info();
|
|
472
|
+
return this;
|
|
159
473
|
}
|
|
160
474
|
succeed(message) {
|
|
161
475
|
this.spinner.succeed(`${message} (${this.#getElapsedTimeStr()})`);
|
|
@@ -191,18 +505,19 @@ import {
|
|
|
191
505
|
fork,
|
|
192
506
|
spawn as spawn2
|
|
193
507
|
} from "child_process";
|
|
194
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
508
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
195
509
|
import {
|
|
196
510
|
copyFile,
|
|
197
511
|
cp as cpEntry,
|
|
198
|
-
|
|
512
|
+
lstat,
|
|
513
|
+
mkdir as mkdir2,
|
|
199
514
|
readdir as readDirEntries,
|
|
200
515
|
realpath,
|
|
201
516
|
rm as rm3,
|
|
202
517
|
stat as stat2,
|
|
203
518
|
symlink
|
|
204
519
|
} from "fs/promises";
|
|
205
|
-
import
|
|
520
|
+
import path9 from "path";
|
|
206
521
|
import { pathToFileURL } from "url";
|
|
207
522
|
import {
|
|
208
523
|
capitalize,
|
|
@@ -218,7 +533,7 @@ import chalk2 from "chalk";
|
|
|
218
533
|
|
|
219
534
|
// pkgs/@akanjs/devkit/agentsIndex.ts
|
|
220
535
|
var recipeScannerLoad = null;
|
|
221
|
-
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-
|
|
536
|
+
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-4b69ybe0.js");
|
|
222
537
|
var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
|
|
223
538
|
var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
|
|
224
539
|
var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
|
|
@@ -228,7 +543,7 @@ ${block}`;
|
|
|
228
543
|
var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
|
|
229
544
|
var readDevkitVersion = async () => {
|
|
230
545
|
const { readFile } = await import("fs/promises");
|
|
231
|
-
const { getDirname } = await import("./
|
|
546
|
+
const { getDirname } = await import("./fileSys-wzzrntkw.js");
|
|
232
547
|
try {
|
|
233
548
|
const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
|
|
234
549
|
return JSON.parse(raw).version ?? null;
|
|
@@ -354,79 +669,93 @@ var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
|
|
|
354
669
|
${CLAUDE_COMMENT_RULE}
|
|
355
670
|
`;
|
|
356
671
|
|
|
357
|
-
// pkgs/@akanjs/devkit/akanConfig/
|
|
358
|
-
import
|
|
359
|
-
import path2 from "path";
|
|
360
|
-
import { fileURLToPath } from "url";
|
|
361
|
-
import { normalizeRoutePrefix } from "akanjs/base";
|
|
362
|
-
import { resolveAkanI18nConfig } from "akanjs/common";
|
|
672
|
+
// pkgs/@akanjs/devkit/akanConfig/akanBin.ts
|
|
673
|
+
import path4 from "path";
|
|
363
674
|
|
|
364
675
|
// pkgs/@akanjs/devkit/akanConfig/types.ts
|
|
365
|
-
import { archs } from "akanjs";
|
|
676
|
+
import { archs, binPlatforms } from "akanjs";
|
|
366
677
|
|
|
678
|
+
// pkgs/@akanjs/devkit/akanConfig/akanBin.ts
|
|
679
|
+
class AkanBin {
|
|
680
|
+
static archive = /\.(zip|tar\.gz|tgz|tar\.xz|txz|tar\.bz2|tbz2|tar)$/i;
|
|
681
|
+
static fileName = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
682
|
+
static parse(declared, owner, baseDir) {
|
|
683
|
+
if (declared === undefined)
|
|
684
|
+
return {};
|
|
685
|
+
if (!declared || typeof declared !== "object" || Array.isArray(declared))
|
|
686
|
+
throw new Error(`${owner}: bin maps each executable's name to its platforms`);
|
|
687
|
+
return Object.fromEntries(Object.entries(declared).map(([name, platforms]) => [name, AkanBin.#platforms(name, platforms, owner, baseDir)]));
|
|
688
|
+
}
|
|
689
|
+
static isArchive(source) {
|
|
690
|
+
return AkanBin.archive.test("url" in source ? new URL(source.url).pathname : source.path);
|
|
691
|
+
}
|
|
692
|
+
static #platforms(name, platforms, owner, baseDir) {
|
|
693
|
+
if (!AkanBin.fileName.test(name))
|
|
694
|
+
throw new Error(`${owner}: bin.${name} is not a file name`);
|
|
695
|
+
if (!platforms || typeof platforms !== "object" || Array.isArray(platforms))
|
|
696
|
+
throw new Error(`${owner}: bin.${name} maps platforms (${binPlatforms.join(", ")}) to where the file comes from`);
|
|
697
|
+
const resolved = {};
|
|
698
|
+
for (const [platform, source] of Object.entries(platforms)) {
|
|
699
|
+
if (!binPlatforms.includes(platform))
|
|
700
|
+
throw new Error(`${owner}: bin.${name}.${platform} is not one of ${binPlatforms.join(", ")}`);
|
|
701
|
+
resolved[platform] = AkanBin.#source(`${owner}: bin.${name}.${platform}`, source, baseDir);
|
|
702
|
+
}
|
|
703
|
+
return resolved;
|
|
704
|
+
}
|
|
705
|
+
static #source(at, source, baseDir) {
|
|
706
|
+
if (!source || typeof source !== "object" || Array.isArray(source))
|
|
707
|
+
throw new Error(`${at} must be { url, sha256 } or { path }`);
|
|
708
|
+
const { url, sha256, path: localPath, file, ...rest } = source;
|
|
709
|
+
const extra = Object.keys(rest);
|
|
710
|
+
if (extra.length)
|
|
711
|
+
throw new Error(`${at}: unknown key ${extra.join(", ")}`);
|
|
712
|
+
if (url === undefined === (localPath === undefined))
|
|
713
|
+
throw new Error(`${at} takes either url and sha256, or path`);
|
|
714
|
+
const resolved = url !== undefined ? AkanBin.#urlSource(at, url, sha256) : AkanBin.#pathSource(at, localPath, sha256, baseDir);
|
|
715
|
+
const archive = AkanBin.isArchive(resolved);
|
|
716
|
+
if (file === undefined) {
|
|
717
|
+
if (archive)
|
|
718
|
+
throw new Error(`${at} names an archive: file picks the executable inside it`);
|
|
719
|
+
return resolved;
|
|
720
|
+
}
|
|
721
|
+
if (!archive)
|
|
722
|
+
throw new Error(`${at}: file picks the executable out of an archive, and this source is none`);
|
|
723
|
+
const parts = typeof file === "string" ? file.split(/[\\/]/).filter((part) => part && part !== ".") : [];
|
|
724
|
+
if (!parts.length || path4.isAbsolute(file) || parts.includes(".."))
|
|
725
|
+
throw new Error(`${at}: file must be a path inside the archive (got ${JSON.stringify(file)})`);
|
|
726
|
+
return { ...resolved, file: parts.join("/") };
|
|
727
|
+
}
|
|
728
|
+
static #urlSource(at, url, sha256) {
|
|
729
|
+
if (typeof url !== "string" || !URL.canParse(url) || !/^https?:$/.test(new URL(url).protocol))
|
|
730
|
+
throw new Error(`${at}: url must be an http(s) address`);
|
|
731
|
+
if (typeof sha256 !== "string" || !/^[0-9a-f]{64}$/i.test(sha256))
|
|
732
|
+
throw new Error(`${at}: sha256 must be the downloaded file's 64-digit hex digest`);
|
|
733
|
+
return { url, sha256: sha256.toLowerCase() };
|
|
734
|
+
}
|
|
735
|
+
static #pathSource(at, localPath, sha256, baseDir) {
|
|
736
|
+
if (typeof localPath !== "string" || !localPath)
|
|
737
|
+
throw new Error(`${at}: path must name a file`);
|
|
738
|
+
if (sha256 !== undefined)
|
|
739
|
+
throw new Error(`${at}: sha256 checks a download, and a path source downloads nothing`);
|
|
740
|
+
return { path: path4.resolve(baseDir, localPath) };
|
|
741
|
+
}
|
|
742
|
+
}
|
|
367
743
|
// pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
|
|
744
|
+
import fs2 from "fs";
|
|
745
|
+
import path5 from "path";
|
|
746
|
+
import { fileURLToPath } from "url";
|
|
747
|
+
import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
|
|
748
|
+
import { resolveAkanI18nConfig } from "akanjs/common";
|
|
368
749
|
var DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui", "akanjs/server"];
|
|
369
750
|
var DEFAULT_OPTIMIZE_IMPORTS = [
|
|
370
|
-
"lucide-react",
|
|
371
|
-
"
|
|
372
|
-
"
|
|
373
|
-
"
|
|
374
|
-
|
|
375
|
-
"react-bootstrap",
|
|
376
|
-
"ahooks",
|
|
377
|
-
"@ant-design/icons",
|
|
378
|
-
"@headlessui/react",
|
|
379
|
-
"@headlessui-float/react",
|
|
380
|
-
"@heroicons/react/20/solid",
|
|
381
|
-
"@heroicons/react/24/solid",
|
|
382
|
-
"@heroicons/react/24/outline",
|
|
383
|
-
"@visx/visx",
|
|
384
|
-
"@tremor/react",
|
|
385
|
-
"rxjs",
|
|
386
|
-
"@mui/material",
|
|
387
|
-
"@mui/icons-material",
|
|
388
|
-
"recharts",
|
|
389
|
-
"react-use",
|
|
390
|
-
"@material-ui/core",
|
|
391
|
-
"@material-ui/icons",
|
|
392
|
-
"@tabler/icons-react",
|
|
393
|
-
"mui-core",
|
|
394
|
-
"react-icons/*"
|
|
395
|
-
];
|
|
751
|
+
"lucide-react date-fns lodash-es ramda antd react-bootstrap ahooks @ant-design/icons @headlessui/react",
|
|
752
|
+
"@headlessui-float/react @heroicons/react/20/solid @heroicons/react/24/solid @heroicons/react/24/outline",
|
|
753
|
+
"@visx/visx @tremor/react rxjs @mui/material @mui/icons-material recharts react-use @material-ui/core",
|
|
754
|
+
"@material-ui/icons @tabler/icons-react mui-core react-icons/*"
|
|
755
|
+
].flatMap((line) => line.split(" "));
|
|
396
756
|
var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
|
|
397
757
|
var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
|
|
398
758
|
var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
|
|
399
|
-
var MOBILE_RUNTIME_PACKAGES = [
|
|
400
|
-
"@capacitor/cli",
|
|
401
|
-
"@capacitor/core",
|
|
402
|
-
"@capacitor/ios",
|
|
403
|
-
"@capacitor/android",
|
|
404
|
-
"@capacitor/assets"
|
|
405
|
-
];
|
|
406
|
-
var MOBILE_APP_CAPACITOR_PLUGINS = [
|
|
407
|
-
"@capacitor/app",
|
|
408
|
-
"@capacitor/browser",
|
|
409
|
-
"@capacitor/camera",
|
|
410
|
-
"@capacitor/core",
|
|
411
|
-
"@capacitor/device",
|
|
412
|
-
"@capacitor/geolocation",
|
|
413
|
-
"@capacitor/haptics",
|
|
414
|
-
"@capacitor/inappbrowser",
|
|
415
|
-
"@capacitor/keyboard",
|
|
416
|
-
"@capacitor/preferences",
|
|
417
|
-
"@capacitor/push-notifications",
|
|
418
|
-
"capacitor-plugin-safe-area"
|
|
419
|
-
];
|
|
420
|
-
var DATABASE_MODE_RUNTIME_PACKAGES = {
|
|
421
|
-
single: [],
|
|
422
|
-
multiple: ["@libsql/client", "bullmq", "ioredis", "protobufjs"],
|
|
423
|
-
cluster: ["bullmq", "ioredis", "postgres", "protobufjs"]
|
|
424
|
-
};
|
|
425
|
-
var AKAN_RUNTIME_PACKAGES = new Set([
|
|
426
|
-
...SSR_RUNTIME_PACKAGES,
|
|
427
|
-
...MOBILE_RUNTIME_PACKAGES,
|
|
428
|
-
...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat()
|
|
429
|
-
]);
|
|
430
759
|
var DEFAULT_AKAN_IMAGE_CONFIG = {
|
|
431
760
|
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
|
432
761
|
imageSizes: [32, 48, 64, 96, 128, 256, 384],
|
|
@@ -471,26 +800,26 @@ var normalizeDeepLinkDomain = (domain) => {
|
|
|
471
800
|
}
|
|
472
801
|
};
|
|
473
802
|
var normalizeDeepLinks = (deepLinks) => {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const domains = normalizeStringList(deepLinks.domains?.map(normalizeDeepLinkDomain));
|
|
478
|
-
const teamId = deepLinks.ios?.teamId?.trim();
|
|
479
|
-
const sha256CertFingerprints = normalizeStringList(deepLinks.android?.sha256CertFingerprints);
|
|
480
|
-
if (!schemes && !domains && !teamId && !sha256CertFingerprints)
|
|
803
|
+
const schemes = normalizeStringList(deepLinks?.schemes);
|
|
804
|
+
const domains = normalizeStringList(deepLinks?.domains?.map(normalizeDeepLinkDomain));
|
|
805
|
+
if (!schemes && !domains)
|
|
481
806
|
return;
|
|
482
|
-
return {
|
|
483
|
-
...schemes ? { schemes } : {},
|
|
484
|
-
...domains ? { domains } : {},
|
|
485
|
-
...teamId ? { ios: { teamId } } : {},
|
|
486
|
-
...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
|
|
487
|
-
};
|
|
807
|
+
return { ...schemes ? { schemes } : {}, ...domains ? { domains } : {} };
|
|
488
808
|
};
|
|
489
809
|
var emptyLibContributions = () => ({
|
|
490
810
|
externalLibs: [],
|
|
811
|
+
trustedDependencies: [],
|
|
812
|
+
bin: [],
|
|
491
813
|
docker: { preRuns: [], postRuns: [] },
|
|
492
814
|
keepFonts: []
|
|
493
815
|
});
|
|
816
|
+
var normalizePackageNames = (names, owner) => {
|
|
817
|
+
if (names === undefined)
|
|
818
|
+
return [];
|
|
819
|
+
if (!Array.isArray(names) || names.some((name) => typeof name !== "string" || !name.trim()))
|
|
820
|
+
throw new Error(`${owner}: trustedDependencies lists package names`);
|
|
821
|
+
return [...new Set(names.map((name) => name.trim()))];
|
|
822
|
+
};
|
|
494
823
|
var normalizeKeepFonts = (keepFonts) => [
|
|
495
824
|
...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean))
|
|
496
825
|
];
|
|
@@ -506,17 +835,20 @@ class AkanAppConfig {
|
|
|
506
835
|
rootPackageJson;
|
|
507
836
|
docker;
|
|
508
837
|
dockerfile;
|
|
509
|
-
|
|
838
|
+
database;
|
|
510
839
|
web;
|
|
511
840
|
externalLibs;
|
|
841
|
+
trustedDependencies;
|
|
842
|
+
bin;
|
|
843
|
+
libBins;
|
|
512
844
|
barrelImports;
|
|
513
845
|
optimizeImports;
|
|
514
846
|
images;
|
|
515
847
|
i18n;
|
|
516
848
|
api;
|
|
517
849
|
publicEnv;
|
|
518
|
-
|
|
519
|
-
|
|
850
|
+
native;
|
|
851
|
+
hasNativeConfig;
|
|
520
852
|
secrets;
|
|
521
853
|
assets;
|
|
522
854
|
syncPageLibs;
|
|
@@ -534,8 +866,17 @@ class AkanAppConfig {
|
|
|
534
866
|
this.baseDevEnv = baseDevEnv;
|
|
535
867
|
this.plugins = plugins;
|
|
536
868
|
this.#applyRoutes(config?.routes);
|
|
537
|
-
this.
|
|
869
|
+
this.database = AkanAppConfig.#database(app, config);
|
|
538
870
|
this.externalLibs = [...new Set([...config?.externalLibs ?? [], ...libContributions.externalLibs])];
|
|
871
|
+
const owner = `apps/${app.name}/akan.config.ts`;
|
|
872
|
+
this.trustedDependencies = [
|
|
873
|
+
...new Set([
|
|
874
|
+
...normalizePackageNames(config?.trustedDependencies, owner),
|
|
875
|
+
...libContributions.trustedDependencies ?? []
|
|
876
|
+
])
|
|
877
|
+
];
|
|
878
|
+
this.bin = AkanBin.parse(config?.bin, owner, app.cwdPath);
|
|
879
|
+
this.libBins = libContributions.bin ?? [];
|
|
539
880
|
this.barrelImports = [
|
|
540
881
|
...DEFAULT_BARREL_IMPORTS,
|
|
541
882
|
...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
|
|
@@ -563,100 +904,202 @@ class AkanAppConfig {
|
|
|
563
904
|
]
|
|
564
905
|
};
|
|
565
906
|
this.syncPageLibs = config?.syncPageLibs ?? false;
|
|
566
|
-
|
|
567
|
-
|
|
907
|
+
if (config.mobile !== undefined)
|
|
908
|
+
throw new Error(`apps/${this.app.name}/akan.config.ts declares \`mobile\`, which is now \`native\`: the platform settings sit in native.ios, native.android and native.desktop, and a target overrides them under native.targets.<name>.`);
|
|
909
|
+
this.hasNativeConfig = Boolean(config.native);
|
|
910
|
+
this.native = this.#resolveNativeConfig(config.native);
|
|
568
911
|
this.web = this.#resolveWebConfig(config.web);
|
|
569
912
|
this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
|
|
570
913
|
this.dockerfile = this.#makeDockerfile();
|
|
571
914
|
}
|
|
572
915
|
#resolveWebConfig(web) {
|
|
573
916
|
const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
|
|
574
|
-
if (!resolved.csr && this.
|
|
575
|
-
throw new Error(`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares
|
|
917
|
+
if (!resolved.csr && this.hasNativeConfig)
|
|
918
|
+
throw new Error(`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares a native section; the native apps ship that bundle. Drop the native section or leave CSR on.`);
|
|
576
919
|
return resolved;
|
|
577
920
|
}
|
|
578
|
-
#
|
|
579
|
-
const {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const
|
|
585
|
-
const
|
|
586
|
-
const
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
if (
|
|
599
|
-
throw new Error(
|
|
600
|
-
}
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
921
|
+
#resolveNativeConfig(native) {
|
|
922
|
+
const configPath = `apps/${this.app.name}/akan.config.ts`;
|
|
923
|
+
const { targets: rawTargets, ...root } = native ?? {};
|
|
924
|
+
AkanAppConfig.#assertNativeKeys(root, "native", configPath);
|
|
925
|
+
AkanAppConfig.#assertFiles(root, "native", configPath);
|
|
926
|
+
const appName = root.appName ?? this.app.name;
|
|
927
|
+
const appId = root.appId ?? deriveDefaultAppId(this.baseDevEnv.repoName, this.app.name);
|
|
928
|
+
const version = root.version ?? "0.0.1";
|
|
929
|
+
const buildNum = root.buildNum ?? 1;
|
|
930
|
+
const named = rawTargets !== undefined && Object.keys(rawTargets).length > 0;
|
|
931
|
+
const entries = named ? Object.entries(rawTargets) : [[this.basePaths.has(this.app.name) ? this.app.name : "default", {}]];
|
|
932
|
+
const targets = Object.fromEntries(entries.map(([name, own]) => {
|
|
933
|
+
const where = named ? `native.targets.${name}` : "native";
|
|
934
|
+
AkanAppConfig.#assertNativeKeys(own ?? {}, where, configPath);
|
|
935
|
+
const merged = AkanAppConfig.#overlay(root, own ?? {});
|
|
936
|
+
const fallbackBasePath = !named && this.basePaths.has(name) ? name : undefined;
|
|
937
|
+
const basePath = (merged.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
|
|
938
|
+
if (basePath && !this.basePaths.has(basePath))
|
|
939
|
+
throw new Error(`Native target '${name}' uses unknown basePath '${basePath}' in ${configPath}`);
|
|
940
|
+
const { updates } = merged;
|
|
941
|
+
if (updates && (!updates.url || !updates.publicKey))
|
|
942
|
+
throw new Error(`${where}.updates in ${configPath} has no url or publicKey${named ? "; give them in native.updates or the target's own" : ""}.`);
|
|
943
|
+
AkanAppConfig.#assertFiles(own ?? {}, where, configPath);
|
|
944
|
+
const {
|
|
945
|
+
basePath: _basePath,
|
|
946
|
+
indexPath: rawIndexPath,
|
|
947
|
+
deepLinks: rawDeepLinks,
|
|
948
|
+
updates: _updates,
|
|
949
|
+
...rest
|
|
950
|
+
} = merged;
|
|
951
|
+
const indexPath = normalizeIndexPath(rawIndexPath);
|
|
952
|
+
const deepLinks = normalizeDeepLinks(rawDeepLinks);
|
|
953
|
+
const { teamId: rawTeamId, ...iosRest } = AkanAppConfig.#platformIndexPath(merged.ios ?? {});
|
|
954
|
+
const teamId = rawTeamId?.trim();
|
|
955
|
+
const { sha256CertFingerprints: rawFingerprints, ...androidRest } = AkanAppConfig.#platformIndexPath(merged.android ?? {});
|
|
956
|
+
const fingerprints = normalizeStringList(rawFingerprints);
|
|
957
|
+
const target = {
|
|
958
|
+
...rest,
|
|
604
959
|
name,
|
|
605
|
-
basePath,
|
|
606
|
-
indexPath,
|
|
607
|
-
deepLinks,
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
...target.android
|
|
619
|
-
},
|
|
620
|
-
ios: {
|
|
621
|
-
...rawMobile.ios,
|
|
622
|
-
...target.ios
|
|
623
|
-
}
|
|
960
|
+
...basePath ? { basePath } : {},
|
|
961
|
+
...indexPath ? { indexPath } : {},
|
|
962
|
+
...deepLinks ? { deepLinks } : {},
|
|
963
|
+
...merged.ios ? { ios: { ...iosRest, ...teamId ? { teamId } : {} } } : {},
|
|
964
|
+
...merged.android ? { android: { ...androidRest, ...fingerprints ? { sha256CertFingerprints: fingerprints } : {} } } : {},
|
|
965
|
+
...merged.desktop ? {
|
|
966
|
+
desktop: AkanAppConfig.#desktopServer(AkanAppConfig.#platformIndexPath(merged.desktop), `${where}.desktop.server`, configPath)
|
|
967
|
+
} : {},
|
|
968
|
+
appName: merged.appName ?? this.app.name,
|
|
969
|
+
appId: merged.appId ?? appId,
|
|
970
|
+
version: merged.version ?? version,
|
|
971
|
+
buildNum: merged.buildNum ?? buildNum,
|
|
972
|
+
...updates ? { updates } : {}
|
|
624
973
|
};
|
|
625
|
-
return [name,
|
|
974
|
+
return [name, target];
|
|
626
975
|
}));
|
|
627
|
-
return {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
976
|
+
return { appName, appId, ...root.fileName ? { fileName: root.fileName } : {}, version, buildNum, targets };
|
|
977
|
+
}
|
|
978
|
+
static #desktopServer(desktop, where, configPath) {
|
|
979
|
+
const { server } = desktop;
|
|
980
|
+
if (server === undefined || typeof server === "boolean")
|
|
981
|
+
return desktop;
|
|
982
|
+
if (!AkanAppConfig.#isPlainObject(server))
|
|
983
|
+
throw new Error(`${where} in ${configPath} must be true or { omit: string[] }.`);
|
|
984
|
+
const omit = server.omit;
|
|
985
|
+
if (omit !== undefined && (!Array.isArray(omit) || omit.some((name) => typeof name !== "string" || !name.trim())))
|
|
986
|
+
throw new Error(`${where}.omit in ${configPath} must be a list of package names.`);
|
|
987
|
+
const names = [...new Set((omit ?? []).map((name) => name.trim()))].sort();
|
|
988
|
+
return { ...desktop, server: { omit: names } };
|
|
989
|
+
}
|
|
990
|
+
static #platformIndexPath(section) {
|
|
991
|
+
const { indexPath: rawIndexPath, ...rest } = section;
|
|
992
|
+
const indexPath = normalizeIndexPath(rawIndexPath);
|
|
993
|
+
return { ...rest, ...indexPath ? { indexPath } : {} };
|
|
994
|
+
}
|
|
995
|
+
static #overlay(base, own) {
|
|
996
|
+
const merge = (kept, value, top, key) => {
|
|
997
|
+
if (!AkanAppConfig.#isPlainObject(kept) || !AkanAppConfig.#isPlainObject(value))
|
|
998
|
+
return value;
|
|
999
|
+
if (top && (key === "icon" || key === "splash"))
|
|
1000
|
+
return value;
|
|
1001
|
+
const out = { ...kept };
|
|
1002
|
+
for (const [inner, next] of Object.entries(value))
|
|
1003
|
+
if (next !== undefined)
|
|
1004
|
+
out[inner] = merge(out[inner], next, false, inner);
|
|
1005
|
+
return out;
|
|
635
1006
|
};
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
return
|
|
1007
|
+
const out = { ...base };
|
|
1008
|
+
for (const [key, value] of Object.entries(own))
|
|
1009
|
+
if (value !== undefined)
|
|
1010
|
+
out[key] = merge(out[key], value, true, key);
|
|
1011
|
+
return out;
|
|
1012
|
+
}
|
|
1013
|
+
static #isPlainObject(value) {
|
|
1014
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1015
|
+
}
|
|
1016
|
+
static #nativeKeys = {
|
|
1017
|
+
native: [
|
|
1018
|
+
"basePath",
|
|
1019
|
+
"indexPath",
|
|
1020
|
+
"appName",
|
|
1021
|
+
"appId",
|
|
1022
|
+
"fileName",
|
|
1023
|
+
"version",
|
|
1024
|
+
"buildNum",
|
|
1025
|
+
"icon",
|
|
1026
|
+
"splash",
|
|
1027
|
+
"permissions",
|
|
1028
|
+
"plugins",
|
|
1029
|
+
"deepLinks",
|
|
1030
|
+
"updates",
|
|
1031
|
+
"ios",
|
|
1032
|
+
"android",
|
|
1033
|
+
"desktop"
|
|
1034
|
+
],
|
|
1035
|
+
ios: ["indexPath", "teamId", "infoPlist", "entitlements", "privacy", "files"],
|
|
1036
|
+
android: [
|
|
1037
|
+
"indexPath",
|
|
1038
|
+
"sha256CertFingerprints",
|
|
1039
|
+
"googleServices",
|
|
1040
|
+
"push",
|
|
1041
|
+
"autoplay",
|
|
1042
|
+
"manifest",
|
|
1043
|
+
"application",
|
|
1044
|
+
"activity",
|
|
1045
|
+
"files"
|
|
1046
|
+
],
|
|
1047
|
+
desktop: ["indexPath", "server", "recovery", "window", "screenCapture", "entitlements"],
|
|
1048
|
+
desktopServer: ["omit"],
|
|
1049
|
+
deepLinks: ["schemes", "domains"]
|
|
1050
|
+
};
|
|
1051
|
+
static #movedKeys = {
|
|
1052
|
+
native: "the platform sections and plugins sit directly in the native section: ios, android, desktop, plugins",
|
|
1053
|
+
assets: "icon and splash sit directly in the native section",
|
|
1054
|
+
files: "ios.files (keyed by the path in the app bundle) or android.files (keyed res/<type>/<file> or assets/<path>)",
|
|
1055
|
+
push: "android.push",
|
|
1056
|
+
privacy: "ios.privacy",
|
|
1057
|
+
"deepLinks.ios": "ios.teamId",
|
|
1058
|
+
"deepLinks.android": "android.sha256CertFingerprints"
|
|
1059
|
+
};
|
|
1060
|
+
static #assertNativeKeys(section, where, configPath) {
|
|
1061
|
+
const check = (value, known, at, kind) => {
|
|
1062
|
+
if (!AkanAppConfig.#isPlainObject(value))
|
|
1063
|
+
return;
|
|
1064
|
+
for (const key of Object.keys(value)) {
|
|
1065
|
+
if (known.includes(key))
|
|
1066
|
+
continue;
|
|
1067
|
+
const moved = AkanAppConfig.#movedKeys[kind === "native" ? key : `${kind}.${key}`];
|
|
1068
|
+
throw new Error(moved ? `${at}.${key} in ${configPath} has moved: ${moved}.` : `${at}.${key} in ${configPath} is not a native setting. Known: ${known.join(", ")}.`);
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
check(section, AkanAppConfig.#nativeKeys.native, where, "native");
|
|
1072
|
+
const settings = section;
|
|
1073
|
+
check(settings.ios, AkanAppConfig.#nativeKeys.ios, `${where}.ios`, "ios");
|
|
1074
|
+
check(settings.android, AkanAppConfig.#nativeKeys.android, `${where}.android`, "android");
|
|
1075
|
+
check(settings.desktop, AkanAppConfig.#nativeKeys.desktop, `${where}.desktop`, "desktop");
|
|
1076
|
+
check(settings.desktop?.server, AkanAppConfig.#nativeKeys.desktopServer, `${where}.desktop.server`, "desktopServer");
|
|
1077
|
+
check(settings.deepLinks, AkanAppConfig.#nativeKeys.deepLinks, `${where}.deepLinks`, "deepLinks");
|
|
1078
|
+
}
|
|
1079
|
+
static #androidFilePattern = /^(?:res\/[^/]+\/[^/]+|assets\/.+)$/;
|
|
1080
|
+
static #assertFiles(settings, where, configPath) {
|
|
1081
|
+
const check = (files, at, valid, shape) => {
|
|
1082
|
+
for (const [to, from] of Object.entries(files ?? {})) {
|
|
1083
|
+
if (typeof from !== "string")
|
|
1084
|
+
throw new Error(`${at}["${to}"] in ${configPath} must name its source file, relative to the app folder, e.g. { "${shape}": "assets/chime.mp3" }.`);
|
|
1085
|
+
if (!valid(to))
|
|
1086
|
+
throw new Error(`${at}["${to}"] in ${configPath} must land at ${shape}.`);
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
check(settings.ios?.files, `${where}.ios.files`, (to) => to.length > 0 && !to.startsWith("/") && !to.split("/").includes(".."), "<path in the app bundle>");
|
|
1090
|
+
check(settings.android?.files, `${where}.android.files`, (to) => AkanAppConfig.#androidFilePattern.test(to), "res/<type>/<file> or assets/<path>");
|
|
641
1091
|
}
|
|
642
1092
|
#applyRoutes(routes = []) {
|
|
643
1093
|
for (const route of routes) {
|
|
644
|
-
|
|
645
|
-
|
|
1094
|
+
const basePath = route.basePath ? route.basePath.replace(/^\/+|\/+$/g, "") : null;
|
|
1095
|
+
if (basePath !== null)
|
|
646
1096
|
this.basePaths.add(basePath);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
} else {
|
|
654
|
-
Object.keys(route.domains).forEach((branch) => void this.branches.add(branch));
|
|
655
|
-
Object.values(route.domains).flat().forEach((domain) => {
|
|
656
|
-
if (domain)
|
|
657
|
-
this.domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
|
|
658
|
-
});
|
|
659
|
-
}
|
|
1097
|
+
const domains = basePath !== null ? this.subRoutes.getOrInsert(basePath, new Set) : this.domains;
|
|
1098
|
+
for (const branch of Object.keys(route.domains))
|
|
1099
|
+
this.branches.add(branch);
|
|
1100
|
+
for (const domain of Object.values(route.domains).flat())
|
|
1101
|
+
if (domain)
|
|
1102
|
+
domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
|
|
660
1103
|
}
|
|
661
1104
|
const appName = this.app.name.toLowerCase();
|
|
662
1105
|
const serveDomain = this.baseDevEnv.serveDomain.toLowerCase();
|
|
@@ -693,18 +1136,37 @@ class AkanAppConfig {
|
|
|
693
1136
|
command: docker?.command ?? ["bun", "main.js"]
|
|
694
1137
|
};
|
|
695
1138
|
}
|
|
696
|
-
|
|
1139
|
+
getProductionEnv(environment = this.baseDevEnv.env) {
|
|
1140
|
+
return {
|
|
1141
|
+
PORT: "8282",
|
|
1142
|
+
NODE_ENV: "production",
|
|
1143
|
+
AKAN_PUBLIC_REPO_NAME: this.baseDevEnv.repoName,
|
|
1144
|
+
AKAN_PUBLIC_SERVE_DOMAIN: this.baseDevEnv.serveDomain,
|
|
1145
|
+
AKAN_PUBLIC_APP_NAME: this.app.name,
|
|
1146
|
+
AKAN_PUBLIC_ENV: environment,
|
|
1147
|
+
...this.basePaths.size ? { AKAN_PUBLIC_BASE_PATHS: [...this.basePaths].join(",") } : {},
|
|
1148
|
+
AKAN_PUBLIC_DEFAULT_LOCALE: this.i18n.defaultLocale,
|
|
1149
|
+
AKAN_PUBLIC_LOCALES: this.i18n.locales.join(","),
|
|
1150
|
+
AKAN_PUBLIC_API_PREFIX: this.api.prefix,
|
|
1151
|
+
AKAN_PUBLIC_WS_PREFIX: this.api.websocketPrefix,
|
|
1152
|
+
AKAN_PUBLIC_OPERATION_MODE: "cloud",
|
|
1153
|
+
AKAN_DATABASE_MODES: this.database.modes.join(","),
|
|
1154
|
+
AKAN_LOG_TO_FILE: "0",
|
|
1155
|
+
...this.web.ssr ? {} : { AKAN_SSR: "false" },
|
|
1156
|
+
...this.web.csr ? {} : { AKAN_CSR: "false" }
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
dockerfileFor(environment) {
|
|
1160
|
+
return this.#makeDockerfile(environment);
|
|
1161
|
+
}
|
|
1162
|
+
#makeDockerfile(environment) {
|
|
697
1163
|
if (typeof this.docker === "string")
|
|
698
1164
|
return this.docker;
|
|
699
1165
|
const { image, preRuns, postRuns, command } = this.docker;
|
|
700
1166
|
const preRunScripts = this.#getDockerRunScripts(preRuns);
|
|
701
1167
|
const postRunScripts = this.#getDockerRunScripts(postRuns);
|
|
702
1168
|
const imageScript = this.#getDockerImageScript(image, DEFAULT_DOCKER_IMAGE);
|
|
703
|
-
const
|
|
704
|
-
...this.web.ssr ? [] : ["ENV AKAN_SSR=false"],
|
|
705
|
-
...this.web.csr ? [] : ["ENV AKAN_CSR=false"]
|
|
706
|
-
].join(`
|
|
707
|
-
`);
|
|
1169
|
+
const envLines = Object.entries(this.getProductionEnv(environment)).map(([key, value]) => `ENV ${key}=${value}`);
|
|
708
1170
|
return `${imageScript}
|
|
709
1171
|
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
|
|
710
1172
|
RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
|
|
@@ -718,20 +1180,8 @@ RUN bun install --production
|
|
|
718
1180
|
${postRunScripts.join(`
|
|
719
1181
|
`)}
|
|
720
1182
|
COPY . .
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
ENV AKAN_PUBLIC_REPO_NAME=${this.baseDevEnv.repoName}
|
|
724
|
-
ENV AKAN_PUBLIC_SERVE_DOMAIN=${this.baseDevEnv.serveDomain}
|
|
725
|
-
ENV AKAN_PUBLIC_APP_NAME=${this.app.name}
|
|
726
|
-
ENV AKAN_PUBLIC_ENV=${this.baseDevEnv.env}
|
|
727
|
-
${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join(",")}` : ""}
|
|
728
|
-
ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
|
|
729
|
-
ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
|
|
730
|
-
ENV AKAN_PUBLIC_API_PREFIX=${this.api.prefix}
|
|
731
|
-
ENV AKAN_PUBLIC_WS_PREFIX=${this.api.websocketPrefix}
|
|
732
|
-
ENV AKAN_PUBLIC_OPERATION_MODE=cloud
|
|
733
|
-
ENV AKAN_LOG_TO_FILE=0
|
|
734
|
-
${webEnvLines}
|
|
1183
|
+
${envLines.join(`
|
|
1184
|
+
`)}
|
|
735
1185
|
CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
736
1186
|
}
|
|
737
1187
|
static #importGeneration = 0;
|
|
@@ -743,7 +1193,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
743
1193
|
static async from(app, { bustImportCache = false } = {}) {
|
|
744
1194
|
const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
|
|
745
1195
|
AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
|
|
746
|
-
WorkspaceExecutor.getBaseDevEnv(
|
|
1196
|
+
WorkspaceExecutor.getBaseDevEnv(path5.join(app.workspace.workspaceRoot, ".env")),
|
|
747
1197
|
app.workspace.getLibs(),
|
|
748
1198
|
app.workspace.getPackageJson()
|
|
749
1199
|
]);
|
|
@@ -752,504 +1202,160 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
752
1202
|
const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
|
|
753
1203
|
return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
|
|
754
1204
|
}
|
|
755
|
-
static async#collectLibContributions(app, libs, bustImportCache) {
|
|
756
|
-
const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
|
|
757
|
-
app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
|
|
758
|
-
return null;
|
|
759
|
-
})));
|
|
760
|
-
return {
|
|
761
|
-
externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
|
|
771
|
-
if (rootVersion)
|
|
772
|
-
return rootVersion;
|
|
773
|
-
const akanPackageJson = getAkanPackageJson();
|
|
774
|
-
return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
775
|
-
}
|
|
776
|
-
#getProductionRuntimePackages() {
|
|
777
|
-
return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
|
|
778
|
-
}
|
|
779
|
-
getDatabaseModeRuntimePackages(databaseMode = this.defaultDatabaseMode) {
|
|
780
|
-
return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
|
|
781
|
-
}
|
|
782
|
-
getMissingDatabaseModeDependencySpecs(databaseMode = this.defaultDatabaseMode) {
|
|
783
|
-
return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
|
|
784
|
-
}
|
|
785
|
-
getMobileRuntimePackages() {
|
|
786
|
-
return [...new Set([...MOBILE_RUNTIME_PACKAGES, ...MOBILE_APP_CAPACITOR_PLUGINS])];
|
|
787
|
-
}
|
|
788
|
-
getMissingMobileDependencySpecs() {
|
|
789
|
-
return this.#getMissingDependencySpecs(this.getMobileRuntimePackages());
|
|
790
|
-
}
|
|
791
|
-
getMobileAppCapacitorPlugins() {
|
|
792
|
-
return [...MOBILE_APP_CAPACITOR_PLUGINS];
|
|
793
|
-
}
|
|
794
|
-
#getMissingDependencySpecs(libs) {
|
|
795
|
-
const rootDependencies = {
|
|
796
|
-
...this.rootPackageJson.dependencies,
|
|
797
|
-
...this.rootPackageJson.devDependencies
|
|
798
|
-
};
|
|
799
|
-
return libs.filter((lib) => !rootDependencies[lib]).map((lib) => {
|
|
800
|
-
const version = this.#resolveProductionDependencyVersion(lib);
|
|
801
|
-
if (!version)
|
|
802
|
-
throw new Error(`Dependency ${lib} not found in package.json`);
|
|
803
|
-
return `${lib}@${version}`;
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
getProductionPackageJson(data = {}) {
|
|
807
|
-
return {
|
|
808
|
-
name: this.app.name,
|
|
809
|
-
description: this.app.name,
|
|
810
|
-
version: "1.0.0",
|
|
811
|
-
main: "./main.js",
|
|
812
|
-
dependencies: Object.fromEntries([...new Set(this.#getProductionRuntimePackages())].map((lib) => {
|
|
813
|
-
const version = this.#resolveProductionDependencyVersion(lib);
|
|
814
|
-
if (!version)
|
|
815
|
-
throw new Error(`Dependency ${lib} not found in package.json`);
|
|
816
|
-
return [lib, version];
|
|
817
|
-
})),
|
|
818
|
-
...data
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
var akanPackageJson = null;
|
|
823
|
-
function getAkanPackageJson() {
|
|
824
|
-
if (akanPackageJson)
|
|
825
|
-
return akanPackageJson;
|
|
826
|
-
const sourceDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
827
|
-
const packageJsonPaths = [
|
|
828
|
-
path2.join(sourceDir, "../../../akanjs/package.json"),
|
|
829
|
-
path2.join(process.cwd(), "pkgs/akanjs/package.json"),
|
|
830
|
-
path2.join(path2.dirname(Bun.main), "node_modules/akanjs/package.json")
|
|
831
|
-
];
|
|
832
|
-
try {
|
|
833
|
-
packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
|
|
834
|
-
} catch {}
|
|
835
|
-
for (const packageJsonPath of packageJsonPaths) {
|
|
836
|
-
try {
|
|
837
|
-
akanPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
838
|
-
return akanPackageJson;
|
|
839
|
-
} catch {}
|
|
840
|
-
}
|
|
841
|
-
akanPackageJson = {
|
|
842
|
-
name: "akanjs",
|
|
843
|
-
version: "0.0.0",
|
|
844
|
-
description: "akanjs",
|
|
845
|
-
dependencies: {}
|
|
846
|
-
};
|
|
847
|
-
return akanPackageJson;
|
|
848
|
-
}
|
|
849
|
-
function mergeImageConfig(config = {}) {
|
|
850
|
-
return {
|
|
851
|
-
...DEFAULT_AKAN_IMAGE_CONFIG,
|
|
852
|
-
...config,
|
|
853
|
-
deviceSizes: config.deviceSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.deviceSizes,
|
|
854
|
-
imageSizes: config.imageSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.imageSizes,
|
|
855
|
-
formats: config.formats ?? DEFAULT_AKAN_IMAGE_CONFIG.formats,
|
|
856
|
-
qualities: config.qualities ?? DEFAULT_AKAN_IMAGE_CONFIG.qualities,
|
|
857
|
-
remotePatterns: config.remotePatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.remotePatterns,
|
|
858
|
-
localPatterns: config.localPatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.localPatterns
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
class AkanLibConfig {
|
|
863
|
-
lib;
|
|
864
|
-
externalLibs;
|
|
865
|
-
docker;
|
|
866
|
-
assets;
|
|
867
|
-
plugins;
|
|
868
|
-
constructor(lib, config, plugins = []) {
|
|
869
|
-
this.lib = lib;
|
|
870
|
-
this.externalLibs = config?.externalLibs ?? [];
|
|
871
|
-
this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
|
|
872
|
-
this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
|
|
873
|
-
this.plugins = plugins;
|
|
874
|
-
}
|
|
875
|
-
static async from(lib, { bustImportCache = false } = {}) {
|
|
876
|
-
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
877
|
-
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
878
|
-
const { plugins, ...config } = resolved ?? {};
|
|
879
|
-
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
//! need to refactor
|
|
883
|
-
var increaseBuildNum = async (app) => {
|
|
884
|
-
const appConfig = await AkanAppConfig.from(app);
|
|
885
|
-
const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
|
|
886
|
-
const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
|
|
887
|
-
const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum + 1}`);
|
|
888
|
-
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
889
|
-
};
|
|
890
|
-
var decreaseBuildNum = async (app) => {
|
|
891
|
-
const appConfig = await AkanAppConfig.from(app);
|
|
892
|
-
const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
|
|
893
|
-
const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
|
|
894
|
-
const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
|
|
895
|
-
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
896
|
-
};
|
|
897
|
-
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
898
|
-
import { mkdir as mkdir2 } from "fs/promises";
|
|
899
|
-
import path4 from "path";
|
|
900
|
-
|
|
901
|
-
// pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
|
|
902
|
-
import fs2 from "fs";
|
|
903
|
-
import path3 from "path";
|
|
904
|
-
|
|
905
|
-
class AsyncDefaultExportDetector {
|
|
906
|
-
static #typescriptLoad;
|
|
907
|
-
static #loadTypescript() {
|
|
908
|
-
AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
|
|
909
|
-
return AsyncDefaultExportDetector.#typescriptLoad;
|
|
910
|
-
}
|
|
911
|
-
static async detect(moduleAbsPath) {
|
|
912
|
-
try {
|
|
913
|
-
const typescript = await AsyncDefaultExportDetector.#loadTypescript();
|
|
914
|
-
const source = fs2.readFileSync(path3.resolve(moduleAbsPath), "utf8");
|
|
915
|
-
const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, AsyncDefaultExportDetector.#scriptKind(typescript, moduleAbsPath));
|
|
916
|
-
return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
|
|
917
|
-
} catch {
|
|
918
|
-
return false;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
#ts;
|
|
922
|
-
constructor(typescript) {
|
|
923
|
-
this.#ts = typescript;
|
|
924
|
-
}
|
|
925
|
-
detectInSourceFile(sourceFile) {
|
|
926
|
-
const ts = this.#ts;
|
|
927
|
-
const asyncBindings = new Map;
|
|
928
|
-
let defaultIdentifier = null;
|
|
929
|
-
for (const statement of sourceFile.statements) {
|
|
930
|
-
if (ts.isFunctionDeclaration(statement)) {
|
|
931
|
-
if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
|
|
932
|
-
return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
|
|
933
|
-
}
|
|
934
|
-
if (statement.name) {
|
|
935
|
-
asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
|
|
936
|
-
}
|
|
937
|
-
continue;
|
|
938
|
-
}
|
|
939
|
-
if (ts.isVariableStatement(statement)) {
|
|
940
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
941
|
-
if (!ts.isIdentifier(declaration.name))
|
|
942
|
-
continue;
|
|
943
|
-
asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
|
|
944
|
-
}
|
|
945
|
-
continue;
|
|
946
|
-
}
|
|
947
|
-
if (ts.isExportAssignment(statement)) {
|
|
948
|
-
if (this.#isAsyncFunctionExpression(statement.expression))
|
|
949
|
-
return true;
|
|
950
|
-
if (ts.isCallExpression(statement.expression))
|
|
951
|
-
return true;
|
|
952
|
-
if (ts.isIdentifier(statement.expression))
|
|
953
|
-
defaultIdentifier = statement.expression.text;
|
|
954
|
-
continue;
|
|
955
|
-
}
|
|
956
|
-
if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
957
|
-
const exportClause = statement.exportClause;
|
|
958
|
-
for (const specifier of exportClause.elements) {
|
|
959
|
-
if (specifier.name.text !== "default")
|
|
960
|
-
continue;
|
|
961
|
-
defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
|
|
1205
|
+
static async#collectLibContributions(app, libs, bustImportCache) {
|
|
1206
|
+
const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
|
|
1207
|
+
app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
|
|
1208
|
+
return null;
|
|
1209
|
+
})));
|
|
1210
|
+
return {
|
|
1211
|
+
externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
|
|
1212
|
+
trustedDependencies: libConfigs.flatMap((libConfig) => libConfig?.trustedDependencies ?? []),
|
|
1213
|
+
bin: libConfigs.flatMap((libConfig) => libConfig && Object.keys(libConfig.bin).length ? [{ lib: libConfig.lib.name, bin: libConfig.bin }] : []),
|
|
1214
|
+
docker: {
|
|
1215
|
+
preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
|
|
1216
|
+
postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? [])
|
|
1217
|
+
},
|
|
1218
|
+
keepFonts: libConfigs.flatMap((libConfig) => (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`))
|
|
1219
|
+
};
|
|
966
1220
|
}
|
|
967
|
-
#
|
|
968
|
-
const
|
|
969
|
-
|
|
1221
|
+
#resolveProductionDependencyVersion(lib) {
|
|
1222
|
+
const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
|
|
1223
|
+
if (rootVersion)
|
|
1224
|
+
return rootVersion;
|
|
1225
|
+
const akanPackageJson = getAkanPackageJson();
|
|
1226
|
+
const version = akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
1227
|
+
if (!version)
|
|
1228
|
+
throw new Error(`Dependency ${lib} not found in package.json`);
|
|
1229
|
+
return version;
|
|
970
1230
|
}
|
|
971
|
-
#
|
|
972
|
-
|
|
973
|
-
|
|
1231
|
+
#getProductionRuntimePackages() {
|
|
1232
|
+
return [
|
|
1233
|
+
...this.externalLibs,
|
|
1234
|
+
...SSR_RUNTIME_PACKAGES,
|
|
1235
|
+
...this.database.modes.flatMap((mode) => this.getDatabaseModeRuntimePackages(mode))
|
|
1236
|
+
];
|
|
974
1237
|
}
|
|
975
|
-
|
|
976
|
-
return
|
|
1238
|
+
getDatabaseModeRuntimePackages(databaseMode) {
|
|
1239
|
+
return [...DatabaseModes.drivers[databaseMode]];
|
|
977
1240
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
var IMPLICIT_DICT_DIR = path4.join(".akan", "generated", "dict");
|
|
987
|
-
var IMPLICIT_OVERRIDES_DIR = path4.join(".akan", "generated", "overrides");
|
|
988
|
-
var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
989
|
-
async function writeGeneratedOverridesLayoutFile(opts) {
|
|
990
|
-
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
991
|
-
const absPath = path4.join(path4.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
992
|
-
const userRel = path4.relative(path4.dirname(absPath), opts.userAbsPath).split(path4.sep).join("/");
|
|
993
|
-
const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
|
|
994
|
-
const source = `"use client";
|
|
995
|
-
import { UiOverrideProvider } from "akanjs/ui";
|
|
996
|
-
import { createElement, type ReactNode } from "react";
|
|
997
|
-
import value from ${JSON.stringify(userSpecifier)};
|
|
998
|
-
|
|
999
|
-
export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
|
|
1000
|
-
return createElement(UiOverrideProvider, { value }, children);
|
|
1001
|
-
}
|
|
1002
|
-
`;
|
|
1003
|
-
await Bun.write(absPath, source);
|
|
1004
|
-
return absPath;
|
|
1005
|
-
}
|
|
1006
|
-
function getRootBoundarySegments(key) {
|
|
1007
|
-
const match = LAYOUT_KEY_RE.exec(key);
|
|
1008
|
-
if (!match)
|
|
1009
|
-
return null;
|
|
1010
|
-
const prefix = match[1]?.replace(/\/$/, "");
|
|
1011
|
-
if (!prefix)
|
|
1012
|
-
return [];
|
|
1013
|
-
return prefix.split("/").filter(Boolean);
|
|
1014
|
-
}
|
|
1015
|
-
function implicitRootLayoutKey(segments) {
|
|
1016
|
-
return `./${[...segments, "__root_layout"].join("/")}.tsx`;
|
|
1017
|
-
}
|
|
1018
|
-
function implicitRootLayoutAbsPath(appCwdPath, segments) {
|
|
1019
|
-
const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
|
|
1020
|
-
return path4.join(path4.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
1021
|
-
}
|
|
1022
|
-
function implicitDictionaryMacroAbsPath(appCwdPath) {
|
|
1023
|
-
return path4.join(path4.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
1024
|
-
}
|
|
1025
|
-
function isRootBoundarySegments(segments, basePaths) {
|
|
1026
|
-
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
1027
|
-
if (firstVisibleIndex === -1)
|
|
1028
|
-
return segments.length <= 1;
|
|
1029
|
-
if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
|
|
1030
|
-
return false;
|
|
1031
|
-
const visible = segments.slice(firstVisibleIndex);
|
|
1032
|
-
const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
|
|
1033
|
-
return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
|
|
1034
|
-
}
|
|
1035
|
-
function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
|
|
1036
|
-
const boundaries = new Map;
|
|
1037
|
-
for (const key of pageKeys) {
|
|
1038
|
-
const segments = getRootBoundarySegments(key);
|
|
1039
|
-
if (!segments)
|
|
1040
|
-
continue;
|
|
1041
|
-
if (!isRootBoundarySegments(segments, basePaths))
|
|
1042
|
-
continue;
|
|
1043
|
-
const id = segments.join("/");
|
|
1044
|
-
boundaries.set(id, {
|
|
1045
|
-
sourceKey: key,
|
|
1046
|
-
sourceAbsPath: path4.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
|
|
1047
|
-
segments
|
|
1241
|
+
getMissingDatabaseModeDependencySpecs(databaseMode) {
|
|
1242
|
+
return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
|
|
1243
|
+
}
|
|
1244
|
+
resolveDatabaseMode() {
|
|
1245
|
+
return DatabaseModes.resolve({
|
|
1246
|
+
requested: process.env.AKAN_DATABASE_MODE,
|
|
1247
|
+
declared: this.database.modes.join(","),
|
|
1248
|
+
local: true
|
|
1048
1249
|
});
|
|
1049
1250
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1251
|
+
static #database(app, config) {
|
|
1252
|
+
const declared = config?.database?.modes?.filter((mode) => !!mode);
|
|
1253
|
+
const modes = declared?.length ? declared : ["single"];
|
|
1254
|
+
return { modes: DatabaseModes.parseList(modes.join(","), `database.modes in apps/${app.name}/akan.config.ts`) };
|
|
1053
1255
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
return
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1256
|
+
#getMissingDependencySpecs(libs) {
|
|
1257
|
+
const rootDependencies = {
|
|
1258
|
+
...this.rootPackageJson.dependencies,
|
|
1259
|
+
...this.rootPackageJson.devDependencies
|
|
1260
|
+
};
|
|
1261
|
+
return libs.filter((lib) => !rootDependencies[lib]).map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
|
|
1262
|
+
}
|
|
1263
|
+
get akanVersion() {
|
|
1264
|
+
return getAkanPackageJson().version;
|
|
1265
|
+
}
|
|
1266
|
+
getProductionPackageJson(data = {}) {
|
|
1267
|
+
return {
|
|
1268
|
+
name: this.app.name,
|
|
1269
|
+
description: this.app.name,
|
|
1270
|
+
version: "1.0.0",
|
|
1271
|
+
main: "./main.js",
|
|
1272
|
+
dependencies: Object.fromEntries([...new Set(this.#getProductionRuntimePackages())].map((lib) => [
|
|
1273
|
+
lib,
|
|
1274
|
+
this.#resolveProductionDependencyVersion(lib)
|
|
1275
|
+
])),
|
|
1276
|
+
...this.trustedDependencies.length ? { trustedDependencies: this.trustedDependencies } : {},
|
|
1277
|
+
...data
|
|
1278
|
+
};
|
|
1074
1279
|
}
|
|
1075
1280
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
|
|
1103
|
-
const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
|
|
1104
|
-
` : `const inheritedLayout: LayoutModule = {};
|
|
1105
|
-
`;
|
|
1106
|
-
const prefix = routePrefixForSegments(opts.boundary.segments);
|
|
1107
|
-
const userLabel = opts.boundary.sourceAbsPath ? path4.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
|
|
1108
|
-
const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
|
|
1109
|
-
const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
|
|
1110
|
-
const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
|
|
1111
|
-
` : `const UserLayout = ({ children }: LayoutProps) => children;
|
|
1112
|
-
const userLayout: LayoutModule = {};
|
|
1113
|
-
`;
|
|
1114
|
-
const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
|
|
1115
|
-
const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
|
|
1116
|
-
const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
|
|
1117
|
-
const layoutBinding = isAsyncUserLayout ? ` const layout =
|
|
1118
|
-
process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
|
|
1119
|
-
? await UserLayout({ params, searchParams, children })
|
|
1120
|
-
: ${userLayoutElement};
|
|
1121
|
-
` : "";
|
|
1122
|
-
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
1123
|
-
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
1124
|
-
const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
1125
|
-
import { loadFonts, resolveRouteModule } from "akanjs/client";
|
|
1126
|
-
import type { ReactNode } from "react";
|
|
1127
|
-
import { System } from "akanjs/ui";
|
|
1128
|
-
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
1129
|
-
import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
|
|
1130
|
-
${clientImport}${inheritedImport}${userImport}
|
|
1131
|
-
const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
|
|
1132
|
-
const defaultFonts = userFonts.filter((font) => font.default);
|
|
1133
|
-
if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
|
|
1134
|
-
const defaultFont = defaultFonts[0];
|
|
1135
|
-
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
1136
|
-
|
|
1137
|
-
export async function generateHead(props: PageProps) {
|
|
1138
|
-
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
1139
|
-
if (userLayout.head !== undefined) return userLayout.head;
|
|
1140
|
-
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
1141
|
-
return inheritedLayout.head;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
export async function generateMetadata(props: PageProps) {
|
|
1145
|
-
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
1146
|
-
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
1147
|
-
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
1148
|
-
return inheritedLayout.metadata;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
1152
|
-
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
1153
|
-
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
1154
|
-
|
|
1155
|
-
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
1156
|
-
${layoutBinding} return (
|
|
1157
|
-
<System.Provider
|
|
1158
|
-
of={GeneratedLayout as never}
|
|
1159
|
-
appName=${JSON.stringify(opts.appName)}
|
|
1160
|
-
${prefix ? `prefix=${JSON.stringify(prefix)}
|
|
1161
|
-
` : ""}params={params}
|
|
1162
|
-
manifest={userLayout.manifest ?? inheritedLayout.manifest}
|
|
1163
|
-
env={env}
|
|
1164
|
-
theme={userLayout.theme ?? inheritedLayout.theme}
|
|
1165
|
-
fonts={loadFonts(userFonts)}
|
|
1166
|
-
className={defaultFontClassName}
|
|
1167
|
-
gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
|
|
1168
|
-
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
1169
|
-
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
1170
|
-
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
|
|
1171
|
-
allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
|
|
1172
|
-
>
|
|
1173
|
-
${layoutChild}
|
|
1174
|
-
</System.Provider>
|
|
1175
|
-
);
|
|
1176
|
-
}
|
|
1177
|
-
` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
1178
|
-
import { resolveRouteModule } from "akanjs/client";
|
|
1179
|
-
import type { ReactNode } from "react";
|
|
1180
|
-
${inheritedImport}${userImport}
|
|
1181
|
-
export async function generateHead(props: PageProps) {
|
|
1182
|
-
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
1183
|
-
if (userLayout.head !== undefined) return userLayout.head;
|
|
1184
|
-
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
1185
|
-
return inheritedLayout.head;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
export async function generateMetadata(props: PageProps) {
|
|
1189
|
-
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
1190
|
-
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
1191
|
-
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
1192
|
-
return inheritedLayout.metadata;
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
1196
|
-
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
1197
|
-
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
1198
|
-
|
|
1199
|
-
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
1200
|
-
${layoutBinding} return ${layoutReturn};
|
|
1201
|
-
}
|
|
1202
|
-
`;
|
|
1203
|
-
await Bun.write(absPath, source);
|
|
1204
|
-
return absPath;
|
|
1205
|
-
}
|
|
1206
|
-
async function resolveSsrPageEntries(opts) {
|
|
1207
|
-
const absPageDir = path4.resolve(opts.appCwdPath, "page");
|
|
1208
|
-
const hasSt = await appHasStModule(opts.appCwdPath);
|
|
1209
|
-
const basePaths = opts.basePaths ?? [];
|
|
1210
|
-
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
1211
|
-
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
1212
|
-
const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
|
|
1213
|
-
const segments = getRootBoundarySegments(key);
|
|
1214
|
-
return segments !== null && isRootBoundarySegments(segments, basePaths);
|
|
1215
|
-
}));
|
|
1216
|
-
const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
|
|
1217
|
-
const userAbsPath = path4.resolve(absPageDir, key);
|
|
1218
|
-
if (OVERRIDES_KEY_RE.test(key)) {
|
|
1219
|
-
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
1220
|
-
appCwdPath: opts.appCwdPath,
|
|
1221
|
-
key,
|
|
1222
|
-
userAbsPath
|
|
1223
|
-
});
|
|
1224
|
-
return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
|
|
1225
|
-
}
|
|
1226
|
-
return { key, moduleAbsPath: userAbsPath };
|
|
1227
|
-
}));
|
|
1228
|
-
const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
|
|
1229
|
-
key: implicitRootLayoutKey(boundary.segments),
|
|
1230
|
-
moduleAbsPath: await writeGeneratedRootLayoutFile({
|
|
1231
|
-
appCwdPath: opts.appCwdPath,
|
|
1232
|
-
appName: opts.appName,
|
|
1233
|
-
boundary,
|
|
1234
|
-
rootSourceAbsPath,
|
|
1235
|
-
includeStInit: hasSt && boundary.segments.length === 0,
|
|
1236
|
-
includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
|
|
1237
|
-
}),
|
|
1238
|
-
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
|
|
1239
|
-
})));
|
|
1240
|
-
const entries = [...base, ...generated];
|
|
1241
|
-
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
1242
|
-
return entries;
|
|
1281
|
+
var akanPackageJson = null;
|
|
1282
|
+
function getAkanPackageJson() {
|
|
1283
|
+
if (akanPackageJson)
|
|
1284
|
+
return akanPackageJson;
|
|
1285
|
+
const sourceDir = path5.dirname(fileURLToPath(import.meta.url));
|
|
1286
|
+
const packageJsonPaths = [
|
|
1287
|
+
path5.join(sourceDir, "../../../akanjs/package.json"),
|
|
1288
|
+
path5.join(process.cwd(), "pkgs/akanjs/package.json"),
|
|
1289
|
+
path5.join(path5.dirname(Bun.main), "node_modules/akanjs/package.json")
|
|
1290
|
+
];
|
|
1291
|
+
try {
|
|
1292
|
+
packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
|
|
1293
|
+
} catch {}
|
|
1294
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
1295
|
+
try {
|
|
1296
|
+
akanPackageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
|
|
1297
|
+
return akanPackageJson;
|
|
1298
|
+
} catch {}
|
|
1299
|
+
}
|
|
1300
|
+
akanPackageJson = {
|
|
1301
|
+
name: "akanjs",
|
|
1302
|
+
version: "0.0.0",
|
|
1303
|
+
description: "akanjs",
|
|
1304
|
+
dependencies: {}
|
|
1305
|
+
};
|
|
1306
|
+
return akanPackageJson;
|
|
1243
1307
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1308
|
+
function mergeImageConfig(config = {}) {
|
|
1309
|
+
return {
|
|
1310
|
+
...DEFAULT_AKAN_IMAGE_CONFIG,
|
|
1311
|
+
...config,
|
|
1312
|
+
deviceSizes: config.deviceSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.deviceSizes,
|
|
1313
|
+
imageSizes: config.imageSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.imageSizes,
|
|
1314
|
+
formats: config.formats ?? DEFAULT_AKAN_IMAGE_CONFIG.formats,
|
|
1315
|
+
qualities: config.qualities ?? DEFAULT_AKAN_IMAGE_CONFIG.qualities,
|
|
1316
|
+
remotePatterns: config.remotePatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.remotePatterns,
|
|
1317
|
+
localPatterns: config.localPatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.localPatterns
|
|
1318
|
+
};
|
|
1247
1319
|
}
|
|
1248
1320
|
|
|
1321
|
+
class AkanLibConfig {
|
|
1322
|
+
lib;
|
|
1323
|
+
externalLibs;
|
|
1324
|
+
trustedDependencies;
|
|
1325
|
+
bin;
|
|
1326
|
+
docker;
|
|
1327
|
+
assets;
|
|
1328
|
+
plugins;
|
|
1329
|
+
constructor(lib, config, plugins = []) {
|
|
1330
|
+
this.lib = lib;
|
|
1331
|
+
this.externalLibs = config?.externalLibs ?? [];
|
|
1332
|
+
const owner = `libs/${lib.name}/akan.config.ts`;
|
|
1333
|
+
this.trustedDependencies = normalizePackageNames(config?.trustedDependencies, owner);
|
|
1334
|
+
this.bin = AkanBin.parse(config?.bin, owner, lib.cwdPath);
|
|
1335
|
+
this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
|
|
1336
|
+
this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
|
|
1337
|
+
this.plugins = plugins;
|
|
1338
|
+
}
|
|
1339
|
+
static async from(lib, { bustImportCache = false } = {}) {
|
|
1340
|
+
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
1341
|
+
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
1342
|
+
const { plugins, ...config } = resolved ?? {};
|
|
1343
|
+
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
//! need to refactor
|
|
1347
|
+
var shiftBuildNum = async (app, delta) => {
|
|
1348
|
+
const { buildNum } = (await AkanAppConfig.from(app)).native;
|
|
1349
|
+
const akanConfigPath = path5.join(app.cwdPath, "akan.config.ts");
|
|
1350
|
+
const akanConfig = fs2.readFileSync(akanConfigPath, "utf8");
|
|
1351
|
+
fs2.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
|
|
1352
|
+
};
|
|
1353
|
+
var increaseBuildNum = async (app) => await shiftBuildNum(app, 1);
|
|
1354
|
+
var decreaseBuildNum = async (app) => await shiftBuildNum(app, -1);
|
|
1249
1355
|
// pkgs/@akanjs/devkit/linter.ts
|
|
1250
1356
|
import { spawn } from "child_process";
|
|
1251
1357
|
import { existsSync, readFileSync } from "fs";
|
|
1252
|
-
import
|
|
1358
|
+
import path6 from "path";
|
|
1253
1359
|
import chalk from "chalk";
|
|
1254
1360
|
var parseBiomeReport = (output) => {
|
|
1255
1361
|
for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
|
|
@@ -1299,20 +1405,29 @@ var asBiomeReport = (candidate) => {
|
|
|
1299
1405
|
}
|
|
1300
1406
|
};
|
|
1301
1407
|
var BIOME_CONFIG_FILES = ["biome.json", "biome.jsonc"];
|
|
1408
|
+
var emptyResult = (filePath) => ({
|
|
1409
|
+
filePath,
|
|
1410
|
+
messages: [],
|
|
1411
|
+
errorCount: 0,
|
|
1412
|
+
warningCount: 0,
|
|
1413
|
+
fixableErrorCount: 0,
|
|
1414
|
+
fixableWarningCount: 0
|
|
1415
|
+
});
|
|
1302
1416
|
|
|
1303
1417
|
class Linter {
|
|
1418
|
+
static biomeBinName = process.platform === "win32" ? "biome.exe" : "biome";
|
|
1304
1419
|
lintRoot;
|
|
1305
1420
|
configPath;
|
|
1306
1421
|
#biomeBin;
|
|
1307
1422
|
constructor(cwdPath) {
|
|
1308
1423
|
this.lintRoot = this.#findBiomeRootPath(cwdPath);
|
|
1309
|
-
this.configPath = Linter.#configPathIn(this.lintRoot) ??
|
|
1310
|
-
const localBiomeBin =
|
|
1424
|
+
this.configPath = Linter.#configPathIn(this.lintRoot) ?? path6.join(this.lintRoot, "biome.json");
|
|
1425
|
+
const localBiomeBin = path6.join(this.lintRoot, "node_modules", ".bin", Linter.biomeBinName);
|
|
1311
1426
|
this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
|
|
1312
1427
|
}
|
|
1313
1428
|
static #configPathIn(dir) {
|
|
1314
1429
|
for (const fileName of BIOME_CONFIG_FILES) {
|
|
1315
|
-
const configPath =
|
|
1430
|
+
const configPath = path6.join(dir, fileName);
|
|
1316
1431
|
if (existsSync(configPath))
|
|
1317
1432
|
return configPath;
|
|
1318
1433
|
}
|
|
@@ -1321,19 +1436,19 @@ class Linter {
|
|
|
1321
1436
|
#findBiomeRootPath(dir) {
|
|
1322
1437
|
if (Linter.#configPathIn(dir))
|
|
1323
1438
|
return dir;
|
|
1324
|
-
const parentDir =
|
|
1439
|
+
const parentDir = path6.dirname(dir);
|
|
1325
1440
|
if (parentDir === dir)
|
|
1326
1441
|
throw new Error(`biome.json not found from ${dir}`);
|
|
1327
1442
|
return this.#findBiomeRootPath(parentDir);
|
|
1328
1443
|
}
|
|
1329
1444
|
#toBiomePath(filePath) {
|
|
1330
|
-
const relativePath =
|
|
1331
|
-
if (!relativePath.startsWith("..") && !
|
|
1445
|
+
const relativePath = path6.relative(this.lintRoot, filePath);
|
|
1446
|
+
if (!relativePath.startsWith("..") && !path6.isAbsolute(relativePath))
|
|
1332
1447
|
return relativePath;
|
|
1333
1448
|
return filePath;
|
|
1334
1449
|
}
|
|
1335
1450
|
#resolveFilePath(filePath) {
|
|
1336
|
-
return
|
|
1451
|
+
return path6.isAbsolute(filePath) ? filePath : path6.join(this.lintRoot, filePath);
|
|
1337
1452
|
}
|
|
1338
1453
|
async#runBiome(args, input) {
|
|
1339
1454
|
return await new Promise((resolve, reject) => {
|
|
@@ -1358,7 +1473,7 @@ class Linter {
|
|
|
1358
1473
|
const diagnosticPath = diagnostic.location?.path;
|
|
1359
1474
|
if (!diagnosticPath)
|
|
1360
1475
|
return fallbackFilePath;
|
|
1361
|
-
return
|
|
1476
|
+
return path6.isAbsolute(diagnosticPath) ? diagnosticPath : path6.join(this.lintRoot, diagnosticPath);
|
|
1362
1477
|
}
|
|
1363
1478
|
#createLintMessage(diagnostic) {
|
|
1364
1479
|
const start = diagnostic.location?.start;
|
|
@@ -1379,14 +1494,7 @@ class Linter {
|
|
|
1379
1494
|
if (diagnostic.severity !== "error" && diagnostic.severity !== "warning")
|
|
1380
1495
|
continue;
|
|
1381
1496
|
const diagnosticFilePath = this.#diagnosticFilePath(diagnostic, filePath);
|
|
1382
|
-
const result = resultsByPath.get(diagnosticFilePath) ??
|
|
1383
|
-
filePath: diagnosticFilePath,
|
|
1384
|
-
messages: [],
|
|
1385
|
-
errorCount: 0,
|
|
1386
|
-
warningCount: 0,
|
|
1387
|
-
fixableErrorCount: 0,
|
|
1388
|
-
fixableWarningCount: 0
|
|
1389
|
-
};
|
|
1497
|
+
const result = resultsByPath.get(diagnosticFilePath) ?? emptyResult(diagnosticFilePath);
|
|
1390
1498
|
const message = this.#createLintMessage(diagnostic);
|
|
1391
1499
|
result.messages.push(message);
|
|
1392
1500
|
if (message.severity === 2)
|
|
@@ -1396,14 +1504,7 @@ class Linter {
|
|
|
1396
1504
|
resultsByPath.set(diagnosticFilePath, result);
|
|
1397
1505
|
}
|
|
1398
1506
|
return [
|
|
1399
|
-
resultsByPath.get(filePath) ??
|
|
1400
|
-
filePath,
|
|
1401
|
-
messages: [],
|
|
1402
|
-
errorCount: 0,
|
|
1403
|
-
warningCount: 0,
|
|
1404
|
-
fixableErrorCount: 0,
|
|
1405
|
-
fixableWarningCount: 0
|
|
1406
|
-
},
|
|
1507
|
+
resultsByPath.get(filePath) ?? emptyResult(filePath),
|
|
1407
1508
|
...[...resultsByPath.entries()].filter(([resultPath]) => resultPath !== filePath).map(([, result]) => result)
|
|
1408
1509
|
];
|
|
1409
1510
|
}
|
|
@@ -1513,12 +1614,7 @@ ${errorText}, ${warningText} found`];
|
|
|
1513
1614
|
warningCount: acc.warningCount + result.warningCount,
|
|
1514
1615
|
fixableErrorCount: acc.fixableErrorCount + result.fixableErrorCount,
|
|
1515
1616
|
fixableWarningCount: acc.fixableWarningCount + result.fixableWarningCount
|
|
1516
|
-
}), {
|
|
1517
|
-
errorCount: 0,
|
|
1518
|
-
warningCount: 0,
|
|
1519
|
-
fixableErrorCount: 0,
|
|
1520
|
-
fixableWarningCount: 0
|
|
1521
|
-
});
|
|
1617
|
+
}), { errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 });
|
|
1522
1618
|
return { results, details, stats };
|
|
1523
1619
|
}
|
|
1524
1620
|
async hasNoLintErrors(filePath) {
|
|
@@ -1576,19 +1672,16 @@ ${errorText}, ${warningText} found`];
|
|
|
1576
1672
|
async getProblematicRules(filePath) {
|
|
1577
1673
|
const { results } = await this.lintFile(filePath);
|
|
1578
1674
|
const ruleCounts = {};
|
|
1579
|
-
results.
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
ruleCounts[message.ruleId] = (ruleCounts[message.ruleId] || 0) + 1;
|
|
1583
|
-
});
|
|
1584
|
-
});
|
|
1675
|
+
for (const message of results.flatMap((result) => result.messages))
|
|
1676
|
+
if (message.ruleId)
|
|
1677
|
+
ruleCounts[message.ruleId] = (ruleCounts[message.ruleId] || 0) + 1;
|
|
1585
1678
|
return ruleCounts;
|
|
1586
1679
|
}
|
|
1587
1680
|
}
|
|
1588
1681
|
|
|
1589
1682
|
// pkgs/@akanjs/devkit/repoIdentity.ts
|
|
1590
1683
|
import { execFileSync } from "child_process";
|
|
1591
|
-
import
|
|
1684
|
+
import path7 from "path";
|
|
1592
1685
|
var resolved = new Map;
|
|
1593
1686
|
var readRemoteName = (workspaceRoot) => {
|
|
1594
1687
|
try {
|
|
@@ -1606,14 +1699,13 @@ var resolveRepoName = (workspaceRoot) => {
|
|
|
1606
1699
|
const cached = resolved.get(workspaceRoot);
|
|
1607
1700
|
if (cached)
|
|
1608
1701
|
return cached;
|
|
1609
|
-
const repoName = readRemoteName(workspaceRoot) ??
|
|
1702
|
+
const repoName = readRemoteName(workspaceRoot) ?? path7.basename(workspaceRoot);
|
|
1610
1703
|
resolved.set(workspaceRoot, repoName);
|
|
1611
1704
|
return repoName;
|
|
1612
1705
|
};
|
|
1613
1706
|
|
|
1614
1707
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
1615
|
-
import
|
|
1616
|
-
import path7 from "path";
|
|
1708
|
+
import path8 from "path";
|
|
1617
1709
|
|
|
1618
1710
|
// pkgs/@akanjs/devkit/workspaceLayout.ts
|
|
1619
1711
|
var appRootAllowedFiles = new Set([
|
|
@@ -1621,7 +1713,6 @@ var appRootAllowedFiles = new Set([
|
|
|
1621
1713
|
"CLAUDE.md",
|
|
1622
1714
|
"akan.app.json",
|
|
1623
1715
|
"akan.config.ts",
|
|
1624
|
-
"capacitor.config.ts",
|
|
1625
1716
|
"client.ts",
|
|
1626
1717
|
"main.ts",
|
|
1627
1718
|
"package.json",
|
|
@@ -1631,12 +1722,10 @@ var appRootAllowedFiles = new Set([
|
|
|
1631
1722
|
]);
|
|
1632
1723
|
var appRootAllowedDirs = new Set([
|
|
1633
1724
|
".akan",
|
|
1634
|
-
"android",
|
|
1635
1725
|
"common",
|
|
1636
1726
|
"env",
|
|
1637
|
-
"ios",
|
|
1638
1727
|
"lib",
|
|
1639
|
-
"
|
|
1728
|
+
"native",
|
|
1640
1729
|
"page",
|
|
1641
1730
|
"plugin",
|
|
1642
1731
|
"private",
|
|
@@ -1665,6 +1754,7 @@ var libRootAllowedDirs = new Set([
|
|
|
1665
1754
|
"common",
|
|
1666
1755
|
"env",
|
|
1667
1756
|
"lib",
|
|
1757
|
+
"native",
|
|
1668
1758
|
"page",
|
|
1669
1759
|
"plugin",
|
|
1670
1760
|
"private",
|
|
@@ -1688,13 +1778,20 @@ var libFacetRootAllowedFiles = new Set([
|
|
|
1688
1778
|
]);
|
|
1689
1779
|
var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
|
|
1690
1780
|
var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
|
|
1781
|
+
var retiredAppRootEntries = new Set([
|
|
1782
|
+
"android",
|
|
1783
|
+
"ios",
|
|
1784
|
+
"mobile",
|
|
1785
|
+
"capacitor.config.ts",
|
|
1786
|
+
"capacitor.config.js",
|
|
1787
|
+
"capacitor.config.json"
|
|
1788
|
+
]);
|
|
1789
|
+
var rootEntryHintOf = (type, name) => type === "app" && retiredAppRootEntries.has(name) ? "a Capacitor-era entry; the native runtime generates its projects under .akan/native/<target>, so delete it" : null;
|
|
1691
1790
|
var isScannedRootEntry = (type, name) => !name.startsWith(".") || rootAllowedDirs[type].has(name);
|
|
1692
1791
|
|
|
1693
1792
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
1694
|
-
var
|
|
1695
|
-
"constant",
|
|
1793
|
+
var serviceFileTypes = [
|
|
1696
1794
|
"dictionary",
|
|
1697
|
-
"document",
|
|
1698
1795
|
"service",
|
|
1699
1796
|
"signal",
|
|
1700
1797
|
"store",
|
|
@@ -1704,7 +1801,9 @@ var databaseFileTypes = [
|
|
|
1704
1801
|
"view",
|
|
1705
1802
|
"zone"
|
|
1706
1803
|
];
|
|
1707
|
-
var
|
|
1804
|
+
var databaseFileTypes = ["constant", "dictionary", "document", ...serviceFileTypes.slice(1)];
|
|
1805
|
+
var uiFileTypes = new Set(["template", "unit", "util", "view", "zone"]);
|
|
1806
|
+
var fileTypeOf = (filename) => databaseFileTypes.find((type) => filename.endsWith(uiFileTypes.has(type) ? `.${type[0].toUpperCase()}${type.slice(1)}.tsx` : `.${type}.ts`));
|
|
1708
1807
|
var internalLibDirs = new Set(["__lib", "__scalar"]);
|
|
1709
1808
|
var moduleNonUiFileTypes = {
|
|
1710
1809
|
database: new Set(["constant", "dictionary", "document", "service", "signal", "store"]),
|
|
@@ -1716,21 +1815,13 @@ var moduleUiFileTypes = {
|
|
|
1716
1815
|
service: new Set(["Util", "Zone"]),
|
|
1717
1816
|
scalar: new Set(["Template", "Unit"])
|
|
1718
1817
|
};
|
|
1719
|
-
var
|
|
1720
|
-
var
|
|
1721
|
-
var isAllowedTestFile = (filename) => testFilePattern.test(filename);
|
|
1722
|
-
var getScanPath = (exec, relativePath) => path7.posix.join(`${exec.type}s`, exec.name, relativePath.split(path7.sep).join("/"));
|
|
1723
|
-
async function clearGeneratedRootCapacitorConfigs(exec) {
|
|
1724
|
-
if (exec.type !== "app")
|
|
1725
|
-
return;
|
|
1726
|
-
await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm2(exec.getPath(filename), { force: true })));
|
|
1727
|
-
}
|
|
1818
|
+
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-06z2dmrr.js")).TypeScriptDependencyScanner.from(exec);
|
|
1819
|
+
var getScanPath = (exec, relativePath) => path8.posix.join(`${exec.type}s`, exec.name, relativePath.split(path8.sep).join("/"));
|
|
1728
1820
|
var getModuleNameFromPath = (kind, modulePath) => {
|
|
1729
|
-
const dirname =
|
|
1821
|
+
const dirname = path8.basename(modulePath);
|
|
1730
1822
|
return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
|
|
1731
1823
|
};
|
|
1732
1824
|
async function assertScanConvention(exec, libRoot) {
|
|
1733
|
-
await clearGeneratedRootCapacitorConfigs(exec);
|
|
1734
1825
|
const violations = [];
|
|
1735
1826
|
const addViolation = (relativePath, reason) => {
|
|
1736
1827
|
violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
|
|
@@ -1738,28 +1829,28 @@ async function assertScanConvention(exec, libRoot) {
|
|
|
1738
1829
|
const allowedRootFiles = rootAllowedFiles[exec.type];
|
|
1739
1830
|
const allowedRootDirs = rootAllowedDirs[exec.type];
|
|
1740
1831
|
const { files, dirs } = await exec.getFilesAndDirs(".");
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1832
|
+
for (const filename of files)
|
|
1833
|
+
if (!allowedRootFiles.has(filename))
|
|
1834
|
+
addViolation(filename, rootEntryHintOf(exec.type, filename) ?? `unsupported ${exec.type} root file`);
|
|
1835
|
+
for (const dirname of dirs)
|
|
1836
|
+
if (!allowedRootDirs.has(dirname))
|
|
1837
|
+
addViolation(dirname, rootEntryHintOf(exec.type, dirname) ?? `unsupported ${exec.type} root folder`);
|
|
1747
1838
|
if (exec.type === "lib")
|
|
1748
1839
|
for (const { relativePath, reason } of await exec.getPageConventionViolations())
|
|
1749
1840
|
addViolation(relativePath, reason);
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1841
|
+
for (const filename of libRoot.files)
|
|
1842
|
+
if (!isAllowedLibFacetRootFile(filename))
|
|
1843
|
+
addViolation(path8.join("lib", filename), "unsupported lib facet root file");
|
|
1844
|
+
for (const dirname of libRoot.dirs)
|
|
1845
|
+
if (dirname.startsWith("__") && !internalLibDirs.has(dirname))
|
|
1846
|
+
addViolation(path8.join("lib", dirname), "unsupported internal lib folder");
|
|
1756
1847
|
const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
|
|
1757
1848
|
const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
|
|
1758
1849
|
const scalarDirs = await exec.readdir("lib/__scalar");
|
|
1759
1850
|
await Promise.all([
|
|
1760
|
-
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database",
|
|
1761
|
-
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service",
|
|
1762
|
-
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar",
|
|
1851
|
+
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path8.join("lib", dirname))),
|
|
1852
|
+
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path8.join("lib", dirname))),
|
|
1853
|
+
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path8.join("lib/__scalar", dirname)))
|
|
1763
1854
|
]);
|
|
1764
1855
|
if (violations.length > 0) {
|
|
1765
1856
|
throw new Error(`[scan-convention]
|
|
@@ -1771,41 +1862,28 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
|
|
|
1771
1862
|
const { files, dirs } = await exec.getFilesAndDirs(modulePath);
|
|
1772
1863
|
const moduleName = getModuleNameFromPath(kind, modulePath);
|
|
1773
1864
|
dirs.forEach((dirname) => {
|
|
1774
|
-
violations.push(`${getScanPath(exec,
|
|
1865
|
+
violations.push(`${getScanPath(exec, path8.join(modulePath, dirname))}: unsupported module folder`);
|
|
1775
1866
|
});
|
|
1776
1867
|
const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
|
|
1777
|
-
|
|
1778
|
-
const
|
|
1779
|
-
if (filename === "index.ts" || filename === "index.tsx" ||
|
|
1780
|
-
|
|
1868
|
+
for (const filename of files) {
|
|
1869
|
+
const scanPath = getScanPath(exec, path8.join(modulePath, filename));
|
|
1870
|
+
if (filename === "index.ts" || filename === "index.tsx" || /\.(test|spec)\.(ts|tsx)$/.test(filename))
|
|
1871
|
+
continue;
|
|
1781
1872
|
if (filename === `${moduleName}.abstract.md`)
|
|
1782
|
-
|
|
1873
|
+
continue;
|
|
1783
1874
|
const uiMatch = filename.match(/^([A-Z][A-Za-z0-9]+)\.([A-Z][A-Za-z0-9]*)\.tsx$/);
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
violations.push(`${getScanPath(exec, filePath)}: module name mismatch: expected '${uiModuleName}', got '${fileModuleName}'`);
|
|
1789
|
-
}
|
|
1790
|
-
if (!moduleUiFileTypes[kind].has(fileType)) {
|
|
1791
|
-
violations.push(`${getScanPath(exec, filePath)}: unsupported ${kind} UI file`);
|
|
1792
|
-
}
|
|
1793
|
-
return;
|
|
1794
|
-
}
|
|
1795
|
-
const nonUiMatch = filename.match(/^([a-z][a-zA-Z0-9]*)\.([a-z][a-z0-9]*)\.ts$/);
|
|
1796
|
-
if (nonUiMatch) {
|
|
1797
|
-
const fileModuleName = nonUiMatch[1];
|
|
1798
|
-
const fileType = nonUiMatch[2];
|
|
1799
|
-
if (fileModuleName !== moduleName) {
|
|
1800
|
-
violations.push(`${getScanPath(exec, filePath)}: module name mismatch: expected '${moduleName}', got '${fileModuleName}'`);
|
|
1801
|
-
}
|
|
1802
|
-
if (!moduleNonUiFileTypes[kind].has(fileType)) {
|
|
1803
|
-
violations.push(`${getScanPath(exec, filePath)}: unsupported ${kind} file`);
|
|
1804
|
-
}
|
|
1805
|
-
return;
|
|
1875
|
+
const match = uiMatch ?? filename.match(/^([a-z][a-zA-Z0-9]*)\.([a-z][a-z0-9]*)\.ts$/);
|
|
1876
|
+
if (!match) {
|
|
1877
|
+
violations.push(`${scanPath}: unsupported module file`);
|
|
1878
|
+
continue;
|
|
1806
1879
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1880
|
+
const [, fileModuleName, fileType] = match;
|
|
1881
|
+
const expectedName = uiMatch ? uiModuleName : moduleName;
|
|
1882
|
+
if (fileModuleName !== expectedName)
|
|
1883
|
+
violations.push(`${scanPath}: module name mismatch: expected '${expectedName}', got '${fileModuleName}'`);
|
|
1884
|
+
if (!(uiMatch ? moduleUiFileTypes : moduleNonUiFileTypes)[kind].has(fileType))
|
|
1885
|
+
violations.push(`${scanPath}: unsupported ${kind}${uiMatch ? " UI" : ""} file`);
|
|
1886
|
+
}
|
|
1809
1887
|
}
|
|
1810
1888
|
|
|
1811
1889
|
class ScanInfo {
|
|
@@ -1853,86 +1931,24 @@ class ScanInfo {
|
|
|
1853
1931
|
} else
|
|
1854
1932
|
databaseDirs.push(name);
|
|
1855
1933
|
});
|
|
1934
|
+
const collect = async (dir, name, kind) => {
|
|
1935
|
+
for (const filename of await exec.readdir(dir)) {
|
|
1936
|
+
const type = fileTypeOf(filename);
|
|
1937
|
+
if (type)
|
|
1938
|
+
files[type][kind]?.push(name);
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1856
1941
|
await Promise.all([
|
|
1857
|
-
...databaseDirs.map(
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
if (filename.endsWith(".constant.ts"))
|
|
1861
|
-
files.constant.databases.push(name);
|
|
1862
|
-
else if (filename.endsWith(".dictionary.ts"))
|
|
1863
|
-
files.dictionary.databases.push(name);
|
|
1864
|
-
else if (filename.endsWith(".document.ts"))
|
|
1865
|
-
files.document.databases.push(name);
|
|
1866
|
-
else if (filename.endsWith(".service.ts"))
|
|
1867
|
-
files.service.databases.push(name);
|
|
1868
|
-
else if (filename.endsWith(".signal.ts"))
|
|
1869
|
-
files.signal.databases.push(name);
|
|
1870
|
-
else if (filename.endsWith(".store.ts"))
|
|
1871
|
-
files.store.databases.push(name);
|
|
1872
|
-
else if (filename.endsWith(".Template.tsx"))
|
|
1873
|
-
files.template.databases.push(name);
|
|
1874
|
-
else if (filename.endsWith(".Unit.tsx"))
|
|
1875
|
-
files.unit.databases.push(name);
|
|
1876
|
-
else if (filename.endsWith(".Util.tsx"))
|
|
1877
|
-
files.util.databases.push(name);
|
|
1878
|
-
else if (filename.endsWith(".View.tsx"))
|
|
1879
|
-
files.view.databases.push(name);
|
|
1880
|
-
else if (filename.endsWith(".Zone.tsx"))
|
|
1881
|
-
files.zone.databases.push(name);
|
|
1882
|
-
});
|
|
1883
|
-
}),
|
|
1884
|
-
...serviceDirs.map(async (dirname) => {
|
|
1885
|
-
const name = dirname.slice(1);
|
|
1886
|
-
const filenames = await exec.readdir(path7.join("lib", dirname));
|
|
1887
|
-
filenames.forEach((filename) => {
|
|
1888
|
-
if (filename.endsWith(".dictionary.ts"))
|
|
1889
|
-
files.dictionary.services.push(name);
|
|
1890
|
-
else if (filename.endsWith(".service.ts"))
|
|
1891
|
-
files.service.services.push(name);
|
|
1892
|
-
else if (filename.endsWith(".signal.ts"))
|
|
1893
|
-
files.signal.services.push(name);
|
|
1894
|
-
else if (filename.endsWith(".store.ts"))
|
|
1895
|
-
files.store.services.push(name);
|
|
1896
|
-
else if (filename.endsWith(".Template.tsx"))
|
|
1897
|
-
files.template.services.push(name);
|
|
1898
|
-
else if (filename.endsWith(".Unit.tsx"))
|
|
1899
|
-
files.unit.services.push(name);
|
|
1900
|
-
else if (filename.endsWith(".Util.tsx"))
|
|
1901
|
-
files.util.services.push(name);
|
|
1902
|
-
else if (filename.endsWith(".View.tsx"))
|
|
1903
|
-
files.view.services.push(name);
|
|
1904
|
-
else if (filename.endsWith(".Zone.tsx"))
|
|
1905
|
-
files.zone.services.push(name);
|
|
1906
|
-
});
|
|
1907
|
-
}),
|
|
1908
|
-
...scalarDirs.map(async (name) => {
|
|
1909
|
-
const filenames = await exec.readdir(path7.join("lib/__scalar", name));
|
|
1910
|
-
filenames.forEach((filename) => {
|
|
1911
|
-
if (filename.endsWith(".constant.ts"))
|
|
1912
|
-
files.constant.scalars.push(name);
|
|
1913
|
-
else if (filename.endsWith(".dictionary.ts"))
|
|
1914
|
-
files.dictionary.scalars.push(name);
|
|
1915
|
-
else if (filename.endsWith(".document.ts"))
|
|
1916
|
-
files.document.scalars.push(name);
|
|
1917
|
-
else if (filename.endsWith(".Template.tsx"))
|
|
1918
|
-
files.template.scalars.push(name);
|
|
1919
|
-
else if (filename.endsWith(".Unit.tsx"))
|
|
1920
|
-
files.unit.scalars.push(name);
|
|
1921
|
-
else if (filename.endsWith(".Util.tsx"))
|
|
1922
|
-
files.util.scalars.push(name);
|
|
1923
|
-
else if (filename.endsWith(".View.tsx"))
|
|
1924
|
-
files.view.scalars.push(name);
|
|
1925
|
-
else if (filename.endsWith(".Zone.tsx"))
|
|
1926
|
-
files.zone.scalars.push(name);
|
|
1927
|
-
});
|
|
1928
|
-
})
|
|
1942
|
+
...databaseDirs.map((name) => collect(path8.join("lib", name), name, "databases")),
|
|
1943
|
+
...serviceDirs.map((dirname) => collect(path8.join("lib", dirname), dirname.slice(1), "services")),
|
|
1944
|
+
...scalarDirs.map((name) => collect(path8.join("lib/__scalar", name), name, "scalars"))
|
|
1929
1945
|
]);
|
|
1930
1946
|
const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
|
|
1931
1947
|
const common = {
|
|
1932
1948
|
name: exec.name,
|
|
1933
1949
|
type: exec.type,
|
|
1934
1950
|
repoName: exec.workspace.repoName,
|
|
1935
|
-
serveDomain: WorkspaceExecutor.getBaseDevEnv(
|
|
1951
|
+
serveDomain: WorkspaceExecutor.getBaseDevEnv(path8.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
|
|
1936
1952
|
files,
|
|
1937
1953
|
libDeps,
|
|
1938
1954
|
pkgDeps,
|
|
@@ -1946,31 +1962,14 @@ class ScanInfo {
|
|
|
1946
1962
|
constructor(scanResult) {
|
|
1947
1963
|
this.name = scanResult.name;
|
|
1948
1964
|
this.scanResult = scanResult;
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
const
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
this.file[key].databases.add(modelName);
|
|
1958
|
-
});
|
|
1959
|
-
services?.forEach((serviceName) => {
|
|
1960
|
-
const service = this.service.get(serviceName) ?? new Set;
|
|
1961
|
-
service.add(key);
|
|
1962
|
-
this.service.set(serviceName, service);
|
|
1963
|
-
this.file[key].all.add(serviceName);
|
|
1964
|
-
this.file[key].services.add(serviceName);
|
|
1965
|
-
});
|
|
1966
|
-
scalars?.forEach((scalarName) => {
|
|
1967
|
-
const scalar = this.scalar.get(scalarName) ?? new Set;
|
|
1968
|
-
scalar.add(key);
|
|
1969
|
-
this.scalar.set(scalarName, scalar);
|
|
1970
|
-
this.file[key].all.add(scalarName);
|
|
1971
|
-
this.file[key].scalars.add(scalarName);
|
|
1972
|
-
});
|
|
1973
|
-
});
|
|
1965
|
+
const modulesOf = { databases: this.database, services: this.service, scalars: this.scalar };
|
|
1966
|
+
for (const [key, groups] of Object.entries(scanResult.files))
|
|
1967
|
+
for (const kind of ["databases", "services", "scalars"])
|
|
1968
|
+
for (const name of groups[kind] ?? []) {
|
|
1969
|
+
modulesOf[kind].set(name, (modulesOf[kind].get(name) ?? new Set).add(key));
|
|
1970
|
+
this.file[key].all.add(name);
|
|
1971
|
+
this.file[key][kind].add(name);
|
|
1972
|
+
}
|
|
1974
1973
|
}
|
|
1975
1974
|
getScanResult() {
|
|
1976
1975
|
return this.scanResult;
|
|
@@ -1984,6 +1983,28 @@ class ScanInfo {
|
|
|
1984
1983
|
getScalarModules() {
|
|
1985
1984
|
return [...this.scalar.keys()];
|
|
1986
1985
|
}
|
|
1986
|
+
#sortedLibs = null;
|
|
1987
|
+
sortedLibs(libDeps) {
|
|
1988
|
+
if (this.#sortedLibs)
|
|
1989
|
+
return this.#sortedLibs;
|
|
1990
|
+
const libIndices = LibInfo.getSortedLibIndices();
|
|
1991
|
+
this.#sortedLibs = libDeps.sort((libNameA, libNameB) => {
|
|
1992
|
+
const indexA = libIndices.get(libNameA);
|
|
1993
|
+
const indexB = libIndices.get(libNameB);
|
|
1994
|
+
if (indexA === undefined || indexB === undefined)
|
|
1995
|
+
throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
|
|
1996
|
+
return indexA - indexB;
|
|
1997
|
+
});
|
|
1998
|
+
return this.#sortedLibs;
|
|
1999
|
+
}
|
|
2000
|
+
sortedLibInfos(libDeps) {
|
|
2001
|
+
return new Map(this.sortedLibs(libDeps).map((libName) => {
|
|
2002
|
+
const libInfo = LibInfo.libInfos.get(libName);
|
|
2003
|
+
if (!libInfo)
|
|
2004
|
+
throw new Error(`LibInfo not found: ${libName}`);
|
|
2005
|
+
return [libName, libInfo];
|
|
2006
|
+
}));
|
|
2007
|
+
}
|
|
1987
2008
|
}
|
|
1988
2009
|
var isAkanFrameworkDependency = (dep) => dep === "akanjs" || dep.startsWith("akanjs/");
|
|
1989
2010
|
|
|
@@ -2033,30 +2054,11 @@ class AppInfo extends ScanInfo {
|
|
|
2033
2054
|
}));
|
|
2034
2055
|
return [...libSet];
|
|
2035
2056
|
}
|
|
2036
|
-
#sortedLibs = null;
|
|
2037
|
-
#getSortedLibs() {
|
|
2038
|
-
if (this.#sortedLibs)
|
|
2039
|
-
return this.#sortedLibs;
|
|
2040
|
-
const libIndices = LibInfo.getSortedLibIndices();
|
|
2041
|
-
this.#sortedLibs = this.libDeps.sort((libNameA, libNameB) => {
|
|
2042
|
-
const indexA = libIndices.get(libNameA);
|
|
2043
|
-
const indexB = libIndices.get(libNameB);
|
|
2044
|
-
if (indexA === undefined || indexB === undefined)
|
|
2045
|
-
throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
|
|
2046
|
-
return indexA - indexB;
|
|
2047
|
-
});
|
|
2048
|
-
return this.#sortedLibs;
|
|
2049
|
-
}
|
|
2050
2057
|
getLibs() {
|
|
2051
|
-
return this
|
|
2058
|
+
return this.sortedLibs(this.libDeps);
|
|
2052
2059
|
}
|
|
2053
2060
|
getLibInfos() {
|
|
2054
|
-
return
|
|
2055
|
-
const libInfo = LibInfo.libInfos.get(libName);
|
|
2056
|
-
if (!libInfo)
|
|
2057
|
-
throw new Error(`LibInfo not found: ${libName}`);
|
|
2058
|
-
return [libName, libInfo];
|
|
2059
|
-
}));
|
|
2061
|
+
return this.sortedLibInfos(this.libDeps);
|
|
2060
2062
|
}
|
|
2061
2063
|
}
|
|
2062
2064
|
|
|
@@ -2096,38 +2098,18 @@ class LibInfo extends ScanInfo {
|
|
|
2096
2098
|
getScanResult() {
|
|
2097
2099
|
return this.scanResult;
|
|
2098
2100
|
}
|
|
2099
|
-
#sortedLibs = null;
|
|
2100
|
-
#getSortedLibs() {
|
|
2101
|
-
if (this.#sortedLibs)
|
|
2102
|
-
return this.#sortedLibs;
|
|
2103
|
-
const libs = LibInfo.getSortedLibIndices();
|
|
2104
|
-
this.#sortedLibs = this.scanResult.libDeps.sort((libNameA, libNameB) => {
|
|
2105
|
-
const indexA = libs.get(libNameA);
|
|
2106
|
-
const indexB = libs.get(libNameB);
|
|
2107
|
-
if (indexA === undefined || indexB === undefined)
|
|
2108
|
-
throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
|
|
2109
|
-
return indexA - indexB;
|
|
2110
|
-
});
|
|
2111
|
-
return this.#sortedLibs;
|
|
2112
|
-
}
|
|
2113
2101
|
getLibs() {
|
|
2114
|
-
return this
|
|
2102
|
+
return this.sortedLibs(this.scanResult.libDeps);
|
|
2115
2103
|
}
|
|
2116
2104
|
getLibInfo(libName) {
|
|
2117
2105
|
if (!this.getScanResult().libDeps.includes(libName))
|
|
2118
2106
|
return;
|
|
2119
|
-
|
|
2120
|
-
if (!libSet.has(libName))
|
|
2107
|
+
if (!this.getLibs().includes(libName))
|
|
2121
2108
|
throw new Error(`LibInfo is invalid: ${libName}`);
|
|
2122
2109
|
return LibInfo.libInfos.get(libName);
|
|
2123
2110
|
}
|
|
2124
2111
|
getLibInfos() {
|
|
2125
|
-
return
|
|
2126
|
-
const libInfo = LibInfo.libInfos.get(libName);
|
|
2127
|
-
if (!libInfo)
|
|
2128
|
-
throw new Error(`LibInfo not found: ${libName}`);
|
|
2129
|
-
return [libName, libInfo];
|
|
2130
|
-
}));
|
|
2112
|
+
return this.sortedLibInfos(this.scanResult.libDeps);
|
|
2131
2113
|
}
|
|
2132
2114
|
}
|
|
2133
2115
|
|
|
@@ -2139,18 +2121,14 @@ class PkgInfo {
|
|
|
2139
2121
|
const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
|
|
2140
2122
|
const scanner = await createDependencyScanner(exec);
|
|
2141
2123
|
const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
|
|
2142
|
-
const
|
|
2124
|
+
const workspacePathOf = (resolve) => resolve.replace(/^\.\//, "");
|
|
2125
|
+
const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path) => tsconfig.compilerOptions.paths?.[path]?.some((resolve) => workspacePathOf(resolve).startsWith("pkgs/"))).map((path) => path.replace("/*", "")));
|
|
2143
2126
|
const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
|
|
2144
2127
|
const pkgDeps = [...pkgPathDepSet].map((path) => {
|
|
2145
2128
|
const pathSplitLength = path.split("/").length;
|
|
2146
|
-
return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
|
|
2129
|
+
return workspacePathOf(tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
|
|
2147
2130
|
}).filter((pkg) => pkg !== exec.name);
|
|
2148
|
-
|
|
2149
|
-
name: exec.name,
|
|
2150
|
-
pkgDeps,
|
|
2151
|
-
dependencies: [...npmDepSet]
|
|
2152
|
-
};
|
|
2153
|
-
return pkgScanResult;
|
|
2131
|
+
return { name: exec.name, pkgDeps, dependencies: [...npmDepSet] };
|
|
2154
2132
|
}
|
|
2155
2133
|
static #pkgInfos = new Map;
|
|
2156
2134
|
static async fromExecutor(exec, options = {}) {
|
|
@@ -2188,17 +2166,9 @@ class WorkspaceInfo {
|
|
|
2188
2166
|
return existingWorkspaceInfo;
|
|
2189
2167
|
const [appNames, libNames, pkgNames] = await Promise.all([exec.getApps(), exec.getLibs(), exec.getPkgs()]);
|
|
2190
2168
|
const [appInfos, libInfos, pkgInfos] = await Promise.all([
|
|
2191
|
-
Promise.all(appNames.map(async (appName) =>
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
})),
|
|
2195
|
-
Promise.all(libNames.map(async (libName) => {
|
|
2196
|
-
const lib = LibExecutor.from(exec, libName);
|
|
2197
|
-
return await lib.scan();
|
|
2198
|
-
})),
|
|
2199
|
-
Promise.all(pkgNames.map(async (pkgName) => {
|
|
2200
|
-
return await PkgExecutor.from(exec, pkgName).scan();
|
|
2201
|
-
}))
|
|
2169
|
+
Promise.all(appNames.map(async (appName) => await AppExecutor.from(exec, appName).scan())),
|
|
2170
|
+
Promise.all(libNames.map(async (libName) => await LibExecutor.from(exec, libName).scan())),
|
|
2171
|
+
Promise.all(pkgNames.map(async (pkgName) => await PkgExecutor.from(exec, pkgName).scan()))
|
|
2202
2172
|
]);
|
|
2203
2173
|
const workspaceInfo = new WorkspaceInfo(new Map(appInfos.map((app) => [app.exec.name, app])), new Map(libInfos.map((lib) => [lib.exec.name, lib])), new Map(pkgInfos.map((pkg) => [pkg.exec.name, pkg])));
|
|
2204
2174
|
WorkspaceInfo.#workspaceInfos.set(exec.name, workspaceInfo);
|
|
@@ -2207,40 +2177,9 @@ class WorkspaceInfo {
|
|
|
2207
2177
|
}
|
|
2208
2178
|
|
|
2209
2179
|
// pkgs/@akanjs/devkit/executors.ts
|
|
2210
|
-
var staticTemplateFileExtensions = new Set(
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
".cjs",
|
|
2214
|
-
".css",
|
|
2215
|
-
".eot",
|
|
2216
|
-
".gif",
|
|
2217
|
-
".html",
|
|
2218
|
-
".ico",
|
|
2219
|
-
".jpeg",
|
|
2220
|
-
".jpg",
|
|
2221
|
-
".js",
|
|
2222
|
-
".json",
|
|
2223
|
-
".map",
|
|
2224
|
-
".md",
|
|
2225
|
-
".mjs",
|
|
2226
|
-
".mp3",
|
|
2227
|
-
".mp4",
|
|
2228
|
-
".ogg",
|
|
2229
|
-
".otf",
|
|
2230
|
-
".pdf",
|
|
2231
|
-
".png",
|
|
2232
|
-
".svg",
|
|
2233
|
-
".ttf",
|
|
2234
|
-
".txt",
|
|
2235
|
-
".wasm",
|
|
2236
|
-
".wav",
|
|
2237
|
-
".webm",
|
|
2238
|
-
".webp",
|
|
2239
|
-
".woff",
|
|
2240
|
-
".woff2",
|
|
2241
|
-
".xml"
|
|
2242
|
-
]);
|
|
2243
|
-
var formatCommandArg = (value) => /^[\w@%+=:,./-]+$/.test(value) ? value : JSON.stringify(value);
|
|
2180
|
+
var staticTemplateFileExtensions = new Set(".avif .bmp .cjs .css .eot .gif .html .ico .jpeg .jpg .js .json .map .md .mjs .mp3 .mp4 .ogg .otf .pdf .png .svg".split(" ").concat(".ttf .txt .wasm .wav .webm .webp .woff .woff2 .xml".split(" ")));
|
|
2181
|
+
var plainCommandArgPattern = process.platform === "win32" ? /^[\w@%+=:,./\\-]+$/ : /^[\w@%+=:,./-]+$/;
|
|
2182
|
+
var formatCommandArg = (value) => plainCommandArgPattern.test(value) ? value : JSON.stringify(value);
|
|
2244
2183
|
var formatCommandForDisplay = (command, args = []) => [command, ...args].map(formatCommandArg).join(" ");
|
|
2245
2184
|
|
|
2246
2185
|
class CommandExecutionError extends Error {
|
|
@@ -2314,6 +2253,10 @@ var parseEnvFile = (envPath) => {
|
|
|
2314
2253
|
}
|
|
2315
2254
|
return env;
|
|
2316
2255
|
};
|
|
2256
|
+
var withCapitalizedDict = (dict = {}) => ({
|
|
2257
|
+
...dict,
|
|
2258
|
+
...Object.fromEntries(Object.entries(dict).map(([key, value]) => [capitalize(key), capitalize(value)]))
|
|
2259
|
+
});
|
|
2317
2260
|
|
|
2318
2261
|
class Executor {
|
|
2319
2262
|
static verbose = false;
|
|
@@ -2340,150 +2283,57 @@ class Executor {
|
|
|
2340
2283
|
#stderr(data) {
|
|
2341
2284
|
Logger2.raw(chalk2.red(data.toString()));
|
|
2342
2285
|
}
|
|
2343
|
-
|
|
2344
|
-
const cwd = options.cwd?.toString() ?? this.cwdPath;
|
|
2345
|
-
const proc = exec(command, { cwd: this.cwdPath, ...options });
|
|
2286
|
+
#settle(proc, target, { onClose = false, keepLogs = false, redStderr = false } = {}) {
|
|
2346
2287
|
let stdout = "";
|
|
2347
2288
|
let stderr = "";
|
|
2348
2289
|
proc.stdout?.on("data", (data) => {
|
|
2349
2290
|
stdout += data.toString();
|
|
2291
|
+
if (keepLogs)
|
|
2292
|
+
this.logs.push(data.toString());
|
|
2350
2293
|
this.#stdout(data);
|
|
2351
2294
|
});
|
|
2352
2295
|
proc.stderr?.on("data", (data) => {
|
|
2353
2296
|
stderr += data.toString();
|
|
2354
|
-
|
|
2297
|
+
if (keepLogs)
|
|
2298
|
+
this.logs.push(data.toString());
|
|
2299
|
+
if (redStderr)
|
|
2300
|
+
this.#stderr(data);
|
|
2301
|
+
else
|
|
2302
|
+
this.#stdout(data);
|
|
2355
2303
|
});
|
|
2356
2304
|
return new Promise((resolve, reject) => {
|
|
2357
|
-
proc.on("error", (
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
code: null,
|
|
2362
|
-
signal: null,
|
|
2363
|
-
stdout,
|
|
2364
|
-
stderr,
|
|
2365
|
-
cause: error
|
|
2366
|
-
}));
|
|
2367
|
-
});
|
|
2368
|
-
proc.on("exit", (code, signal) => {
|
|
2369
|
-
if (code || signal)
|
|
2370
|
-
reject(new CommandExecutionError({
|
|
2371
|
-
command,
|
|
2372
|
-
cwd,
|
|
2373
|
-
code,
|
|
2374
|
-
signal,
|
|
2375
|
-
stdout,
|
|
2376
|
-
stderr
|
|
2377
|
-
}));
|
|
2305
|
+
proc.on("error", (cause) => reject(new CommandExecutionError({ ...target, code: null, signal: null, stdout, stderr, cause })));
|
|
2306
|
+
proc.on(onClose ? "close" : "exit", (code, signal) => {
|
|
2307
|
+
if ((onClose ? code !== 0 : code) || signal)
|
|
2308
|
+
reject(new CommandExecutionError({ ...target, code, signal, stdout, stderr }));
|
|
2378
2309
|
else
|
|
2379
|
-
resolve({ code, signal });
|
|
2310
|
+
resolve({ code, signal, stdout });
|
|
2380
2311
|
});
|
|
2381
2312
|
});
|
|
2382
2313
|
}
|
|
2314
|
+
exec(command, options = {}) {
|
|
2315
|
+
const target = { command, cwd: options.cwd?.toString() ?? this.cwdPath };
|
|
2316
|
+
const proc = exec(command, { cwd: this.cwdPath, ...options });
|
|
2317
|
+
return this.#settle(proc, target).then(({ code, signal }) => ({ code, signal }));
|
|
2318
|
+
}
|
|
2383
2319
|
spawn(command, args = [], options = {}) {
|
|
2384
|
-
const
|
|
2385
|
-
const proc = spawn2(command, args, {
|
|
2386
|
-
|
|
2387
|
-
...options
|
|
2388
|
-
});
|
|
2389
|
-
let stdout = "";
|
|
2390
|
-
let stderr = "";
|
|
2391
|
-
proc.stdout?.on("data", (data) => {
|
|
2392
|
-
stdout += data.toString();
|
|
2393
|
-
this.logs.push(data.toString());
|
|
2394
|
-
this.#stdout(data);
|
|
2395
|
-
});
|
|
2396
|
-
proc.stderr?.on("data", (data) => {
|
|
2397
|
-
stderr += data.toString();
|
|
2398
|
-
this.logs.push(data.toString());
|
|
2399
|
-
this.#stdout(data);
|
|
2400
|
-
});
|
|
2401
|
-
return new Promise((resolve, reject) => {
|
|
2402
|
-
proc.on("error", (error) => {
|
|
2403
|
-
reject(new CommandExecutionError({
|
|
2404
|
-
command,
|
|
2405
|
-
args,
|
|
2406
|
-
cwd,
|
|
2407
|
-
code: null,
|
|
2408
|
-
signal: null,
|
|
2409
|
-
stdout,
|
|
2410
|
-
stderr,
|
|
2411
|
-
cause: error
|
|
2412
|
-
}));
|
|
2413
|
-
});
|
|
2414
|
-
proc.on("close", (code, signal) => {
|
|
2415
|
-
if (code !== 0 || signal)
|
|
2416
|
-
reject(new CommandExecutionError({
|
|
2417
|
-
command,
|
|
2418
|
-
args,
|
|
2419
|
-
cwd,
|
|
2420
|
-
code,
|
|
2421
|
-
signal,
|
|
2422
|
-
stdout,
|
|
2423
|
-
stderr
|
|
2424
|
-
}));
|
|
2425
|
-
else
|
|
2426
|
-
resolve(stdout);
|
|
2427
|
-
});
|
|
2428
|
-
});
|
|
2320
|
+
const target = { command, args, cwd: options.cwd?.toString() ?? this.cwdPath };
|
|
2321
|
+
const proc = spawn2(command, args, { cwd: this.cwdPath, ...options });
|
|
2322
|
+
return this.#settle(proc, target, { onClose: true, keepLogs: true }).then(({ stdout }) => stdout);
|
|
2429
2323
|
}
|
|
2430
2324
|
spawnSync(command, args = [], options = {}) {
|
|
2431
|
-
|
|
2432
|
-
cwd: this.cwdPath,
|
|
2433
|
-
...options
|
|
2434
|
-
});
|
|
2435
|
-
return proc;
|
|
2325
|
+
return spawn2(command, args, { cwd: this.cwdPath, ...options });
|
|
2436
2326
|
}
|
|
2437
2327
|
fork(modulePath, args = [], options = {}) {
|
|
2438
|
-
const
|
|
2439
|
-
const proc = fork(modulePath, args, {
|
|
2440
|
-
|
|
2441
|
-
...options
|
|
2442
|
-
});
|
|
2443
|
-
let stdout = "";
|
|
2444
|
-
let stderr = "";
|
|
2445
|
-
proc.stdout?.on("data", (data) => {
|
|
2446
|
-
stdout += data.toString();
|
|
2447
|
-
this.#stdout(data);
|
|
2448
|
-
});
|
|
2449
|
-
proc.stderr?.on("data", (data) => {
|
|
2450
|
-
stderr += data.toString();
|
|
2451
|
-
this.#stderr(data);
|
|
2452
|
-
});
|
|
2453
|
-
return new Promise((resolve, reject) => {
|
|
2454
|
-
proc.on("error", (error) => {
|
|
2455
|
-
reject(new CommandExecutionError({
|
|
2456
|
-
command: modulePath,
|
|
2457
|
-
args,
|
|
2458
|
-
cwd,
|
|
2459
|
-
code: null,
|
|
2460
|
-
signal: null,
|
|
2461
|
-
stdout,
|
|
2462
|
-
stderr,
|
|
2463
|
-
cause: error
|
|
2464
|
-
}));
|
|
2465
|
-
});
|
|
2466
|
-
proc.on("exit", (code, signal) => {
|
|
2467
|
-
if (code || signal)
|
|
2468
|
-
reject(new CommandExecutionError({
|
|
2469
|
-
command: modulePath,
|
|
2470
|
-
args,
|
|
2471
|
-
cwd,
|
|
2472
|
-
code,
|
|
2473
|
-
signal,
|
|
2474
|
-
stdout,
|
|
2475
|
-
stderr
|
|
2476
|
-
}));
|
|
2477
|
-
else
|
|
2478
|
-
resolve({ code, signal });
|
|
2479
|
-
});
|
|
2480
|
-
});
|
|
2328
|
+
const target = { command: modulePath, args, cwd: options.cwd?.toString() ?? this.cwdPath };
|
|
2329
|
+
const proc = fork(modulePath, args, { cwd: this.cwdPath, ...options });
|
|
2330
|
+
return this.#settle(proc, target, { redStderr: true }).then(({ code, signal }) => ({ code, signal }));
|
|
2481
2331
|
}
|
|
2482
2332
|
getPath(filePath) {
|
|
2483
|
-
if (
|
|
2333
|
+
if (path9.isAbsolute(filePath))
|
|
2484
2334
|
return filePath;
|
|
2485
2335
|
if (filePath.startsWith("."))
|
|
2486
|
-
return
|
|
2336
|
+
return path9.join(this.cwdPath, filePath);
|
|
2487
2337
|
const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
|
|
2488
2338
|
const targetParts = filePath.split(/[\\/]/).filter(Boolean);
|
|
2489
2339
|
let overlapLength = 0;
|
|
@@ -2497,12 +2347,12 @@ class Executor {
|
|
|
2497
2347
|
if (isOverlap)
|
|
2498
2348
|
overlapLength = i;
|
|
2499
2349
|
}
|
|
2500
|
-
return
|
|
2350
|
+
return path9.join(this.cwdPath, ...targetParts.slice(overlapLength));
|
|
2501
2351
|
}
|
|
2502
2352
|
async mkdir(dirPath) {
|
|
2503
2353
|
const writePath = this.getPath(dirPath);
|
|
2504
|
-
if (!await
|
|
2505
|
-
await
|
|
2354
|
+
if (!await FileSys2.dirExists(writePath))
|
|
2355
|
+
await mkdir2(writePath, { recursive: true });
|
|
2506
2356
|
this.logger.verbose(`Make directory ${writePath}`);
|
|
2507
2357
|
return this;
|
|
2508
2358
|
}
|
|
@@ -2521,50 +2371,48 @@ class Executor {
|
|
|
2521
2371
|
}
|
|
2522
2372
|
async getFilesAndDirs(dirPath) {
|
|
2523
2373
|
const fullDirPath = this.getPath(dirPath);
|
|
2524
|
-
const
|
|
2525
|
-
const files = Array.from(
|
|
2526
|
-
const
|
|
2527
|
-
const allEntries = Array.from(dirGlob.scanSync({ cwd: fullDirPath, onlyFiles: false }));
|
|
2374
|
+
const glob = new Bun.Glob("*");
|
|
2375
|
+
const files = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: true }));
|
|
2376
|
+
const allEntries = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: false }));
|
|
2528
2377
|
const dirs = allEntries.filter((entry) => !files.includes(entry));
|
|
2529
2378
|
return { files, dirs };
|
|
2530
2379
|
}
|
|
2531
2380
|
async exists(filePath) {
|
|
2532
|
-
|
|
2533
|
-
return await FileSys.exists(readPath);
|
|
2381
|
+
return await FileSys2.exists(this.getPath(filePath));
|
|
2534
2382
|
}
|
|
2535
2383
|
async remove(filePath) {
|
|
2536
2384
|
const readPath = this.getPath(filePath);
|
|
2537
|
-
if (await
|
|
2538
|
-
await
|
|
2385
|
+
if (await FileSys2.fileExists(readPath))
|
|
2386
|
+
await FileSys2.delete(readPath);
|
|
2539
2387
|
this.logger.verbose(`Remove file ${readPath}`);
|
|
2540
2388
|
return this;
|
|
2541
2389
|
}
|
|
2542
2390
|
async removeDir(dirPath) {
|
|
2543
|
-
const readPath =
|
|
2544
|
-
if (await
|
|
2391
|
+
const readPath = path9.join(this.getPath(dirPath), ".");
|
|
2392
|
+
if (await FileSys2.entryExists(readPath))
|
|
2545
2393
|
await rm3(readPath, { recursive: true, force: true });
|
|
2546
2394
|
this.logger.verbose(`Remove directory ${readPath}`);
|
|
2547
2395
|
return this;
|
|
2548
2396
|
}
|
|
2549
2397
|
async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
|
|
2550
2398
|
const writePath = this.getPath(filePath);
|
|
2551
|
-
const dir =
|
|
2552
|
-
if (!await
|
|
2553
|
-
await
|
|
2399
|
+
const dir = path9.dirname(writePath);
|
|
2400
|
+
if (!await FileSys2.dirExists(dir))
|
|
2401
|
+
await mkdir2(dir, { recursive: true });
|
|
2554
2402
|
let contentStr = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}
|
|
2555
2403
|
`;
|
|
2556
|
-
if (await
|
|
2557
|
-
const currentContent = await
|
|
2404
|
+
if (await FileSys2.fileExists(writePath)) {
|
|
2405
|
+
const currentContent = await FileSys2.readText(writePath);
|
|
2558
2406
|
if (currentContent === contentStr || !overwrite) {
|
|
2559
2407
|
this.logger.verbose(`File ${writePath} is unchanged`);
|
|
2560
2408
|
contentStr = currentContent;
|
|
2561
2409
|
} else {
|
|
2562
|
-
await
|
|
2410
|
+
await FileSys2.writeText(writePath, contentStr);
|
|
2563
2411
|
if (Logger2.isVerbose())
|
|
2564
2412
|
this.logger.rawLog(chalk2.yellow(`File Update: ${filePath}`));
|
|
2565
2413
|
}
|
|
2566
2414
|
} else {
|
|
2567
|
-
await
|
|
2415
|
+
await FileSys2.writeText(writePath, contentStr);
|
|
2568
2416
|
if (!silent)
|
|
2569
2417
|
this.logger.rawLog(chalk2.green(`File Create: ${filePath}`));
|
|
2570
2418
|
}
|
|
@@ -2574,27 +2422,25 @@ class Executor {
|
|
|
2574
2422
|
await this.writeFile(filePath, content);
|
|
2575
2423
|
}
|
|
2576
2424
|
async getLocalFile(targetPath) {
|
|
2577
|
-
const filePath =
|
|
2425
|
+
const filePath = path9.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
|
|
2578
2426
|
const content = await this.readFile(filePath);
|
|
2579
2427
|
return { filePath, content };
|
|
2580
2428
|
}
|
|
2581
2429
|
async readFile(filePath) {
|
|
2582
|
-
|
|
2583
|
-
return await FileSys.readText(readPath);
|
|
2430
|
+
return await FileSys2.readText(this.getPath(filePath));
|
|
2584
2431
|
}
|
|
2585
2432
|
async readJson(filePath) {
|
|
2586
|
-
|
|
2587
|
-
return await FileSys.readJson(readPath);
|
|
2433
|
+
return await FileSys2.readJson(this.getPath(filePath));
|
|
2588
2434
|
}
|
|
2589
2435
|
async cp(srcPath, destPath, { dereference = false } = {}) {
|
|
2590
2436
|
const src = this.getPath(srcPath);
|
|
2591
2437
|
const dest = this.getPath(destPath);
|
|
2592
|
-
if (!await
|
|
2438
|
+
if (!await FileSys2.exists(src))
|
|
2593
2439
|
return;
|
|
2594
2440
|
const isDirectory = (await stat2(src)).isDirectory();
|
|
2595
|
-
if (!await
|
|
2596
|
-
await
|
|
2597
|
-
if (dereference)
|
|
2441
|
+
if (!await FileSys2.exists(dest) && isDirectory)
|
|
2442
|
+
await mkdir2(dest, { recursive: true });
|
|
2443
|
+
if (dereference || process.platform === "win32")
|
|
2598
2444
|
await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
|
|
2599
2445
|
else
|
|
2600
2446
|
await $`cp -r ${src}${isDirectory ? "/." : ""} ${dest}`;
|
|
@@ -2618,19 +2464,10 @@ class Executor {
|
|
|
2618
2464
|
async getTsConfig(pathname = "tsconfig.json", { refresh } = {}) {
|
|
2619
2465
|
if (this.#tsconfig && !refresh)
|
|
2620
2466
|
return this.#tsconfig;
|
|
2621
|
-
|
|
2467
|
+
let tsconfig = await this.readJson(pathname);
|
|
2622
2468
|
if (tsconfig.extends) {
|
|
2623
|
-
const
|
|
2624
|
-
|
|
2625
|
-
...extendsTsconfig,
|
|
2626
|
-
...tsconfig,
|
|
2627
|
-
compilerOptions: {
|
|
2628
|
-
...extendsTsconfig.compilerOptions,
|
|
2629
|
-
...tsconfig.compilerOptions
|
|
2630
|
-
}
|
|
2631
|
-
};
|
|
2632
|
-
this.#tsconfig = result;
|
|
2633
|
-
return result;
|
|
2469
|
+
const base = await this.getTsConfig(tsconfig.extends, { refresh });
|
|
2470
|
+
tsconfig = { ...base, ...tsconfig, compilerOptions: { ...base.compilerOptions, ...tsconfig.compilerOptions } };
|
|
2634
2471
|
}
|
|
2635
2472
|
this.#tsconfig = tsconfig;
|
|
2636
2473
|
return tsconfig;
|
|
@@ -2666,31 +2503,29 @@ class Executor {
|
|
|
2666
2503
|
scanInfo,
|
|
2667
2504
|
overwrite = true
|
|
2668
2505
|
}, dict = {}, options = {}) {
|
|
2506
|
+
const convertPath = (target) => Object.entries(dict).reduce((converted, [key, value]) => converted.replace(new RegExp(`__${key}__`, "g"), value), target);
|
|
2669
2507
|
if (targetPath.endsWith(".ts") || targetPath.endsWith(".tsx")) {
|
|
2670
2508
|
const getContent = await import(templatePath);
|
|
2671
2509
|
const result = await getContent.default(scanInfo ?? null, dict, options);
|
|
2672
2510
|
if (result === null)
|
|
2673
2511
|
return null;
|
|
2674
|
-
const filename = typeof result === "object" ? result.filename :
|
|
2512
|
+
const filename = typeof result === "object" ? result.filename : path9.basename(targetPath).replace(".js", ".ts");
|
|
2675
2513
|
const content = typeof result === "object" ? result.content : result;
|
|
2676
|
-
const
|
|
2677
|
-
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), `${dirname}/${filename}`);
|
|
2514
|
+
const convertedTargetPath = convertPath(`${path9.dirname(targetPath)}/${filename}`);
|
|
2678
2515
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2679
2516
|
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
2680
2517
|
} else if (targetPath.endsWith(".template")) {
|
|
2681
|
-
const content = await
|
|
2682
|
-
const convertedTargetPath =
|
|
2518
|
+
const content = await FileSys2.readText(templatePath);
|
|
2519
|
+
const convertedTargetPath = convertPath(targetPath.slice(0, -9));
|
|
2683
2520
|
const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
|
|
2684
2521
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2685
|
-
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
} else if (staticTemplateFileExtensions.has(path8.extname(targetPath).toLowerCase())) {
|
|
2689
|
-
const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
|
|
2522
|
+
return this.writeFile(convertedTargetPath, convertedContent, { overwrite });
|
|
2523
|
+
} else if (staticTemplateFileExtensions.has(path9.extname(targetPath).toLowerCase())) {
|
|
2524
|
+
const convertedTargetPath = convertPath(targetPath);
|
|
2690
2525
|
const writePath = this.getPath(convertedTargetPath);
|
|
2691
|
-
const dirname =
|
|
2692
|
-
if (!await
|
|
2693
|
-
await
|
|
2526
|
+
const dirname = path9.dirname(writePath);
|
|
2527
|
+
if (!await FileSys2.dirExists(dirname))
|
|
2528
|
+
await mkdir2(dirname, { recursive: true });
|
|
2694
2529
|
await copyFile(templatePath, writePath);
|
|
2695
2530
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2696
2531
|
return { filePath: writePath, content: "" };
|
|
@@ -2707,12 +2542,10 @@ class Executor {
|
|
|
2707
2542
|
}) {
|
|
2708
2543
|
const templateRoot = await this.#resolveTemplateRoot();
|
|
2709
2544
|
const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
|
|
2710
|
-
|
|
2711
|
-
if ((await stat2(prefixTemplatePath)).isFile()) {
|
|
2712
|
-
const filename = path8.basename(prefixTemplatePath);
|
|
2545
|
+
if ((await stat2(templatePath)).isFile()) {
|
|
2713
2546
|
const fileContent = await this.#applyTemplateFile({
|
|
2714
|
-
templatePath
|
|
2715
|
-
targetPath:
|
|
2547
|
+
templatePath,
|
|
2548
|
+
targetPath: path9.join(basePath, path9.basename(templatePath)),
|
|
2716
2549
|
scanInfo,
|
|
2717
2550
|
overwrite
|
|
2718
2551
|
}, dict, options);
|
|
@@ -2720,19 +2553,19 @@ class Executor {
|
|
|
2720
2553
|
} else {
|
|
2721
2554
|
const subdirs = await readDirEntries(templatePath);
|
|
2722
2555
|
const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
|
|
2723
|
-
const subpath =
|
|
2556
|
+
const subpath = path9.join(templatePath, subdir);
|
|
2724
2557
|
if ((await stat2(subpath)).isFile()) {
|
|
2725
2558
|
const fileContent = await this.#applyTemplateFile({
|
|
2726
2559
|
templatePath: subpath,
|
|
2727
|
-
targetPath:
|
|
2560
|
+
targetPath: path9.join(basePath, subdir),
|
|
2728
2561
|
scanInfo,
|
|
2729
2562
|
overwrite
|
|
2730
2563
|
}, dict, options);
|
|
2731
2564
|
return fileContent ? [fileContent] : [];
|
|
2732
2565
|
} else
|
|
2733
2566
|
return await this._applyTemplate({
|
|
2734
|
-
basePath:
|
|
2735
|
-
template:
|
|
2567
|
+
basePath: path9.join(basePath, subdir),
|
|
2568
|
+
template: path9.join(template, subdir),
|
|
2736
2569
|
scanInfo,
|
|
2737
2570
|
dict,
|
|
2738
2571
|
overwrite,
|
|
@@ -2744,21 +2577,17 @@ class Executor {
|
|
|
2744
2577
|
}
|
|
2745
2578
|
async#resolveTemplateRoot() {
|
|
2746
2579
|
const candidates = [
|
|
2747
|
-
|
|
2748
|
-
|
|
2580
|
+
path9.resolve(getDirname2(import.meta.url), "templates"),
|
|
2581
|
+
path9.resolve(getDirname2(import.meta.url), "../cli/templates")
|
|
2749
2582
|
];
|
|
2750
2583
|
for (const candidate of candidates) {
|
|
2751
|
-
if (await
|
|
2584
|
+
if (await FileSys2.dirExists(candidate))
|
|
2752
2585
|
return candidate;
|
|
2753
2586
|
}
|
|
2754
2587
|
return candidates[0];
|
|
2755
2588
|
}
|
|
2756
2589
|
async applyTemplate(options) {
|
|
2757
|
-
const
|
|
2758
|
-
...options.dict ?? {},
|
|
2759
|
-
...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
|
|
2760
|
-
};
|
|
2761
|
-
const fileContents = await this._applyTemplate({ ...options, dict });
|
|
2590
|
+
const fileContents = await this._applyTemplate({ ...options, dict: withCapitalizedDict(options.dict) });
|
|
2762
2591
|
await this.#formatAppliedTemplate(fileContents);
|
|
2763
2592
|
return fileContents;
|
|
2764
2593
|
}
|
|
@@ -2775,7 +2604,7 @@ class Executor {
|
|
|
2775
2604
|
}
|
|
2776
2605
|
}
|
|
2777
2606
|
async getTypeChecker() {
|
|
2778
|
-
const { TypeChecker } = await import("./typeChecker-
|
|
2607
|
+
const { TypeChecker } = await import("./typeChecker-px49mezc.js");
|
|
2779
2608
|
this.typeChecker ??= new TypeChecker(this);
|
|
2780
2609
|
return this.typeChecker;
|
|
2781
2610
|
}
|
|
@@ -2817,11 +2646,11 @@ class Executor {
|
|
|
2817
2646
|
async#resolveTypecheckWorkerEntry() {
|
|
2818
2647
|
const dirname = getDirname2(import.meta.url);
|
|
2819
2648
|
const candidates = [
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2649
|
+
path9.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2650
|
+
path9.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2651
|
+
path9.join(dirname, "typecheck/typecheck.proc.ts"),
|
|
2652
|
+
path9.join(dirname, "typecheck.proc.js"),
|
|
2653
|
+
path9.join(dirname, "typecheck.proc.ts")
|
|
2825
2654
|
];
|
|
2826
2655
|
for (const candidate of candidates)
|
|
2827
2656
|
if (await Bun.file(candidate).exists())
|
|
@@ -2853,12 +2682,11 @@ class WorkspaceExecutor extends Executor {
|
|
|
2853
2682
|
this.workspaceRoot = workspaceRoot;
|
|
2854
2683
|
this.repoName = repoName;
|
|
2855
2684
|
}
|
|
2856
|
-
static #execs = new Map;
|
|
2857
2685
|
static fromRoot({
|
|
2858
2686
|
workspaceRoot = process.cwd(),
|
|
2859
2687
|
repoName = resolveRepoName(workspaceRoot)
|
|
2860
2688
|
} = {}) {
|
|
2861
|
-
return
|
|
2689
|
+
return new WorkspaceExecutor({ workspaceRoot, repoName });
|
|
2862
2690
|
}
|
|
2863
2691
|
static getBaseDevEnv(envPath) {
|
|
2864
2692
|
const sourceEnv = envPath ? { ...process.env, ...parseEnvFile(envPath) } : process.env;
|
|
@@ -2897,13 +2725,9 @@ class WorkspaceExecutor extends Executor {
|
|
|
2897
2725
|
return await WorkspaceInfo.fromExecutor(this);
|
|
2898
2726
|
}
|
|
2899
2727
|
async getApps() {
|
|
2900
|
-
if (!await FileSys.dirExists(`${this.workspaceRoot}/apps`))
|
|
2901
|
-
return [];
|
|
2902
2728
|
return await this.#getDirHasFile(`${this.workspaceRoot}/apps`, "akan.config.ts");
|
|
2903
2729
|
}
|
|
2904
2730
|
async getLibs() {
|
|
2905
|
-
if (!await FileSys.dirExists(`${this.workspaceRoot}/libs`))
|
|
2906
|
-
return [];
|
|
2907
2731
|
return await this.#getDirHasFile(`${this.workspaceRoot}/libs`, "akan.config.ts");
|
|
2908
2732
|
}
|
|
2909
2733
|
async getSyss() {
|
|
@@ -2911,8 +2735,6 @@ class WorkspaceExecutor extends Executor {
|
|
|
2911
2735
|
return [appNames, libNames];
|
|
2912
2736
|
}
|
|
2913
2737
|
async getPkgs() {
|
|
2914
|
-
if (!await FileSys.dirExists(`${this.workspaceRoot}/pkgs`))
|
|
2915
|
-
return [];
|
|
2916
2738
|
return await this.#getDirHasFile(`${this.workspaceRoot}/pkgs`, "package.json");
|
|
2917
2739
|
}
|
|
2918
2740
|
async getExecs() {
|
|
@@ -2948,7 +2770,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
2948
2770
|
await Promise.all(dirs.map(async (dir) => {
|
|
2949
2771
|
if (dir.includes("_") || AVOID_DIRS.includes(dir))
|
|
2950
2772
|
return;
|
|
2951
|
-
const dirPath =
|
|
2773
|
+
const dirPath = path9.join(dirname, dir);
|
|
2952
2774
|
if ((await stat2(dirPath)).isDirectory()) {
|
|
2953
2775
|
results.push(`${prefix}${dir}`);
|
|
2954
2776
|
if (maxDepth > 0)
|
|
@@ -2977,15 +2799,17 @@ class WorkspaceExecutor extends Executor {
|
|
|
2977
2799
|
return stdout.split("\x00").filter((file) => !!file).sort();
|
|
2978
2800
|
}
|
|
2979
2801
|
async#getDirHasFile(basePath, targetFilename) {
|
|
2980
|
-
|
|
2802
|
+
if (!await FileSys2.dirExists(basePath))
|
|
2803
|
+
return [];
|
|
2804
|
+
const AVOID_DIRS = ["node_modules", "dist", "public", "webkit", "local"];
|
|
2981
2805
|
const getDirs = async (dirname, maxDepth = 3, results = [], prefix = "") => {
|
|
2982
2806
|
const dirs = await this.readdir(dirname);
|
|
2983
2807
|
await Promise.all(dirs.map(async (dir) => {
|
|
2984
2808
|
if (AVOID_DIRS.includes(dir))
|
|
2985
2809
|
return;
|
|
2986
|
-
const dirPath =
|
|
2987
|
-
if (await
|
|
2988
|
-
const hasTargetFile = await
|
|
2810
|
+
const dirPath = path9.join(dirname, dir);
|
|
2811
|
+
if (await FileSys2.dirExists(dirPath)) {
|
|
2812
|
+
const hasTargetFile = await FileSys2.fileExists(path9.join(dirPath, targetFilename));
|
|
2989
2813
|
if (hasTargetFile)
|
|
2990
2814
|
results.push(`${prefix}${dir}`);
|
|
2991
2815
|
if (maxDepth > 0)
|
|
@@ -2994,39 +2818,25 @@ class WorkspaceExecutor extends Executor {
|
|
|
2994
2818
|
}));
|
|
2995
2819
|
return results;
|
|
2996
2820
|
};
|
|
2997
|
-
return await getDirs(basePath);
|
|
2821
|
+
return (await getDirs(basePath)).sort((a, b) => a.localeCompare(b));
|
|
2998
2822
|
}
|
|
2999
|
-
async
|
|
2823
|
+
async#fromEverySys(read) {
|
|
3000
2824
|
const [appNames, libNames] = await this.getSyss();
|
|
3001
|
-
const
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
2825
|
+
const fromApps = await Promise.all(appNames.map((appName) => read(AppExecutor.from(this, appName))));
|
|
2826
|
+
const fromLibs = await Promise.all(libNames.map((libName) => read(LibExecutor.from(this, libName))));
|
|
2827
|
+
return [...fromApps.flat(), ...fromLibs.flat()];
|
|
2828
|
+
}
|
|
2829
|
+
async getScalarConstantFiles() {
|
|
2830
|
+
return await this.#fromEverySys((sys) => sys.getScalarConstantFiles());
|
|
3006
2831
|
}
|
|
3007
2832
|
async getConstantFiles() {
|
|
3008
|
-
|
|
3009
|
-
const moduleConstantExampleFiles = [
|
|
3010
|
-
...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getConstantFiles()))).flat(),
|
|
3011
|
-
...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getConstantFiles()))).flat()
|
|
3012
|
-
];
|
|
3013
|
-
return moduleConstantExampleFiles;
|
|
2833
|
+
return await this.#fromEverySys((sys) => sys.getConstantFiles());
|
|
3014
2834
|
}
|
|
3015
2835
|
async getDictionaryFiles() {
|
|
3016
|
-
|
|
3017
|
-
const moduleDictionaryExampleFiles = [
|
|
3018
|
-
...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getDictionaryFiles()))).flat(),
|
|
3019
|
-
...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getDictionaryFiles()))).flat()
|
|
3020
|
-
];
|
|
3021
|
-
return moduleDictionaryExampleFiles;
|
|
2836
|
+
return await this.#fromEverySys((sys) => sys.getDictionaryFiles());
|
|
3022
2837
|
}
|
|
3023
2838
|
async getViewFiles() {
|
|
3024
|
-
|
|
3025
|
-
const viewExampleFiles = [
|
|
3026
|
-
...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getViewsSourceCode()))).flat(),
|
|
3027
|
-
...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getViewsSourceCode()))).flat()
|
|
3028
|
-
];
|
|
3029
|
-
return viewExampleFiles;
|
|
2839
|
+
return await this.#fromEverySys((sys) => sys.getViewsSourceCode());
|
|
3030
2840
|
}
|
|
3031
2841
|
}
|
|
3032
2842
|
var scanFacetDirs = ["ui", "webkit", "srvkit", "common", "plugin"];
|
|
@@ -3059,7 +2869,7 @@ class SysExecutor extends Executor {
|
|
|
3059
2869
|
return [];
|
|
3060
2870
|
const violations = [];
|
|
3061
2871
|
for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
|
|
3062
|
-
const relativePath = entry.split(
|
|
2872
|
+
const relativePath = entry.split(path9.sep).join("/");
|
|
3063
2873
|
const reason = getPageSourceFileViolation(relativePath);
|
|
3064
2874
|
if (reason)
|
|
3065
2875
|
violations.push({ relativePath: `page/${relativePath}`, reason });
|
|
@@ -3090,24 +2900,11 @@ class SysExecutor extends Executor {
|
|
|
3090
2900
|
scanInfo,
|
|
3091
2901
|
options: { exec: this, facet }
|
|
3092
2902
|
})),
|
|
3093
|
-
...
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
scanInfo,
|
|
3097
|
-
|
|
3098
|
-
})),
|
|
3099
|
-
...scanInfo.getServiceModules().map((model) => this._applyTemplate({
|
|
3100
|
-
basePath: `lib/_${model}`,
|
|
3101
|
-
template: "moduleRoot",
|
|
3102
|
-
scanInfo,
|
|
3103
|
-
dict: { model, Model: capitalize(model) }
|
|
3104
|
-
})),
|
|
3105
|
-
...scanInfo.getScalarModules().map((model) => this._applyTemplate({
|
|
3106
|
-
basePath: `lib/__scalar/${model}`,
|
|
3107
|
-
template: "moduleRoot",
|
|
3108
|
-
scanInfo,
|
|
3109
|
-
dict: { model, Model: capitalize(model) }
|
|
3110
|
-
}))
|
|
2903
|
+
...[
|
|
2904
|
+
...scanInfo.getDatabaseModules().map((model) => [model, `lib/${model}`]),
|
|
2905
|
+
...scanInfo.getServiceModules().map((model) => [model, `lib/_${model}`]),
|
|
2906
|
+
...scanInfo.getScalarModules().map((model) => [model, `lib/__scalar/${model}`])
|
|
2907
|
+
].map(([model, basePath]) => this._applyTemplate({ basePath, template: "moduleRoot", scanInfo, dict: { model, Model: capitalize(model) } }))
|
|
3111
2908
|
];
|
|
3112
2909
|
}
|
|
3113
2910
|
async scan({
|
|
@@ -3169,45 +2966,43 @@ class SysExecutor extends Executor {
|
|
|
3169
2966
|
await this.setPackageJson(libPkgJsonWithDeps);
|
|
3170
2967
|
}
|
|
3171
2968
|
async getLocalFile(targetPath) {
|
|
3172
|
-
const filePath =
|
|
2969
|
+
const filePath = path9.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
|
|
3173
2970
|
const content = await this.workspace.readFile(filePath);
|
|
3174
2971
|
return { filePath, content };
|
|
3175
2972
|
}
|
|
3176
2973
|
async getDatabaseModules() {
|
|
3177
|
-
|
|
3178
|
-
return databaseModules;
|
|
2974
|
+
return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${name}.constant.ts`));
|
|
3179
2975
|
}
|
|
3180
2976
|
async getServiceModules() {
|
|
3181
|
-
|
|
3182
|
-
return serviceModules;
|
|
2977
|
+
return (await this.readdir("lib")).filter((name) => name.startsWith("_") && !name.startsWith("__")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${name.slice(1)}.service.ts`));
|
|
3183
2978
|
}
|
|
3184
2979
|
async getScalarModules() {
|
|
3185
|
-
|
|
3186
|
-
|
|
2980
|
+
return (await this.readdir("lib/__scalar")).filter((name) => !name.startsWith("_")).filter((name) => existsSync2(`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`));
|
|
2981
|
+
}
|
|
2982
|
+
async#getComponentModules(role) {
|
|
2983
|
+
return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${capitalize(name)}.${role}.tsx`));
|
|
2984
|
+
}
|
|
2985
|
+
async#getComponentSources(role) {
|
|
2986
|
+
const modules = await this.#getComponentModules(role);
|
|
2987
|
+
return Promise.all(modules.map((name) => this.getLocalFile(`lib/${name}/${capitalize(name)}.${role}.tsx`)));
|
|
3187
2988
|
}
|
|
3188
2989
|
async getViewComponents() {
|
|
3189
|
-
|
|
3190
|
-
return viewComponents;
|
|
2990
|
+
return await this.#getComponentModules("View");
|
|
3191
2991
|
}
|
|
3192
2992
|
async getUnitComponents() {
|
|
3193
|
-
|
|
3194
|
-
return unitComponents;
|
|
2993
|
+
return await this.#getComponentModules("Unit");
|
|
3195
2994
|
}
|
|
3196
2995
|
async getTemplateComponents() {
|
|
3197
|
-
|
|
3198
|
-
return templateComponents;
|
|
2996
|
+
return await this.#getComponentModules("Template");
|
|
3199
2997
|
}
|
|
3200
2998
|
async getViewsSourceCode() {
|
|
3201
|
-
|
|
3202
|
-
return Promise.all(viewComponents.map((viewComponent) => this.getLocalFile(`lib/${viewComponent}/${capitalize(viewComponent)}.View.tsx`)));
|
|
2999
|
+
return await this.#getComponentSources("View");
|
|
3203
3000
|
}
|
|
3204
3001
|
async getUnitsSourceCode() {
|
|
3205
|
-
|
|
3206
|
-
return Promise.all(unitComponents.map((unitComponent) => this.getLocalFile(`lib/${unitComponent}/${capitalize(unitComponent)}.Unit.tsx`)));
|
|
3002
|
+
return await this.#getComponentSources("Unit");
|
|
3207
3003
|
}
|
|
3208
3004
|
async getTemplatesSourceCode() {
|
|
3209
|
-
|
|
3210
|
-
return Promise.all(templateComponents.map((templateComponent) => this.getLocalFile(`lib/${templateComponent}/${capitalize(templateComponent)}.Template.tsx`)));
|
|
3005
|
+
return await this.#getComponentSources("Template");
|
|
3211
3006
|
}
|
|
3212
3007
|
async getScalarConstantFiles() {
|
|
3213
3008
|
const scalarModules = await this.getScalarModules();
|
|
@@ -3215,7 +3010,7 @@ class SysExecutor extends Executor {
|
|
|
3215
3010
|
}
|
|
3216
3011
|
async getScalarDictionaryFiles() {
|
|
3217
3012
|
const scalarModules = await this.getScalarModules();
|
|
3218
|
-
return Promise.all(scalarModules.map((scalarModule) => this.getLocalFile(`lib/${scalarModule}/${scalarModule}.dictionary.ts`)));
|
|
3013
|
+
return Promise.all(scalarModules.map((scalarModule) => this.getLocalFile(`lib/__scalar/${scalarModule}/${scalarModule}.dictionary.ts`)));
|
|
3219
3014
|
}
|
|
3220
3015
|
async getConstantFiles() {
|
|
3221
3016
|
const modules = await this.getModules();
|
|
@@ -3237,16 +3032,8 @@ class SysExecutor extends Executor {
|
|
|
3237
3032
|
return Promise.all(modules.map((module) => this.getLocalFile(`lib/${module}/${module}.dictionary.ts`)));
|
|
3238
3033
|
}
|
|
3239
3034
|
async applyTemplate(options) {
|
|
3240
|
-
const dict = {
|
|
3241
|
-
...options.dict ?? {},
|
|
3242
|
-
...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
|
|
3243
|
-
};
|
|
3244
3035
|
const scanInfo = await this.scan();
|
|
3245
|
-
const fileContents = await this._applyTemplate({
|
|
3246
|
-
...options,
|
|
3247
|
-
scanInfo,
|
|
3248
|
-
dict
|
|
3249
|
-
});
|
|
3036
|
+
const fileContents = await this._applyTemplate({ ...options, scanInfo, dict: withCapitalizedDict(options.dict) });
|
|
3250
3037
|
await this.scan();
|
|
3251
3038
|
return fileContents;
|
|
3252
3039
|
}
|
|
@@ -3255,7 +3042,7 @@ class SysExecutor extends Executor {
|
|
|
3255
3042
|
class AppExecutor extends SysExecutor {
|
|
3256
3043
|
static #routeSourceValidator = null;
|
|
3257
3044
|
static async#getRouteSourceValidator() {
|
|
3258
|
-
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-
|
|
3045
|
+
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-7ycnfz0e.js")).RouteSourceValidator;
|
|
3259
3046
|
return AppExecutor.#routeSourceValidator;
|
|
3260
3047
|
}
|
|
3261
3048
|
dist;
|
|
@@ -3264,15 +3051,20 @@ class AppExecutor extends SysExecutor {
|
|
|
3264
3051
|
super({ workspace, name, type: "app" });
|
|
3265
3052
|
this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/apps/${name}`);
|
|
3266
3053
|
}
|
|
3267
|
-
static #execs = new Map;
|
|
3268
3054
|
static from(executor, name) {
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3055
|
+
return new AppExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
|
|
3056
|
+
}
|
|
3057
|
+
static #keptOnStart = [path9.join(".akan", "native"), path9.join(".akan", "cache", "bin")];
|
|
3058
|
+
async#removeDevOutput(dir = ".akan") {
|
|
3059
|
+
const entries = await readDirEntries(this.getPath(dir)).catch(() => []);
|
|
3060
|
+
await Promise.all(entries.map(async (name) => {
|
|
3061
|
+
const entry = path9.join(dir, name);
|
|
3062
|
+
if (AppExecutor.#keptOnStart.includes(entry))
|
|
3063
|
+
return;
|
|
3064
|
+
if (AppExecutor.#keptOnStart.some((kept) => kept.startsWith(`${entry}${path9.sep}`)))
|
|
3065
|
+
return await this.#removeDevOutput(entry);
|
|
3066
|
+
await this.removeDir(entry);
|
|
3067
|
+
}));
|
|
3276
3068
|
}
|
|
3277
3069
|
getEnv() {
|
|
3278
3070
|
return WorkspaceExecutor.getBaseDevEnv().env;
|
|
@@ -3288,10 +3080,8 @@ class AppExecutor extends SysExecutor {
|
|
|
3288
3080
|
return basePort + appIndex + portOffset;
|
|
3289
3081
|
}
|
|
3290
3082
|
getCommandEnv(env = {}) {
|
|
3291
|
-
const
|
|
3292
|
-
const
|
|
3293
|
-
const PORT = (basePort + portOffset).toString();
|
|
3294
|
-
const AKAN_PUBLIC_SERVER_PORT = portOffset ? (8282 + portOffset).toString() : undefined;
|
|
3083
|
+
const { portOffset } = WorkspaceExecutor.getBaseDevEnv();
|
|
3084
|
+
const PORT = (8282 + portOffset).toString();
|
|
3295
3085
|
return {
|
|
3296
3086
|
...process.env,
|
|
3297
3087
|
AKAN_PUBLIC_APP_NAME: this.name,
|
|
@@ -3299,18 +3089,24 @@ class AppExecutor extends SysExecutor {
|
|
|
3299
3089
|
NODE_NO_WARNINGS: "1",
|
|
3300
3090
|
PORT,
|
|
3301
3091
|
AKAN_PUBLIC_CLIENT_PORT: PORT,
|
|
3302
|
-
...
|
|
3092
|
+
...portOffset ? { AKAN_PUBLIC_SERVER_PORT: PORT } : {},
|
|
3303
3093
|
...env
|
|
3304
3094
|
};
|
|
3305
3095
|
}
|
|
3096
|
+
async getDatabaseModeEnv() {
|
|
3097
|
+
const akanConfig = await this.getConfig();
|
|
3098
|
+
return {
|
|
3099
|
+
AKAN_DATABASE_MODE: akanConfig.resolveDatabaseMode(),
|
|
3100
|
+
AKAN_DATABASE_MODES: akanConfig.database.modes.join(",")
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3306
3103
|
async prepareCommand(type) {
|
|
3307
3104
|
const akanConfig = await this.getConfig();
|
|
3308
|
-
const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
|
|
3309
3105
|
const routeEnv = {
|
|
3310
3106
|
AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
|
|
3311
3107
|
AKAN_PUBLIC_API_PREFIX: akanConfig.api.prefix,
|
|
3312
3108
|
AKAN_PUBLIC_WS_PREFIX: akanConfig.api.websocketPrefix,
|
|
3313
|
-
|
|
3109
|
+
...await this.getDatabaseModeEnv()
|
|
3314
3110
|
};
|
|
3315
3111
|
Object.assign(process.env, routeEnv);
|
|
3316
3112
|
if (type === "build") {
|
|
@@ -3323,7 +3119,7 @@ class AppExecutor extends SysExecutor {
|
|
|
3323
3119
|
...akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []
|
|
3324
3120
|
]);
|
|
3325
3121
|
} else {
|
|
3326
|
-
await this
|
|
3122
|
+
await this.#removeDevOutput();
|
|
3327
3123
|
if (!akanConfig.web.ssr || !akanConfig.web.csr)
|
|
3328
3124
|
this.logger.verbose(`akan.config.ts disables web.${!akanConfig.web.ssr ? "ssr" : "csr"}; \`akan start\` still serves it, \`akan build\` will not`);
|
|
3329
3125
|
}
|
|
@@ -3339,6 +3135,8 @@ class AppExecutor extends SysExecutor {
|
|
|
3339
3135
|
const buildEnv = { ...env };
|
|
3340
3136
|
delete buildEnv.AKAN_PUBLIC_CLIENT_PORT;
|
|
3341
3137
|
delete buildEnv.AKAN_PUBLIC_SERVER_PORT;
|
|
3138
|
+
delete buildEnv.AKAN_PUBLIC_OPERATION_MODE;
|
|
3139
|
+
delete process.env.AKAN_PUBLIC_OPERATION_MODE;
|
|
3342
3140
|
Object.assign(process.env, buildEnv);
|
|
3343
3141
|
}
|
|
3344
3142
|
return { env };
|
|
@@ -3348,16 +3146,7 @@ class AppExecutor extends SysExecutor {
|
|
|
3348
3146
|
if (this.#publicEnv)
|
|
3349
3147
|
return this.#publicEnv;
|
|
3350
3148
|
const searchPatterns = [...patterns, "AKAN_PUBLIC_*"];
|
|
3351
|
-
const regexes = searchPatterns.map((pattern) => {
|
|
3352
|
-
let body = "";
|
|
3353
|
-
for (const ch of pattern) {
|
|
3354
|
-
if (ch === "*")
|
|
3355
|
-
body += ".*";
|
|
3356
|
-
else
|
|
3357
|
-
body += ch.replace(/[.+^${}()|[\]\\?]/g, "\\$&");
|
|
3358
|
-
}
|
|
3359
|
-
return new RegExp(`^${body}$`);
|
|
3360
|
-
});
|
|
3149
|
+
const regexes = searchPatterns.map((pattern) => new RegExp(`^${pattern.split("*").map((part) => part.replace(/[.+^${}()|[\]\\?]/g, "\\$&")).join(".*")}$`));
|
|
3361
3150
|
const publicEnv = {};
|
|
3362
3151
|
for (const [k, v] of Object.entries(process.env)) {
|
|
3363
3152
|
if (typeof v !== "string")
|
|
@@ -3386,19 +3175,23 @@ class AppExecutor extends SysExecutor {
|
|
|
3386
3175
|
const owners = new Map;
|
|
3387
3176
|
const devOnlyKeys = new Set;
|
|
3388
3177
|
const devOnlyDirs = [];
|
|
3389
|
-
|
|
3390
|
-
|
|
3178
|
+
const pageRoots = await this.getPageRoots();
|
|
3179
|
+
const libKeyPrefixes = pageRoots.map((root) => root.keyPrefix).filter(Boolean);
|
|
3180
|
+
for (const root of pageRoots) {
|
|
3181
|
+
if (!await FileSys2.dirExists(root.dir))
|
|
3391
3182
|
continue;
|
|
3392
3183
|
for await (const rel of glob.scan({
|
|
3393
3184
|
cwd: root.dir,
|
|
3394
3185
|
absolute: false,
|
|
3395
3186
|
onlyFiles: true
|
|
3396
3187
|
})) {
|
|
3397
|
-
const segments = rel.split(
|
|
3188
|
+
const segments = rel.split(path9.sep);
|
|
3398
3189
|
if (segments.some((s) => s === "node_modules"))
|
|
3399
3190
|
continue;
|
|
3400
3191
|
const posix = `${root.keyPrefix}${segments.join("/")}`;
|
|
3401
|
-
|
|
3192
|
+
if (!root.keyPrefix && libKeyPrefixes.some((prefix) => posix.startsWith(prefix)))
|
|
3193
|
+
continue;
|
|
3194
|
+
const absPath = path9.join(root.dir, ...segments);
|
|
3402
3195
|
validatePageSourceFile(posix, { filePath: absPath });
|
|
3403
3196
|
if (!isRouteSourceFile(posix))
|
|
3404
3197
|
continue;
|
|
@@ -3462,7 +3255,7 @@ class AppExecutor extends SysExecutor {
|
|
|
3462
3255
|
const entries = await readDirEntries(libsDir).catch(() => []);
|
|
3463
3256
|
for (const entry of entries.sort()) {
|
|
3464
3257
|
const dir = `${libsDir}/${entry}`;
|
|
3465
|
-
if (!await
|
|
3258
|
+
if (!await FileSys2.dirExists(dir))
|
|
3466
3259
|
continue;
|
|
3467
3260
|
roots.push({ dir, realDir: await realpath(dir), keyPrefix: `${parent}${AppExecutor.#pageLibsDir}/${entry}/` });
|
|
3468
3261
|
}
|
|
@@ -3487,7 +3280,8 @@ class AppExecutor extends SysExecutor {
|
|
|
3487
3280
|
return entries.map((entry) => `${parent}${AppExecutor.#pageLibsDir}/${entry}/`);
|
|
3488
3281
|
}))).flat();
|
|
3489
3282
|
const wanted = parents.flatMap((parent) => libs.map((lib) => `${parent}${AppExecutor.#pageLibsDir}/(${lib})/`));
|
|
3490
|
-
|
|
3283
|
+
const isLink = async (key) => (await lstat(`${this.cwdPath}/page/${key.slice(0, -1)}`).catch(() => null))?.isSymbolicLink() ?? false;
|
|
3284
|
+
if (linked.sort().join(",") === wanted.sort().join(",") && (await Promise.all(linked.map(isLink))).every(Boolean))
|
|
3491
3285
|
return false;
|
|
3492
3286
|
await Promise.all(parents.map((parent) => this.removeDir(`${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`)));
|
|
3493
3287
|
for (const parent of parents) {
|
|
@@ -3503,7 +3297,7 @@ class AppExecutor extends SysExecutor {
|
|
|
3503
3297
|
async#resolvePageLibs(syncPageLibs, libDeps) {
|
|
3504
3298
|
if (!syncPageLibs)
|
|
3505
3299
|
return [];
|
|
3506
|
-
const hasPageDir = async (lib) => await
|
|
3300
|
+
const hasPageDir = async (lib) => await FileSys2.dirExists(`${this.workspace.workspaceRoot}/libs/${lib}/page`);
|
|
3507
3301
|
if (syncPageLibs === true) {
|
|
3508
3302
|
const libs = [];
|
|
3509
3303
|
for (const lib of libDeps)
|
|
@@ -3538,12 +3332,12 @@ class AppExecutor extends SysExecutor {
|
|
|
3538
3332
|
static async#linkLibAsset(targetPath, linkPath) {
|
|
3539
3333
|
const isWindows = process.platform === "win32";
|
|
3540
3334
|
try {
|
|
3541
|
-
const target = isWindows ? targetPath :
|
|
3335
|
+
const target = isWindows ? targetPath : path9.relative(path9.dirname(linkPath), targetPath);
|
|
3542
3336
|
await symlink(target, linkPath, isWindows ? "junction" : "dir");
|
|
3543
3337
|
} catch (error) {
|
|
3544
3338
|
if (!isWindows)
|
|
3545
3339
|
throw error;
|
|
3546
|
-
await
|
|
3340
|
+
await mkdir2(linkPath, { recursive: true });
|
|
3547
3341
|
await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
|
|
3548
3342
|
}
|
|
3549
3343
|
}
|
|
@@ -3575,13 +3369,14 @@ class AppExecutor extends SysExecutor {
|
|
|
3575
3369
|
appPath: this.cwdPath,
|
|
3576
3370
|
executor: this,
|
|
3577
3371
|
getPath: (rel) => this.getPath(rel),
|
|
3578
|
-
fileExists: (rel) =>
|
|
3372
|
+
fileExists: (rel) => FileSys2.fileExists(this.getPath(rel)),
|
|
3373
|
+
readFile: (rel) => this.readFile(rel),
|
|
3579
3374
|
writeFile: async (rel, content, opts) => {
|
|
3580
3375
|
await this.writeFile(rel, content, opts);
|
|
3581
3376
|
},
|
|
3582
3377
|
readEnvClient: async () => {
|
|
3583
|
-
const envClientPath =
|
|
3584
|
-
if (!await
|
|
3378
|
+
const envClientPath = path9.join(this.cwdPath, "env", "env.client.ts");
|
|
3379
|
+
if (!await FileSys2.fileExists(envClientPath))
|
|
3585
3380
|
return null;
|
|
3586
3381
|
try {
|
|
3587
3382
|
const envUrl = pathToFileURL(envClientPath);
|
|
@@ -3612,16 +3407,6 @@ class AppExecutor extends SysExecutor {
|
|
|
3612
3407
|
return true;
|
|
3613
3408
|
});
|
|
3614
3409
|
}
|
|
3615
|
-
async getPluginRuntimePackages() {
|
|
3616
|
-
const plugins = await this.collectPlugins();
|
|
3617
|
-
const appConfig = await this.getConfig();
|
|
3618
|
-
const ctx = {
|
|
3619
|
-
appName: this.name,
|
|
3620
|
-
mobile: appConfig.mobile,
|
|
3621
|
-
hasMobilePermission: (permission) => Object.values(appConfig.mobile.targets).some((target) => target.permissions?.includes(permission) ?? false)
|
|
3622
|
-
};
|
|
3623
|
-
return [...new Set(plugins.flatMap((plugin) => plugin.runtimePackages?.(ctx) ?? []))];
|
|
3624
|
-
}
|
|
3625
3410
|
async increaseBuildNum() {
|
|
3626
3411
|
await increaseBuildNum(this);
|
|
3627
3412
|
}
|
|
@@ -3637,15 +3422,8 @@ class LibExecutor extends SysExecutor {
|
|
|
3637
3422
|
super({ workspace, name, type: "lib" });
|
|
3638
3423
|
this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/libs/${name}`);
|
|
3639
3424
|
}
|
|
3640
|
-
static #execs = new Map;
|
|
3641
3425
|
static from(executor, name) {
|
|
3642
|
-
|
|
3643
|
-
if (exec)
|
|
3644
|
-
return exec;
|
|
3645
|
-
else if (executor instanceof WorkspaceExecutor)
|
|
3646
|
-
return new LibExecutor({ workspace: executor, name });
|
|
3647
|
-
else
|
|
3648
|
-
return new LibExecutor({ workspace: executor.workspace, name });
|
|
3426
|
+
return new LibExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
|
|
3649
3427
|
}
|
|
3650
3428
|
#akanConfig = null;
|
|
3651
3429
|
async getConfig({ refresh } = {}) {
|
|
@@ -3668,9 +3446,7 @@ class PkgExecutor extends Executor {
|
|
|
3668
3446
|
this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/pkgs/${name}`);
|
|
3669
3447
|
}
|
|
3670
3448
|
static from(executor, name) {
|
|
3671
|
-
|
|
3672
|
-
return new PkgExecutor({ workspace: executor, name });
|
|
3673
|
-
return new PkgExecutor({ workspace: executor.workspace, name });
|
|
3449
|
+
return new PkgExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
|
|
3674
3450
|
}
|
|
3675
3451
|
#scanInfo = null;
|
|
3676
3452
|
async scan({ refresh } = {}) {
|
|
@@ -3688,9 +3464,12 @@ class PkgExecutor extends Executor {
|
|
|
3688
3464
|
const rootVersion = rootDeps[dep];
|
|
3689
3465
|
if (rootVersion)
|
|
3690
3466
|
return rootVersion;
|
|
3467
|
+
const overrideVersion = rootPackageJson.overrides?.[dep];
|
|
3468
|
+
if (typeof overrideVersion === "string")
|
|
3469
|
+
return overrideVersion;
|
|
3691
3470
|
try {
|
|
3692
3471
|
const packageJsonPath = `pkgs/${dep}/package.json`;
|
|
3693
|
-
if (!await Bun.file(
|
|
3472
|
+
if (!await Bun.file(path9.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
|
|
3694
3473
|
return;
|
|
3695
3474
|
const packageJson = await this.workspace.readJson(packageJsonPath);
|
|
3696
3475
|
if (packageJson.name === dep)
|
|
@@ -3713,16 +3492,8 @@ class PkgExecutor extends Executor {
|
|
|
3713
3492
|
}
|
|
3714
3493
|
if (missingDeps.length > 0)
|
|
3715
3494
|
throw new Error(`Missing dependency versions in root package.json: ${missingDeps.join(", ")}`);
|
|
3716
|
-
const
|
|
3717
|
-
|
|
3718
|
-
if (!version)
|
|
3719
|
-
throw new Error(`Missing dependency versions in root package.json: ${dep}`);
|
|
3720
|
-
return [dep, version];
|
|
3721
|
-
});
|
|
3722
|
-
return {
|
|
3723
|
-
dependencies: Object.fromEntries(toDependencyEntries(dependencyNames)),
|
|
3724
|
-
devDependencies: Object.fromEntries(toDependencyEntries(devDependencyNames))
|
|
3725
|
-
};
|
|
3495
|
+
const toDependencyMap = (names) => Object.fromEntries(names.map((dep) => [dep, dependencyVersions.get(dep)]));
|
|
3496
|
+
return { dependencies: toDependencyMap(dependencyNames), devDependencies: toDependencyMap(devDependencyNames) };
|
|
3726
3497
|
}
|
|
3727
3498
|
async updatePackageJsonDependencies(dependencies = [], devDependencies = []) {
|
|
3728
3499
|
const [rootPackageJson, pkgJson] = await Promise.all([this.workspace.getPackageJson(), this.getPackageJson()]);
|
|
@@ -3791,4 +3562,4 @@ class ModuleExecutor extends Executor {
|
|
|
3791
3562
|
}
|
|
3792
3563
|
}
|
|
3793
3564
|
|
|
3794
|
-
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, isScannedRootEntry, AppInfo, Spinner,
|
|
3565
|
+
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, binPlatforms, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
|