@akanjs/cli 3.1.0-rc.2 → 3.1.0-rc.21
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-q50k4s45.js → CodeAgent-gzfc5t52.js} +2 -2
- package/{CodeAgentRpcHost-p7h6n2hp.js → CodeAgentRpcHost-9g27hmer.js} +1 -1
- package/{CodeTui-62g35ztk.js → CodeTui-bpsjs8wz.js} +47 -4
- package/TunnelShare-tfrnftgr.js +14 -0
- package/{agent.command-adfd5299.js → agent.command-67ysh3n6.js} +5 -5
- package/akanCode.js +7 -5
- package/{application.command-hj3gv1rx.js → application.command-q0tqw5pc.js} +38 -11
- package/{applicationBuildRunner-by10r9gf.js → applicationBuildRunner-wsszr305.js} +16 -9
- package/buildBatch.proc.js +7 -7
- package/{cloud.command-k2fhaape.js → cloud.command-e7tkxyx6.js} +17 -16
- package/cloud.script-krzh3d7k.js +24 -0
- package/{code.command-kt5zgcc2.js → code.command-ttk8tje2.js} +25 -12
- package/commandManifest.json +1 -1
- package/{context.command-8fgn42jw.js → context.command-bdsk2z2r.js} +17 -16
- package/context.runner-bxzv0gm0.js +25 -0
- package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
- package/{guideline.command-3yxxr2mm.js → guideline.command-man90n70.js} +2 -2
- package/guidelines/agentRule/agentRule.instruction.md +25 -10
- package/guidelines/conventions/conventions.instruction.md +486 -1084
- package/guidelines/cssRule/cssRule.instruction.md +16 -1
- package/guidelines/fieldRule/fieldRule.instruction.md +74 -0
- package/guidelines/framework/framework.instruction.md +4 -7
- package/guidelines/mcpRule/mcpRule.instruction.md +16 -9
- package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
- package/guidelines/modelStore/modelStore.instruction.md +16 -0
- package/guidelines/pageRule/pageRule.instruction.md +80 -0
- package/guidelines/queryRule/queryRule.instruction.md +47 -12
- package/guidelines/runtimeRule/runtimeRule.instruction.md +89 -2
- package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
- package/guidelines/transportRule/transportRule.instruction.md +107 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +70 -7
- package/incrementalBuilder.proc.js +4 -4
- package/{index-rhbabmja.js → index-033wne2j.js} +260 -259
- package/{index-xpdgxsj5.js → index-1r5a8bzx.js} +2216 -2216
- package/{index-jwqtchha.js → index-27y8t17p.js} +1 -1
- package/{index-wa5jbxj1.js → index-2mrn1mrg.js} +1 -0
- package/{index-t7nws9mx.js → index-2w8j2fnv.js} +661 -448
- package/{index-0909wckt.js → index-35m6bzm7.js} +238 -236
- package/{index-sqfdmb5b.js → index-4393nb4b.js} +4 -4
- package/{index-sby8fe1p.js → index-48edp6q4.js} +1 -1
- package/{index-g0vwzy9m.js → index-4v43h00g.js} +20 -21
- package/{index-azh29dc8.js → index-54wc2phm.js} +19 -14
- package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
- package/index-62md4dwy.js +260 -0
- package/{index-hnby7p9n.js → index-75n5q4ph.js} +7 -7
- package/{index-fjsv10hd.js → index-844q7f8z.js} +7 -7
- package/{index-tv8fjaba.js → index-8jehkey8.js} +4 -4
- package/{index-bea2gwc6.js → index-9mt4smfy.js} +2 -2
- package/{index-d2242ksy.js → index-at7bz0xq.js} +3 -0
- package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
- package/{index-8zdwpxef.js → index-cae9m5jz.js} +119 -81
- package/{index-tz5evhdr.js → index-ebarbgba.js} +6 -6
- package/{index-2etqq75t.js → index-g6ax17x4.js} +1114 -1082
- package/{index-h85cxfkg.js → index-g9ks2k8c.js} +2 -2
- package/{index-nq045px5.js → index-h3r3nr9y.js} +356 -307
- package/{index-20cebtth.js → index-h6czey01.js} +79 -24
- package/{index-6726qr1m.js → index-jrmzcp66.js} +57 -58
- package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
- package/{index-8935h3n1.js → index-mdjc93e1.js} +1 -1
- package/{index-rk9sdys3.js → index-nv79n88g.js} +5 -5
- package/{index-kc8z134d.js → index-nx64dz92.js} +38 -15
- package/{index-z507nzhg.js → index-p2r64bpa.js} +4 -4
- package/{index-nq2ja5e0.js → index-pg1t468s.js} +3 -3
- package/{index-3reaaa1h.js → index-rc51f9ht.js} +29 -27
- package/{index-e9prnmyt.js → index-ta1dychm.js} +3 -3
- package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
- package/{index-31ce9vhg.js → index-vkb9m4z7.js} +80 -38
- package/{index-58vvz603.js → index-xsd96yzw.js} +1 -1
- package/{index-pyxn1b53.js → index-zv5s8x58.js} +13 -13
- package/index.js +22 -22
- package/{library.command-8tw59xzj.js → library.command-6amzsrb8.js} +3 -3
- package/{localRegistry.command-tf3dmznd.js → localRegistry.command-yqc0nyhh.js} +15 -14
- package/{module.command-9vm8hffy.js → module.command-10x6jv6c.js} +5 -5
- package/{package.command-erbxe2tz.js → package.command-pwfvecjf.js} +4 -4
- package/package.json +21 -21
- package/{page.command-jpavcj0p.js → page.command-ahhgx9y1.js} +3 -3
- package/{pipeline.command-b8rzvjvv.js → pipeline.command-1ptr3e9y.js} +3 -3
- package/{primitive.command-sm32chzk.js → primitive.command-g5xwv6tj.js} +6 -6
- package/{projectTypecheck-jamtx95x.js → projectTypecheck-fx8x7cat.js} +1 -1
- package/{quality.command-pkd17n6y.js → quality.command-fw9ck061.js} +4 -4
- package/{repair.command-8y4mq6hs.js → repair.command-g8kecj96.js} +5 -5
- package/{scalar.command-q8khmyx5.js → scalar.command-fprap51w.js} +4 -4
- package/skills/akan-code/SKILL.md +4 -0
- package/skills/akan-store/SKILL.md +2 -1
- package/{subspace.command-51n0ykbx.js → subspace.command-sc0t527y.js} +11 -10
- package/templates/appSample/lib/task/Task.Util.tsx +7 -7
- package/templates/appSample/lib/task/Task.Zone.tsx +1 -1
- package/templates/appSample/ui/QuantityControl.tsx +2 -2
- package/templates/crudSinglePage/page.tsx +1 -1
- package/templates/lib/cnst.ts +0 -1
- package/templates/migration/__migrationId__.ts +23 -0
- package/templates/server.ts +8 -1
- package/templates/workspaceRoot/.gitignore.template +23 -52
- package/templates/workspaceRoot/biome.json.template +1 -1
- package/{tunnel.command-7820vtxq.js → tunnel.command-mqnsqbwf.js} +8 -7
- package/{workflow.command-n5sv4wqz.js → workflow.command-j0jgkr13.js} +10 -10
- package/{workspace.command-zdy60jh8.js → workspace.command-1r9hvvw0.js} +29 -28
- package/TunnelShare-f1egrktx.js +0 -13
- package/cloud.script-z8p5dz7e.js +0 -23
- package/context.runner-hnh0rg7m.js +0 -24
- package/index-n68jx79w.js +0 -120
|
@@ -4,101 +4,284 @@ import {
|
|
|
4
4
|
FileSys2
|
|
5
5
|
} from "./index-52w0fvh8.js";
|
|
6
6
|
|
|
7
|
-
// pkgs/@akanjs/devkit/
|
|
8
|
-
import {
|
|
9
|
-
|
|
7
|
+
// pkgs/@akanjs/devkit/executors.ts
|
|
8
|
+
import {
|
|
9
|
+
exec,
|
|
10
|
+
fork,
|
|
11
|
+
spawn as spawn2
|
|
12
|
+
} from "child_process";
|
|
13
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
14
|
+
import {
|
|
15
|
+
copyFile,
|
|
16
|
+
cp as cpEntry,
|
|
17
|
+
lstat,
|
|
18
|
+
mkdir as mkdir3,
|
|
19
|
+
readdir as readDirEntries,
|
|
20
|
+
realpath,
|
|
21
|
+
rm as rm4,
|
|
22
|
+
stat as stat2,
|
|
23
|
+
symlink
|
|
24
|
+
} from "fs/promises";
|
|
25
|
+
import path13 from "path";
|
|
26
|
+
import { pathToFileURL } from "url";
|
|
27
|
+
import {
|
|
28
|
+
capitalize,
|
|
29
|
+
getPageSourceFileViolation,
|
|
30
|
+
isRouteSourceFile,
|
|
31
|
+
Logger as Logger3,
|
|
32
|
+
parseRouteModuleKey,
|
|
33
|
+
toError as toError2,
|
|
34
|
+
validatePageSourceFile,
|
|
35
|
+
validateSubRoutePageKey
|
|
36
|
+
} from "akanjs/common";
|
|
37
|
+
var {$ } = globalThis.Bun;
|
|
38
|
+
import chalk2 from "chalk";
|
|
10
39
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
// pkgs/@akanjs/devkit/agentsIndex.ts
|
|
41
|
+
var recipeScannerLoad = null;
|
|
42
|
+
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-4b69ybe0.js");
|
|
43
|
+
var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
|
|
44
|
+
var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
|
|
45
|
+
var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
|
|
46
|
+
var stampBlockVersion = (block, version) => `${AGENT_VERSION_PREFIX}${version} -->
|
|
47
|
+
|
|
48
|
+
${block}`;
|
|
49
|
+
var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
|
|
50
|
+
var readDevkitVersion = async () => {
|
|
51
|
+
const { readFile } = await import("fs/promises");
|
|
52
|
+
const { getDirname } = await import("./fileSys-wzzrntkw.js");
|
|
53
|
+
try {
|
|
54
|
+
const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
|
|
55
|
+
return JSON.parse(raw).version ?? null;
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
21
58
|
}
|
|
22
|
-
|
|
23
|
-
|
|
59
|
+
};
|
|
60
|
+
var upsertAgentBlock = (existing, block) => {
|
|
61
|
+
const managed = `${AGENT_BLOCK_START}
|
|
62
|
+
${block}
|
|
63
|
+
${AGENT_BLOCK_END}`;
|
|
64
|
+
const startIndex = existing.indexOf(AGENT_BLOCK_START);
|
|
65
|
+
const endIndex = existing.indexOf(AGENT_BLOCK_END);
|
|
66
|
+
if (startIndex >= 0 && endIndex > startIndex) {
|
|
67
|
+
return `${existing.slice(0, startIndex)}${managed}${existing.slice(endIndex + AGENT_BLOCK_END.length)}`;
|
|
24
68
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
69
|
+
return `${existing.replace(/\s*$/, "")}
|
|
70
|
+
|
|
71
|
+
${managed}
|
|
72
|
+
`;
|
|
73
|
+
};
|
|
74
|
+
var extractAgentBlock = (content) => {
|
|
75
|
+
const startIndex = content.indexOf(AGENT_BLOCK_START);
|
|
76
|
+
const endIndex = content.indexOf(AGENT_BLOCK_END);
|
|
77
|
+
if (startIndex < 0 || endIndex <= startIndex)
|
|
78
|
+
return null;
|
|
79
|
+
return content.slice(startIndex + AGENT_BLOCK_START.length, endIndex).trim();
|
|
80
|
+
};
|
|
81
|
+
var signatureOf = (recipe) => {
|
|
82
|
+
const entries = Object.entries(recipe.variants);
|
|
83
|
+
if (entries.length === 0)
|
|
84
|
+
return "";
|
|
85
|
+
const parts = entries.map(([key, values]) => {
|
|
86
|
+
if (values.length === 1 && values[0] === "true")
|
|
87
|
+
return `${key}?`;
|
|
88
|
+
const def = recipe.defaultVariants?.[key];
|
|
89
|
+
return `${key}: ${values.map((value) => value === def ? `${value}*` : value).join("|")}`;
|
|
90
|
+
});
|
|
91
|
+
return `(${parts.join(" \xB7 ")})`;
|
|
92
|
+
};
|
|
93
|
+
var renderRecipeEntries = (recipes) => {
|
|
94
|
+
const groups = new Map;
|
|
95
|
+
for (const recipe of recipes)
|
|
96
|
+
groups.set(recipe.importFrom, [...groups.get(recipe.importFrom) ?? [], recipe]);
|
|
97
|
+
return [...groups.entries()].map(([importFrom, list]) => {
|
|
98
|
+
const items = list.sort((a, b) => a.name.localeCompare(b.name)).map((recipe) => `- \`${recipe.name}\`${signatureOf(recipe)}${recipe.doc ? ` \u2014 ${recipe.doc}` : ""}`).join(`
|
|
37
99
|
`);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
proc.exited
|
|
54
|
-
]);
|
|
55
|
-
if (exitCode !== 0)
|
|
56
|
-
throw new Error((stderr || stdout).trim() || `Typecheck failed with exit code ${exitCode}`);
|
|
57
|
-
}
|
|
58
|
-
static async workerEntry(workspaceRoot) {
|
|
59
|
-
const candidates = [
|
|
60
|
-
path.join(workspaceRoot, "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
61
|
-
path.join(workspaceRoot, "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
62
|
-
path.join(import.meta.dir, "typecheck.proc.js"),
|
|
63
|
-
path.join(import.meta.dir, "typecheck.proc.ts"),
|
|
64
|
-
path.join(import.meta.dir, "..", "typecheck.proc.js")
|
|
65
|
-
];
|
|
66
|
-
for (const candidate of candidates)
|
|
67
|
-
if (await Bun.file(candidate).exists())
|
|
68
|
-
return candidate;
|
|
69
|
-
throw new Error(`[devkit] typecheck worker entry not found; looked in: ${candidates.join(", ")}`);
|
|
100
|
+
return `Import from \`${importFrom}\`:
|
|
101
|
+
${items}`;
|
|
102
|
+
}).join(`
|
|
103
|
+
|
|
104
|
+
`);
|
|
105
|
+
};
|
|
106
|
+
var collectScopeRecipeSources = async (workspaceRoot, scope, libDeps) => {
|
|
107
|
+
const { collectRecipeSources } = await loadRecipeScanner();
|
|
108
|
+
const sources = [
|
|
109
|
+
...await collectRecipeSources(`${workspaceRoot}/${scope.type}s/${scope.name}/ui`, `@${scope.type}s/${scope.name}/ui`)
|
|
110
|
+
];
|
|
111
|
+
for (const lib of [...new Set(libDeps)].sort()) {
|
|
112
|
+
if (scope.type === "lib" && lib === scope.name)
|
|
113
|
+
continue;
|
|
114
|
+
sources.push(...await collectRecipeSources(`${workspaceRoot}/libs/${lib}/ui`, `@libs/${lib}/ui`));
|
|
70
115
|
}
|
|
71
|
-
|
|
116
|
+
return sources;
|
|
117
|
+
};
|
|
118
|
+
var scanScopeRecipes = async (workspaceRoot, scope, libDeps) => {
|
|
119
|
+
const { scanRecipes } = await loadRecipeScanner();
|
|
120
|
+
return scanRecipes(await collectScopeRecipeSources(workspaceRoot, scope, libDeps));
|
|
121
|
+
};
|
|
122
|
+
var renderScopeAgentBlock = (scope, recipes) => {
|
|
123
|
+
const scopePath = `${scope.type}s/${scope.name}`;
|
|
124
|
+
const intro = `## Recipes In Scope
|
|
72
125
|
|
|
73
|
-
|
|
74
|
-
import
|
|
75
|
-
|
|
76
|
-
import
|
|
126
|
+
UI recipes importable from \`${scopePath}\` code, **in addition to** the framework recipes indexed in the root
|
|
127
|
+
\`AGENTS.md\` \`## Recipes\`. Same contract: import by exact name, then \`<name>(variants?, className?)\` \u2014 the second
|
|
128
|
+
arg merges internally and takes an array too, so never wrap it in \`cn()\`. \`*\` marks the default, \`key?\` is a
|
|
129
|
+
boolean flag. Do not guess recipe names or import paths; this index is regenerated by \`akan sync ${scope.name}\`
|
|
130
|
+
and verified by \`akan lint ${scope.name}\`.`;
|
|
131
|
+
if (recipes.length === 0) {
|
|
132
|
+
return `${intro}
|
|
77
133
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
static fileName(environment, profile) {
|
|
82
|
-
return `env.server.${environment}.${profile}.ts`;
|
|
83
|
-
}
|
|
84
|
-
static profileFileOf(file, profile) {
|
|
85
|
-
return file.replace(/\.ts$/, `.${profile}.ts`);
|
|
134
|
+
No scope recipes yet. Before inlining a repeated surface (card, box, tile, \u2026), reuse a framework recipe from the
|
|
135
|
+
root \`AGENTS.md\` or author one as \`${scopePath}/ui/Recipe/<name>.ts\` (one recipe per file, re-exported from that
|
|
136
|
+
folder's \`index.ts\`) \u2014 see the \`recipeRule\` guideline.`;
|
|
86
137
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
138
|
+
return `${intro}
|
|
139
|
+
|
|
140
|
+
${renderRecipeEntries(recipes)}`;
|
|
141
|
+
};
|
|
142
|
+
var renderScopeAgentsMd = (scope, block) => `# ${scope.name} \u2014 Agent Guide
|
|
143
|
+
|
|
144
|
+
Scoped guide for coding agents working in \`${scope.type}s/${scope.name}\`. Workspace-wide conventions live in the
|
|
145
|
+
root \`AGENTS.md\`; this file carries what is importable from this ${scope.type === "app" ? "app" : "library"}. The
|
|
146
|
+
section between the \`akan:agent\` markers is regenerated by \`akan sync ${scope.name}\`; edit anything outside the
|
|
147
|
+
markers freely.
|
|
148
|
+
|
|
149
|
+
${AGENT_BLOCK_START}
|
|
150
|
+
${block}
|
|
151
|
+
${AGENT_BLOCK_END}
|
|
152
|
+
`;
|
|
153
|
+
var CLAUDE_COMMENT_RULE = `## Comments \u2014 Overrides Your Default
|
|
154
|
+
|
|
155
|
+
Write **no comments** unless the comment passes the test below. This is the rule agents break most often here, so it
|
|
156
|
+
is repeated outside the guide: a diff that adds a comment the test rejects is a diff to redo.
|
|
157
|
+
|
|
158
|
+
Before typing \`//\`, \`/*\`, or a doc block, ask \u2014 **does this sentence carry a fact that is nowhere in the code?**
|
|
159
|
+
|
|
160
|
+
- Restates the identifier, the signature, or the line under it \u2192 delete it.
|
|
161
|
+
- Labels a section (\`// helpers\`, \`// state\`) or narrates a step (\`// fetch the user\`, \`// then save\`) \u2192 delete it.
|
|
162
|
+
- JSDoc on an ordinary function, or a why/how preamble on ordinary logic \u2192 delete it.
|
|
163
|
+
- Explains the edit you just made, for whoever reads the diff \u2192 say it in your reply, not in the file.
|
|
164
|
+
- Names a vendor or protocol quirk, an infrastructure constraint, a library gotcha, security reasoning, a math
|
|
165
|
+
derivation, a domain field's business meaning, a state transition, or why an obvious alternative was rejected \u2192
|
|
166
|
+
keep it, one line.
|
|
167
|
+
|
|
168
|
+
That keep-list is exact \u2014 \`Comments\` in the guide is the full version. "It aids readability" and "this logic is
|
|
169
|
+
subtle" are not on it: rename or split the code instead. When you edit an existing file, match its density; if the
|
|
170
|
+
surrounding code carries none, your diff carries none.`;
|
|
171
|
+
var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
|
|
172
|
+
|
|
173
|
+
@AGENTS.md
|
|
174
|
+
|
|
175
|
+
${CLAUDE_COMMENT_RULE}
|
|
176
|
+
`;
|
|
177
|
+
|
|
178
|
+
// pkgs/@akanjs/devkit/akanConfig/akanBin.ts
|
|
179
|
+
import path from "path";
|
|
180
|
+
|
|
181
|
+
// pkgs/@akanjs/devkit/akanConfig/types.ts
|
|
182
|
+
import { archs, binPlatforms, defaultAppPipeline } from "akanjs";
|
|
183
|
+
|
|
184
|
+
// pkgs/@akanjs/devkit/akanConfig/akanBin.ts
|
|
185
|
+
class AkanBin {
|
|
186
|
+
static archive = /\.(zip|tar\.gz|tgz|tar\.xz|txz|tar\.bz2|tbz2|tar)$/i;
|
|
187
|
+
static fileName = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
188
|
+
static parse(declared, owner, baseDir) {
|
|
189
|
+
if (declared === undefined)
|
|
190
|
+
return {};
|
|
191
|
+
if (!declared || typeof declared !== "object" || Array.isArray(declared))
|
|
192
|
+
throw new Error(`${owner}: bin maps each executable's name to its platforms`);
|
|
193
|
+
return Object.fromEntries(Object.entries(declared).map(([name, platforms]) => [name, AkanBin.#platforms(name, platforms, owner, baseDir)]));
|
|
95
194
|
}
|
|
96
|
-
static
|
|
97
|
-
return
|
|
195
|
+
static isArchive(source) {
|
|
196
|
+
return AkanBin.archive.test("url" in source ? new URL(source.url).pathname : source.path);
|
|
98
197
|
}
|
|
99
|
-
static #
|
|
100
|
-
|
|
101
|
-
|
|
198
|
+
static #platforms(name, platforms, owner, baseDir) {
|
|
199
|
+
if (!AkanBin.fileName.test(name))
|
|
200
|
+
throw new Error(`${owner}: bin.${name} is not a file name`);
|
|
201
|
+
if (!platforms || typeof platforms !== "object" || Array.isArray(platforms))
|
|
202
|
+
throw new Error(`${owner}: bin.${name} maps platforms (${binPlatforms.join(", ")}) to where the file comes from`);
|
|
203
|
+
const resolved = {};
|
|
204
|
+
for (const [platform, source] of Object.entries(platforms)) {
|
|
205
|
+
if (!binPlatforms.includes(platform))
|
|
206
|
+
throw new Error(`${owner}: bin.${name}.${platform} is not one of ${binPlatforms.join(", ")}`);
|
|
207
|
+
resolved[platform] = AkanBin.#source(`${owner}: bin.${name}.${platform}`, source, baseDir);
|
|
208
|
+
}
|
|
209
|
+
return resolved;
|
|
210
|
+
}
|
|
211
|
+
static #source(at, source, baseDir) {
|
|
212
|
+
if (!source || typeof source !== "object" || Array.isArray(source))
|
|
213
|
+
throw new Error(`${at} must be { url, sha256 } or { path }`);
|
|
214
|
+
const { url, sha256, path: localPath, file, ...rest } = source;
|
|
215
|
+
const extra = Object.keys(rest);
|
|
216
|
+
if (extra.length)
|
|
217
|
+
throw new Error(`${at}: unknown key ${extra.join(", ")}`);
|
|
218
|
+
if (url === undefined === (localPath === undefined))
|
|
219
|
+
throw new Error(`${at} takes either url and sha256, or path`);
|
|
220
|
+
const resolved = url !== undefined ? AkanBin.#urlSource(at, url, sha256) : AkanBin.#pathSource(at, localPath, sha256, baseDir);
|
|
221
|
+
const archive = AkanBin.isArchive(resolved);
|
|
222
|
+
if (file === undefined) {
|
|
223
|
+
if (archive)
|
|
224
|
+
throw new Error(`${at} names an archive: file picks the executable inside it`);
|
|
225
|
+
return resolved;
|
|
226
|
+
}
|
|
227
|
+
if (!archive)
|
|
228
|
+
throw new Error(`${at}: file picks the executable out of an archive, and this source is none`);
|
|
229
|
+
const parts = typeof file === "string" ? file.split(/[\\/]/).filter((part) => part && part !== ".") : [];
|
|
230
|
+
if (!parts.length || path.isAbsolute(file) || parts.includes(".."))
|
|
231
|
+
throw new Error(`${at}: file must be a path inside the archive (got ${JSON.stringify(file)})`);
|
|
232
|
+
return { ...resolved, file: parts.join("/") };
|
|
233
|
+
}
|
|
234
|
+
static #urlSource(at, url, sha256) {
|
|
235
|
+
if (typeof url !== "string" || !URL.canParse(url) || !/^https?:$/.test(new URL(url).protocol))
|
|
236
|
+
throw new Error(`${at}: url must be an http(s) address`);
|
|
237
|
+
if (typeof sha256 !== "string" || !/^[0-9a-f]{64}$/i.test(sha256))
|
|
238
|
+
throw new Error(`${at}: sha256 must be the downloaded file's 64-digit hex digest`);
|
|
239
|
+
return { url, sha256: sha256.toLowerCase() };
|
|
240
|
+
}
|
|
241
|
+
static #pathSource(at, localPath, sha256, baseDir) {
|
|
242
|
+
if (typeof localPath !== "string" || !localPath)
|
|
243
|
+
throw new Error(`${at}: path must name a file`);
|
|
244
|
+
if (sha256 !== undefined)
|
|
245
|
+
throw new Error(`${at}: sha256 checks a download, and a path source downloads nothing`);
|
|
246
|
+
return { path: path.resolve(baseDir, localPath) };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
|
|
250
|
+
import fs from "fs";
|
|
251
|
+
import path3 from "path";
|
|
252
|
+
import { fileURLToPath } from "url";
|
|
253
|
+
import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
|
|
254
|
+
import { resolveAkanI18nConfig, trimSlashes } from "akanjs/common";
|
|
255
|
+
|
|
256
|
+
// pkgs/@akanjs/devkit/transforms/serverEnvProfile.ts
|
|
257
|
+
import { existsSync, realpathSync } from "fs";
|
|
258
|
+
import path2 from "path";
|
|
259
|
+
import { Logger, toError } from "akanjs/common";
|
|
260
|
+
|
|
261
|
+
class ServerEnvProfile {
|
|
262
|
+
static namePattern = /^[a-z][a-zA-Z0-9-]*$/;
|
|
263
|
+
static envFilePattern = /[\\/]env[\\/]env\.server\.[A-Za-z0-9-]+\.ts$/;
|
|
264
|
+
static fileName(environment, profile) {
|
|
265
|
+
return `env.server.${environment}.${profile}.ts`;
|
|
266
|
+
}
|
|
267
|
+
static profileFileOf(file, profile) {
|
|
268
|
+
return file.replace(/\.ts$/, `.${profile}.ts`);
|
|
269
|
+
}
|
|
270
|
+
static #url(value) {
|
|
271
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(value))
|
|
272
|
+
return null;
|
|
273
|
+
try {
|
|
274
|
+
return new URL(value);
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
static #carriesCredential(url) {
|
|
280
|
+
return !!url.password || [...url.searchParams.keys()].some((name) => Logger.isSecretKey(name));
|
|
281
|
+
}
|
|
282
|
+
static #isCredentialUrl(value) {
|
|
283
|
+
const url = ServerEnvProfile.#url(value);
|
|
284
|
+
return !!url && ServerEnvProfile.#carriesCredential(url);
|
|
102
285
|
}
|
|
103
286
|
static #isSecret(value, underSecret) {
|
|
104
287
|
return underSecret && !!value.trim() || ServerEnvProfile.#isCredentialUrl(value);
|
|
@@ -327,838 +510,92 @@ class ServerEnvProfile {
|
|
|
327
510
|
}
|
|
328
511
|
}
|
|
329
512
|
|
|
330
|
-
// pkgs/@akanjs/devkit/
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return true;
|
|
383
|
-
if (ts.isCallExpression(statement.expression))
|
|
384
|
-
return true;
|
|
385
|
-
if (ts.isIdentifier(statement.expression))
|
|
386
|
-
defaultIdentifier = statement.expression.text;
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
390
|
-
for (const specifier of statement.exportClause.elements) {
|
|
391
|
-
if (specifier.name.text !== "default")
|
|
392
|
-
continue;
|
|
393
|
-
defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
|
|
398
|
-
}
|
|
399
|
-
#hasModifier(node, kind) {
|
|
400
|
-
const ts = this.#ts;
|
|
401
|
-
return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
|
|
402
|
-
}
|
|
403
|
-
#isAsyncFunctionExpression(node) {
|
|
404
|
-
const ts = this.#ts;
|
|
405
|
-
return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
|
|
513
|
+
// pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
|
|
514
|
+
var DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui", "akanjs/server"];
|
|
515
|
+
var DEFAULT_OPTIMIZE_IMPORTS = [
|
|
516
|
+
"lucide-react date-fns lodash-es ramda antd react-bootstrap ahooks @ant-design/icons @headlessui/react",
|
|
517
|
+
"@headlessui-float/react @heroicons/react/20/solid @heroicons/react/24/solid @heroicons/react/24/outline",
|
|
518
|
+
"@visx/visx @tremor/react rxjs @mui/material @mui/icons-material recharts react-use @material-ui/core",
|
|
519
|
+
"@material-ui/icons @tabler/icons-react mui-core react-icons/*"
|
|
520
|
+
].flatMap((line) => line.split(" "));
|
|
521
|
+
var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
|
|
522
|
+
var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
|
|
523
|
+
var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
|
|
524
|
+
var DEFAULT_AKAN_IMAGE_CONFIG = {
|
|
525
|
+
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
|
526
|
+
imageSizes: [32, 48, 64, 96, 128, 256, 384],
|
|
527
|
+
formats: ["image/webp"],
|
|
528
|
+
qualities: [75],
|
|
529
|
+
minimumCacheTTL: 14400,
|
|
530
|
+
remotePatterns: [],
|
|
531
|
+
localPatterns: [{ pathname: "/**" }],
|
|
532
|
+
dangerouslyAllowSVG: false,
|
|
533
|
+
maximumRedirects: 3,
|
|
534
|
+
fetchTimeoutMs: 7000,
|
|
535
|
+
maxRemoteBytes: 25 * 1024 * 1024,
|
|
536
|
+
maxConcurrency: 0
|
|
537
|
+
};
|
|
538
|
+
var normalizeIndexPath = (indexPath) => {
|
|
539
|
+
const normalized = indexPath?.trim();
|
|
540
|
+
if (!normalized)
|
|
541
|
+
return;
|
|
542
|
+
const path = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
543
|
+
return path === "/" ? "/" : path;
|
|
544
|
+
};
|
|
545
|
+
var normalizeStringList = (values) => {
|
|
546
|
+
const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
547
|
+
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
548
|
+
};
|
|
549
|
+
var sanitizeAppIdSegment = (value) => {
|
|
550
|
+
const cleaned = value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
551
|
+
if (!cleaned)
|
|
552
|
+
return "app";
|
|
553
|
+
return /^[a-z]/.test(cleaned) ? cleaned : `app${cleaned}`;
|
|
554
|
+
};
|
|
555
|
+
var deriveDefaultAppId = (orgName, appName) => `com.${sanitizeAppIdSegment(orgName)}.${sanitizeAppIdSegment(appName)}`;
|
|
556
|
+
var normalizeDeepLinkDomain = (domain) => {
|
|
557
|
+
const normalized = domain.trim();
|
|
558
|
+
if (!normalized)
|
|
559
|
+
return "";
|
|
560
|
+
try {
|
|
561
|
+
const url = new URL(normalized.includes("://") ? normalized : `https://${normalized}`);
|
|
562
|
+
return url.host.toLowerCase();
|
|
563
|
+
} catch {
|
|
564
|
+
return normalized.replace(/^https?:\/\//, "").replace(/\/+$/g, "").toLowerCase();
|
|
406
565
|
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
410
|
-
var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
411
|
-
var IMPLICIT_LAYOUT_DIR = path4.join(".akan", "generated", "root-layouts");
|
|
412
|
-
var IMPLICIT_DICT_DIR = path4.join(".akan", "generated", "dict");
|
|
413
|
-
var IMPLICIT_OVERRIDES_DIR = path4.join(".akan", "generated", "overrides");
|
|
414
|
-
var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
415
|
-
var importSpecifier = (fromAbsPath, toAbsPath) => {
|
|
416
|
-
const rel = path4.relative(path4.dirname(fromAbsPath), toAbsPath).split(path4.sep).join("/");
|
|
417
|
-
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
418
566
|
};
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
567
|
+
var normalizeDeepLinks = (deepLinks) => {
|
|
568
|
+
const schemes = normalizeStringList(deepLinks?.schemes);
|
|
569
|
+
const domains = normalizeStringList(deepLinks?.domains?.map(normalizeDeepLinkDomain));
|
|
570
|
+
if (!schemes && !domains)
|
|
571
|
+
return;
|
|
572
|
+
return { ...schemes ? { schemes } : {}, ...domains ? { domains } : {} };
|
|
573
|
+
};
|
|
574
|
+
var emptyLibContributions = () => ({
|
|
575
|
+
externalLibs: [],
|
|
576
|
+
trustedDependencies: [],
|
|
577
|
+
bin: [],
|
|
578
|
+
docker: { preRuns: [], postRuns: [] },
|
|
579
|
+
keepFonts: []
|
|
580
|
+
});
|
|
581
|
+
var normalizePackageNames = (names, owner) => {
|
|
582
|
+
if (names === undefined)
|
|
583
|
+
return [];
|
|
584
|
+
if (!Array.isArray(names) || names.some((name) => typeof name !== "string" || !name.trim()))
|
|
585
|
+
throw new Error(`${owner}: trustedDependencies lists package names`);
|
|
586
|
+
return [...new Set(names.map((name) => name.trim()))];
|
|
587
|
+
};
|
|
588
|
+
var normalizeKeepFonts = (keepFonts) => [
|
|
589
|
+
...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean))
|
|
590
|
+
];
|
|
591
|
+
var dedupeDockerRuns = (runs) => {
|
|
592
|
+
const byKey = new Map;
|
|
593
|
+
for (const run of runs)
|
|
594
|
+
byKey.set(typeof run === "string" ? run : JSON.stringify(run), run);
|
|
595
|
+
return [...byKey.values()];
|
|
596
|
+
};
|
|
426
597
|
|
|
427
|
-
|
|
428
|
-
return createElement(UiOverrideProvider, { value }, children);
|
|
429
|
-
}
|
|
430
|
-
`;
|
|
431
|
-
await Bun.write(absPath, source);
|
|
432
|
-
return absPath;
|
|
433
|
-
}
|
|
434
|
-
async function pruneGeneratedFiles(dirAbsPath, keepAbsPaths) {
|
|
435
|
-
const keep = new Set(keepAbsPaths);
|
|
436
|
-
const names = await readdir(dirAbsPath).catch(() => []);
|
|
437
|
-
await Promise.all(names.map((name) => path4.join(dirAbsPath, name)).filter((absPath) => !keep.has(absPath)).map((absPath) => rm2(absPath, { force: true })));
|
|
438
|
-
}
|
|
439
|
-
function getRootBoundarySegments(key) {
|
|
440
|
-
const match = LAYOUT_KEY_RE.exec(key);
|
|
441
|
-
if (!match)
|
|
442
|
-
return null;
|
|
443
|
-
const prefix = match[1]?.replace(/\/$/, "");
|
|
444
|
-
if (!prefix)
|
|
445
|
-
return [];
|
|
446
|
-
return prefix.split("/").filter(Boolean);
|
|
447
|
-
}
|
|
448
|
-
function implicitRootLayoutKey(segments) {
|
|
449
|
-
return `./${[...segments, "__root_layout"].join("/")}.tsx`;
|
|
450
|
-
}
|
|
451
|
-
function implicitRootLayoutAbsPath(appCwdPath, segments) {
|
|
452
|
-
const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
|
|
453
|
-
return path4.join(path4.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
454
|
-
}
|
|
455
|
-
function isRootBoundarySegments(segments, basePaths) {
|
|
456
|
-
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
457
|
-
if (firstVisibleIndex === -1)
|
|
458
|
-
return segments.length <= 1;
|
|
459
|
-
if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
|
|
460
|
-
return false;
|
|
461
|
-
const visible = segments.slice(firstVisibleIndex);
|
|
462
|
-
const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
|
|
463
|
-
return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
|
|
464
|
-
}
|
|
465
|
-
function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
|
|
466
|
-
const boundaries = new Map;
|
|
467
|
-
for (const key of pageKeys) {
|
|
468
|
-
const segments = getRootBoundarySegments(key);
|
|
469
|
-
if (!segments)
|
|
470
|
-
continue;
|
|
471
|
-
if (!isRootBoundarySegments(segments, basePaths))
|
|
472
|
-
continue;
|
|
473
|
-
const id = segments.join("/");
|
|
474
|
-
boundaries.set(id, {
|
|
475
|
-
sourceKey: key,
|
|
476
|
-
sourceAbsPath: path4.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
|
|
477
|
-
segments
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
if (boundaries.size === 0)
|
|
481
|
-
boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
482
|
-
return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
|
|
483
|
-
}
|
|
484
|
-
function hasAncestorRootBoundary(boundary, boundaries) {
|
|
485
|
-
return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
|
|
486
|
-
}
|
|
487
|
-
function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
|
|
488
|
-
const rootLayoutKey = pageKeys.find((key) => {
|
|
489
|
-
const segments = getRootBoundarySegments(key);
|
|
490
|
-
return segments !== null && segments.length === 0;
|
|
491
|
-
});
|
|
492
|
-
return rootLayoutKey ? path4.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
493
|
-
}
|
|
494
|
-
async function assertEnvClientConvention(appCwdPath, appName) {
|
|
495
|
-
const envPath = path4.join(appCwdPath, "env", "env.client.ts");
|
|
496
|
-
if (!await Bun.file(envPath).exists()) {
|
|
497
|
-
throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
|
|
501
|
-
const absPath = path4.join(path4.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
502
|
-
await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
503
|
-
|
|
504
|
-
export const allDictionary = getAllDictionary();
|
|
505
|
-
`);
|
|
506
|
-
return absPath;
|
|
507
|
-
}
|
|
508
|
-
async function writeGeneratedRootLayoutFile(opts) {
|
|
509
|
-
await assertEnvClientConvention(opts.appCwdPath, opts.appName);
|
|
510
|
-
const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
|
|
511
|
-
const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
|
|
512
|
-
const dictMacroSpecifier = dictMacroAbsPath ? importSpecifier(absPath, dictMacroAbsPath) : null;
|
|
513
|
-
const sourceSpecifier = opts.boundary.sourceAbsPath ? importSpecifier(absPath, opts.boundary.sourceAbsPath) : null;
|
|
514
|
-
const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
|
|
515
|
-
const inheritedSourceSpecifier = inheritedSourceAbsPath ? importSpecifier(absPath, inheritedSourceAbsPath) : null;
|
|
516
|
-
const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
|
|
517
|
-
void st;
|
|
518
|
-
` : `import "@apps/${opts.appName}/client";
|
|
519
|
-
`;
|
|
520
|
-
const inheritedLabel = inheritedSourceAbsPath ? path4.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
|
|
521
|
-
const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
|
|
522
|
-
const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
|
|
523
|
-
` : `const inheritedLayout: LayoutModule = {};
|
|
524
|
-
`;
|
|
525
|
-
const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
|
|
526
|
-
const userLabel = opts.boundary.sourceAbsPath ? path4.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
|
|
527
|
-
const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
|
|
528
|
-
const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
|
|
529
|
-
const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
|
|
530
|
-
` : `const UserLayout = ({ children }: LayoutProps) => children;
|
|
531
|
-
const userLayout: LayoutModule = {};
|
|
532
|
-
`;
|
|
533
|
-
const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
|
|
534
|
-
const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
|
|
535
|
-
const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
|
|
536
|
-
const layoutBinding = isAsyncUserLayout ? ` const layout =
|
|
537
|
-
process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
|
|
538
|
-
? await UserLayout({ params, searchParams, children })
|
|
539
|
-
: ${userLayoutElement};
|
|
540
|
-
` : "";
|
|
541
|
-
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
542
|
-
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
543
|
-
const headLayouts = opts.includeSystemProvider ? "[inheritedLayout, userLayout]" : "[userLayout]";
|
|
544
|
-
const layoutExports = `export async function generateHead(props: PageProps) {
|
|
545
|
-
const heads = await Promise.all(
|
|
546
|
-
${headLayouts}.map((layout) => (layout.generateHead ? layout.generateHead(props) : layout.head)),
|
|
547
|
-
);
|
|
548
|
-
return RouteHead.mergeNodes(heads);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
552
|
-
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
553
|
-
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
554
|
-
|
|
555
|
-
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
556
|
-
${layoutBinding} return `;
|
|
557
|
-
const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
558
|
-
import { loadFonts, resolveRouteModule, RouteHead } from "akanjs/client";
|
|
559
|
-
import type { ReactNode } from "react";
|
|
560
|
-
import { System } from "akanjs/ui";
|
|
561
|
-
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
562
|
-
import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
|
|
563
|
-
${clientImport}${inheritedImport}${userImport}
|
|
564
|
-
const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
|
|
565
|
-
const defaultFonts = userFonts.filter((font) => font.default);
|
|
566
|
-
if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
|
|
567
|
-
const defaultFont = defaultFonts[0];
|
|
568
|
-
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
569
|
-
export const theme = userLayout.theme ?? inheritedLayout.theme;
|
|
570
|
-
export const viewport = userLayout.viewport ?? inheritedLayout.viewport;
|
|
571
|
-
export const manifest = userLayout.manifest ?? inheritedLayout.manifest;
|
|
572
|
-
|
|
573
|
-
${layoutExports}(
|
|
574
|
-
<System.Provider
|
|
575
|
-
of={GeneratedLayout as never}
|
|
576
|
-
appName=${JSON.stringify(opts.appName)}
|
|
577
|
-
${prefix ? `prefix=${JSON.stringify(prefix)}
|
|
578
|
-
` : ""}params={params}
|
|
579
|
-
manifest={manifest}
|
|
580
|
-
env={env}
|
|
581
|
-
theme={theme}
|
|
582
|
-
viewport={viewport}
|
|
583
|
-
fonts={loadFonts(userFonts)}
|
|
584
|
-
className={defaultFontClassName}
|
|
585
|
-
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
586
|
-
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
587
|
-
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
|
|
588
|
-
allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
|
|
589
|
-
>
|
|
590
|
-
${layoutChild}
|
|
591
|
-
</System.Provider>
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
595
|
-
import { resolveRouteModule, RouteHead } from "akanjs/client";
|
|
596
|
-
import type { ReactNode } from "react";
|
|
597
|
-
${inheritedImport}${userImport}
|
|
598
|
-
${layoutExports}${layoutReturn};
|
|
599
|
-
}
|
|
600
|
-
`;
|
|
601
|
-
await Bun.write(absPath, source);
|
|
602
|
-
return absPath;
|
|
603
|
-
}
|
|
604
|
-
async function resolveSsrPageEntries(opts) {
|
|
605
|
-
const absPageDir = path4.resolve(opts.appCwdPath, "page");
|
|
606
|
-
const hasSt = await Bun.file(path4.join(opts.appCwdPath, "lib", "st.ts")).exists();
|
|
607
|
-
const basePaths = opts.basePaths ?? [];
|
|
608
|
-
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
609
|
-
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
610
|
-
const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
|
|
611
|
-
const segments = getRootBoundarySegments(key);
|
|
612
|
-
return segments !== null && isRootBoundarySegments(segments, basePaths);
|
|
613
|
-
}));
|
|
614
|
-
const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
|
|
615
|
-
const userAbsPath = path4.resolve(absPageDir, key);
|
|
616
|
-
if (OVERRIDES_KEY_RE.test(key)) {
|
|
617
|
-
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
618
|
-
appCwdPath: opts.appCwdPath,
|
|
619
|
-
key,
|
|
620
|
-
userAbsPath
|
|
621
|
-
});
|
|
622
|
-
return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
|
|
623
|
-
}
|
|
624
|
-
return { key, moduleAbsPath: userAbsPath };
|
|
625
|
-
}));
|
|
626
|
-
const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
|
|
627
|
-
key: implicitRootLayoutKey(boundary.segments),
|
|
628
|
-
moduleAbsPath: await writeGeneratedRootLayoutFile({
|
|
629
|
-
appCwdPath: opts.appCwdPath,
|
|
630
|
-
appName: opts.appName,
|
|
631
|
-
boundary,
|
|
632
|
-
rootSourceAbsPath,
|
|
633
|
-
includeStInit: hasSt && boundary.segments.length === 0,
|
|
634
|
-
includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
|
|
635
|
-
}),
|
|
636
|
-
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
|
|
637
|
-
})));
|
|
638
|
-
const appCwdAbsPath = path4.resolve(opts.appCwdPath);
|
|
639
|
-
await Promise.all([
|
|
640
|
-
pruneGeneratedFiles(path4.join(appCwdAbsPath, IMPLICIT_OVERRIDES_DIR), base.filter(({ key }) => OVERRIDES_KEY_RE.test(key)).map(({ moduleAbsPath }) => moduleAbsPath)),
|
|
641
|
-
pruneGeneratedFiles(path4.join(appCwdAbsPath, IMPLICIT_LAYOUT_DIR), generated.map(({ moduleAbsPath }) => moduleAbsPath))
|
|
642
|
-
]);
|
|
643
|
-
return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
|
|
644
|
-
}
|
|
645
|
-
async function resolveSsrPageEntriesForApp(app, pageKeys) {
|
|
646
|
-
const config = await app.getConfig();
|
|
647
|
-
return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// pkgs/@akanjs/devkit/codegenLock.ts
|
|
651
|
-
import { mkdir as mkdir2, open, readFile, rm as rm3, stat } from "fs/promises";
|
|
652
|
-
import path5 from "path";
|
|
653
|
-
import { Logger as Logger2 } from "akanjs/common";
|
|
654
|
-
|
|
655
|
-
class CodegenLock {
|
|
656
|
-
static fileName = "codegen.lock";
|
|
657
|
-
static waitTimeoutMs = 1e4;
|
|
658
|
-
static unknownHolderStaleMs = 60000;
|
|
659
|
-
static #pollMs = 25;
|
|
660
|
-
static #logger = new Logger2("CodegenLock");
|
|
661
|
-
static #queue = Promise.resolve();
|
|
662
|
-
static pathIn(workspaceRoot) {
|
|
663
|
-
return path5.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
|
|
664
|
-
}
|
|
665
|
-
static async run(workspaceRoot, label, fn) {
|
|
666
|
-
const ahead = CodegenLock.#queue;
|
|
667
|
-
let done;
|
|
668
|
-
CodegenLock.#queue = new Promise((resolve) => {
|
|
669
|
-
done = resolve;
|
|
670
|
-
});
|
|
671
|
-
try {
|
|
672
|
-
await CodegenLock.#waitForQueue(ahead, label);
|
|
673
|
-
return await CodegenLock.#withFileLock(workspaceRoot, label, fn);
|
|
674
|
-
} finally {
|
|
675
|
-
done();
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
static async#waitForQueue(ahead, label) {
|
|
679
|
-
let timer = null;
|
|
680
|
-
const expired = new Promise((resolve) => {
|
|
681
|
-
timer = setTimeout(() => resolve("expired"), CodegenLock.waitTimeoutMs);
|
|
682
|
-
});
|
|
683
|
-
try {
|
|
684
|
-
if (await Promise.race([ahead.then(() => "done"), expired]) === "expired")
|
|
685
|
-
CodegenLock.#logger.warn(`codegen lock queued past ${CodegenLock.waitTimeoutMs}ms in this process; continuing without waiting (${label})`);
|
|
686
|
-
} finally {
|
|
687
|
-
if (timer)
|
|
688
|
-
clearTimeout(timer);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
static async#withFileLock(workspaceRoot, label, fn) {
|
|
692
|
-
const lockPath = CodegenLock.pathIn(workspaceRoot);
|
|
693
|
-
await mkdir2(path5.dirname(lockPath), { recursive: true }).catch(() => {
|
|
694
|
-
return;
|
|
695
|
-
});
|
|
696
|
-
const held = await CodegenLock.#acquire(lockPath, label);
|
|
697
|
-
try {
|
|
698
|
-
return await fn();
|
|
699
|
-
} finally {
|
|
700
|
-
if (held)
|
|
701
|
-
await rm3(lockPath, { force: true }).catch(() => {
|
|
702
|
-
return;
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
static async#acquire(lockPath, label) {
|
|
707
|
-
const deadline = Date.now() + CodegenLock.waitTimeoutMs;
|
|
708
|
-
for (;; ) {
|
|
709
|
-
const handle = await open(lockPath, "wx").catch(() => null);
|
|
710
|
-
if (handle) {
|
|
711
|
-
await handle.writeFile(JSON.stringify({ pid: process.pid, at: Date.now(), label })).catch(() => {
|
|
712
|
-
return;
|
|
713
|
-
});
|
|
714
|
-
await handle.close().catch(() => {
|
|
715
|
-
return;
|
|
716
|
-
});
|
|
717
|
-
return true;
|
|
718
|
-
}
|
|
719
|
-
if (await CodegenLock.#reclaimIfAbandoned(lockPath))
|
|
720
|
-
continue;
|
|
721
|
-
if (Date.now() >= deadline) {
|
|
722
|
-
CodegenLock.#logger.warn(`codegen lock at ${lockPath} held past ${CodegenLock.waitTimeoutMs}ms; continuing without it (${label})`);
|
|
723
|
-
return false;
|
|
724
|
-
}
|
|
725
|
-
await Bun.sleep(CodegenLock.#pollMs);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
static async#reclaimIfAbandoned(lockPath) {
|
|
729
|
-
const info = await stat(lockPath).catch(() => null);
|
|
730
|
-
if (!info)
|
|
731
|
-
return true;
|
|
732
|
-
const holder = CodegenLock.#parseHolder(await readFile(lockPath, "utf8").catch(() => ""));
|
|
733
|
-
if (holder) {
|
|
734
|
-
if (CodegenLock.#isAlive(holder.pid))
|
|
735
|
-
return false;
|
|
736
|
-
} else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
|
|
737
|
-
return false;
|
|
738
|
-
await rm3(lockPath, { force: true }).catch(() => {
|
|
739
|
-
return;
|
|
740
|
-
});
|
|
741
|
-
return true;
|
|
742
|
-
}
|
|
743
|
-
static #parseHolder(raw) {
|
|
744
|
-
try {
|
|
745
|
-
const parsed = JSON.parse(raw);
|
|
746
|
-
if (typeof parsed.pid !== "number" || typeof parsed.at !== "number")
|
|
747
|
-
return null;
|
|
748
|
-
return { pid: parsed.pid, at: parsed.at, label: typeof parsed.label === "string" ? parsed.label : "" };
|
|
749
|
-
} catch {
|
|
750
|
-
return null;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
static #isAlive(pid) {
|
|
754
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
755
|
-
return false;
|
|
756
|
-
try {
|
|
757
|
-
process.kill(pid, 0);
|
|
758
|
-
return true;
|
|
759
|
-
} catch (error) {
|
|
760
|
-
return error.code === "EPERM";
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// pkgs/@akanjs/devkit/spinner.ts
|
|
766
|
-
import ora from "ora";
|
|
767
|
-
|
|
768
|
-
class Spinner {
|
|
769
|
-
static padding = 12;
|
|
770
|
-
static oraOptions = { discardStdin: false };
|
|
771
|
-
static canAnimate(stream = process.stderr) {
|
|
772
|
-
return !stream.isTTY || stream.columns > 0;
|
|
773
|
-
}
|
|
774
|
-
spinner;
|
|
775
|
-
stopWatch = null;
|
|
776
|
-
startAt = new Date;
|
|
777
|
-
prefix;
|
|
778
|
-
message;
|
|
779
|
-
enableSpin;
|
|
780
|
-
constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
|
|
781
|
-
Spinner.padding = Math.max(Spinner.padding, prefix.length);
|
|
782
|
-
this.prefix = prefix;
|
|
783
|
-
this.message = message;
|
|
784
|
-
this.spinner = ora({ ...Spinner.oraOptions, text: message });
|
|
785
|
-
this.spinner.prefixText = prefix.padStart(Spinner.padding, " ");
|
|
786
|
-
this.spinner.indent = indent;
|
|
787
|
-
this.enableSpin = enableSpin && Spinner.canAnimate();
|
|
788
|
-
}
|
|
789
|
-
start() {
|
|
790
|
-
this.startAt = new Date;
|
|
791
|
-
if (this.enableSpin) {
|
|
792
|
-
this.spinner.start();
|
|
793
|
-
this.stopWatch = setInterval(() => {
|
|
794
|
-
this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
|
|
795
|
-
this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
|
|
796
|
-
}, 1000);
|
|
797
|
-
} else
|
|
798
|
-
this.spinner.info();
|
|
799
|
-
return this;
|
|
800
|
-
}
|
|
801
|
-
succeed(message) {
|
|
802
|
-
this.spinner.succeed(`${message} (${this.#getElapsedTimeStr()})`);
|
|
803
|
-
this.#reset();
|
|
804
|
-
}
|
|
805
|
-
fail(message) {
|
|
806
|
-
this.spinner.fail(`${message} (${this.#getElapsedTimeStr()})`);
|
|
807
|
-
this.#reset();
|
|
808
|
-
}
|
|
809
|
-
isSpinning() {
|
|
810
|
-
return this.spinner.isSpinning;
|
|
811
|
-
}
|
|
812
|
-
#reset() {
|
|
813
|
-
if (this.stopWatch)
|
|
814
|
-
clearInterval(this.stopWatch);
|
|
815
|
-
this.stopWatch = null;
|
|
816
|
-
}
|
|
817
|
-
#getElapsedTimeStr({ floor = false } = {}) {
|
|
818
|
-
const ms = Date.now() - this.startAt.getTime();
|
|
819
|
-
if (ms < 1000)
|
|
820
|
-
return `${ms}ms`;
|
|
821
|
-
const s = Math.floor(ms / 1000);
|
|
822
|
-
if (s < 60)
|
|
823
|
-
return `${floor ? Math.floor(ms / 1000) : Math.floor(ms / 100) / 10}s`;
|
|
824
|
-
const m = Math.floor(s / 60);
|
|
825
|
-
return `${m}m ${s % 60}s`;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// pkgs/@akanjs/devkit/executors.ts
|
|
830
|
-
import {
|
|
831
|
-
exec,
|
|
832
|
-
fork,
|
|
833
|
-
spawn as spawn2
|
|
834
|
-
} from "child_process";
|
|
835
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
836
|
-
import {
|
|
837
|
-
copyFile,
|
|
838
|
-
cp as cpEntry,
|
|
839
|
-
lstat,
|
|
840
|
-
mkdir as mkdir3,
|
|
841
|
-
readdir as readDirEntries,
|
|
842
|
-
realpath,
|
|
843
|
-
rm as rm4,
|
|
844
|
-
stat as stat2,
|
|
845
|
-
symlink
|
|
846
|
-
} from "fs/promises";
|
|
847
|
-
import path13 from "path";
|
|
848
|
-
import { pathToFileURL } from "url";
|
|
849
|
-
import {
|
|
850
|
-
capitalize,
|
|
851
|
-
getPageSourceFileViolation,
|
|
852
|
-
isRouteSourceFile,
|
|
853
|
-
Logger as Logger3,
|
|
854
|
-
parseRouteModuleKey,
|
|
855
|
-
toError as toError2,
|
|
856
|
-
validatePageSourceFile,
|
|
857
|
-
validateSubRoutePageKey
|
|
858
|
-
} from "akanjs/common";
|
|
859
|
-
var {$ } = globalThis.Bun;
|
|
860
|
-
import chalk2 from "chalk";
|
|
861
|
-
|
|
862
|
-
// pkgs/@akanjs/devkit/agentsIndex.ts
|
|
863
|
-
var recipeScannerLoad = null;
|
|
864
|
-
var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-4b69ybe0.js");
|
|
865
|
-
var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
|
|
866
|
-
var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
|
|
867
|
-
var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
|
|
868
|
-
var stampBlockVersion = (block, version) => `${AGENT_VERSION_PREFIX}${version} -->
|
|
869
|
-
|
|
870
|
-
${block}`;
|
|
871
|
-
var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
|
|
872
|
-
var readDevkitVersion = async () => {
|
|
873
|
-
const { readFile } = await import("fs/promises");
|
|
874
|
-
const { getDirname } = await import("./fileSys-wzzrntkw.js");
|
|
875
|
-
try {
|
|
876
|
-
const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
|
|
877
|
-
return JSON.parse(raw).version ?? null;
|
|
878
|
-
} catch {
|
|
879
|
-
return null;
|
|
880
|
-
}
|
|
881
|
-
};
|
|
882
|
-
var upsertAgentBlock = (existing, block) => {
|
|
883
|
-
const managed = `${AGENT_BLOCK_START}
|
|
884
|
-
${block}
|
|
885
|
-
${AGENT_BLOCK_END}`;
|
|
886
|
-
const startIndex = existing.indexOf(AGENT_BLOCK_START);
|
|
887
|
-
const endIndex = existing.indexOf(AGENT_BLOCK_END);
|
|
888
|
-
if (startIndex >= 0 && endIndex > startIndex) {
|
|
889
|
-
return `${existing.slice(0, startIndex)}${managed}${existing.slice(endIndex + AGENT_BLOCK_END.length)}`;
|
|
890
|
-
}
|
|
891
|
-
return `${existing.replace(/\s*$/, "")}
|
|
892
|
-
|
|
893
|
-
${managed}
|
|
894
|
-
`;
|
|
895
|
-
};
|
|
896
|
-
var extractAgentBlock = (content) => {
|
|
897
|
-
const startIndex = content.indexOf(AGENT_BLOCK_START);
|
|
898
|
-
const endIndex = content.indexOf(AGENT_BLOCK_END);
|
|
899
|
-
if (startIndex < 0 || endIndex <= startIndex)
|
|
900
|
-
return null;
|
|
901
|
-
return content.slice(startIndex + AGENT_BLOCK_START.length, endIndex).trim();
|
|
902
|
-
};
|
|
903
|
-
var signatureOf = (recipe) => {
|
|
904
|
-
const entries = Object.entries(recipe.variants);
|
|
905
|
-
if (entries.length === 0)
|
|
906
|
-
return "";
|
|
907
|
-
const parts = entries.map(([key, values]) => {
|
|
908
|
-
if (values.length === 1 && values[0] === "true")
|
|
909
|
-
return `${key}?`;
|
|
910
|
-
const def = recipe.defaultVariants?.[key];
|
|
911
|
-
return `${key}: ${values.map((value) => value === def ? `${value}*` : value).join("|")}`;
|
|
912
|
-
});
|
|
913
|
-
return `(${parts.join(" \xB7 ")})`;
|
|
914
|
-
};
|
|
915
|
-
var renderRecipeEntries = (recipes) => {
|
|
916
|
-
const groups = new Map;
|
|
917
|
-
for (const recipe of recipes)
|
|
918
|
-
groups.set(recipe.importFrom, [...groups.get(recipe.importFrom) ?? [], recipe]);
|
|
919
|
-
return [...groups.entries()].map(([importFrom, list]) => {
|
|
920
|
-
const items = list.sort((a, b) => a.name.localeCompare(b.name)).map((recipe) => `- \`${recipe.name}\`${signatureOf(recipe)}${recipe.doc ? ` \u2014 ${recipe.doc}` : ""}`).join(`
|
|
921
|
-
`);
|
|
922
|
-
return `Import from \`${importFrom}\`:
|
|
923
|
-
${items}`;
|
|
924
|
-
}).join(`
|
|
925
|
-
|
|
926
|
-
`);
|
|
927
|
-
};
|
|
928
|
-
var collectScopeRecipeSources = async (workspaceRoot, scope, libDeps) => {
|
|
929
|
-
const { collectRecipeSources } = await loadRecipeScanner();
|
|
930
|
-
const sources = [
|
|
931
|
-
...await collectRecipeSources(`${workspaceRoot}/${scope.type}s/${scope.name}/ui`, `@${scope.type}s/${scope.name}/ui`)
|
|
932
|
-
];
|
|
933
|
-
for (const lib of [...new Set(libDeps)].sort()) {
|
|
934
|
-
if (scope.type === "lib" && lib === scope.name)
|
|
935
|
-
continue;
|
|
936
|
-
sources.push(...await collectRecipeSources(`${workspaceRoot}/libs/${lib}/ui`, `@libs/${lib}/ui`));
|
|
937
|
-
}
|
|
938
|
-
return sources;
|
|
939
|
-
};
|
|
940
|
-
var scanScopeRecipes = async (workspaceRoot, scope, libDeps) => {
|
|
941
|
-
const { scanRecipes } = await loadRecipeScanner();
|
|
942
|
-
return scanRecipes(await collectScopeRecipeSources(workspaceRoot, scope, libDeps));
|
|
943
|
-
};
|
|
944
|
-
var renderScopeAgentBlock = (scope, recipes) => {
|
|
945
|
-
const scopePath = `${scope.type}s/${scope.name}`;
|
|
946
|
-
const intro = `## Recipes In Scope
|
|
947
|
-
|
|
948
|
-
UI recipes importable from \`${scopePath}\` code, **in addition to** the framework recipes indexed in the root
|
|
949
|
-
\`AGENTS.md\` \`## Recipes\`. Same contract: import by exact name, then \`<name>(variants?, className?)\` \u2014 the second
|
|
950
|
-
arg merges internally and takes an array too, so never wrap it in \`cn()\`. \`*\` marks the default, \`key?\` is a
|
|
951
|
-
boolean flag. Do not guess recipe names or import paths; this index is regenerated by \`akan sync ${scope.name}\`
|
|
952
|
-
and verified by \`akan lint ${scope.name}\`.`;
|
|
953
|
-
if (recipes.length === 0) {
|
|
954
|
-
return `${intro}
|
|
955
|
-
|
|
956
|
-
No scope recipes yet. Before inlining a repeated surface (card, box, tile, \u2026), reuse a framework recipe from the
|
|
957
|
-
root \`AGENTS.md\` or author one as \`${scopePath}/ui/Recipe/<name>.ts\` (one recipe per file, re-exported from that
|
|
958
|
-
folder's \`index.ts\`) \u2014 see the \`recipeRule\` guideline.`;
|
|
959
|
-
}
|
|
960
|
-
return `${intro}
|
|
961
|
-
|
|
962
|
-
${renderRecipeEntries(recipes)}`;
|
|
963
|
-
};
|
|
964
|
-
var renderScopeAgentsMd = (scope, block) => `# ${scope.name} \u2014 Agent Guide
|
|
965
|
-
|
|
966
|
-
Scoped guide for coding agents working in \`${scope.type}s/${scope.name}\`. Workspace-wide conventions live in the
|
|
967
|
-
root \`AGENTS.md\`; this file carries what is importable from this ${scope.type === "app" ? "app" : "library"}. The
|
|
968
|
-
section between the \`akan:agent\` markers is regenerated by \`akan sync ${scope.name}\`; edit anything outside the
|
|
969
|
-
markers freely.
|
|
970
|
-
|
|
971
|
-
${AGENT_BLOCK_START}
|
|
972
|
-
${block}
|
|
973
|
-
${AGENT_BLOCK_END}
|
|
974
|
-
`;
|
|
975
|
-
var CLAUDE_COMMENT_RULE = `## Comments \u2014 Overrides Your Default
|
|
976
|
-
|
|
977
|
-
Write **no comments** unless the comment passes the test below. This is the rule agents break most often here, so it
|
|
978
|
-
is repeated outside the guide: a diff that adds a comment the test rejects is a diff to redo.
|
|
979
|
-
|
|
980
|
-
Before typing \`//\`, \`/*\`, or a doc block, ask \u2014 **does this sentence carry a fact that is nowhere in the code?**
|
|
981
|
-
|
|
982
|
-
- Restates the identifier, the signature, or the line under it \u2192 delete it.
|
|
983
|
-
- Labels a section (\`// helpers\`, \`// state\`) or narrates a step (\`// fetch the user\`, \`// then save\`) \u2192 delete it.
|
|
984
|
-
- JSDoc on an ordinary function, or a why/how preamble on ordinary logic \u2192 delete it.
|
|
985
|
-
- Explains the edit you just made, for whoever reads the diff \u2192 say it in your reply, not in the file.
|
|
986
|
-
- Names a vendor or protocol quirk, an infrastructure constraint, a library gotcha, security reasoning, a math
|
|
987
|
-
derivation, a domain field's business meaning, a state transition, or why an obvious alternative was rejected \u2192
|
|
988
|
-
keep it, one line.
|
|
989
|
-
|
|
990
|
-
That keep-list is exact \u2014 \`Comments\` in the guide is the full version. "It aids readability" and "this logic is
|
|
991
|
-
subtle" are not on it: rename or split the code instead. When you edit an existing file, match its density; if the
|
|
992
|
-
surrounding code carries none, your diff carries none.`;
|
|
993
|
-
var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
|
|
994
|
-
|
|
995
|
-
@AGENTS.md
|
|
996
|
-
|
|
997
|
-
${CLAUDE_COMMENT_RULE}
|
|
998
|
-
`;
|
|
999
|
-
|
|
1000
|
-
// pkgs/@akanjs/devkit/akanConfig/akanBin.ts
|
|
1001
|
-
import path6 from "path";
|
|
1002
|
-
|
|
1003
|
-
// pkgs/@akanjs/devkit/akanConfig/types.ts
|
|
1004
|
-
import { archs, binPlatforms, defaultAppPipeline } from "akanjs";
|
|
1005
|
-
|
|
1006
|
-
// pkgs/@akanjs/devkit/akanConfig/akanBin.ts
|
|
1007
|
-
class AkanBin {
|
|
1008
|
-
static archive = /\.(zip|tar\.gz|tgz|tar\.xz|txz|tar\.bz2|tbz2|tar)$/i;
|
|
1009
|
-
static fileName = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1010
|
-
static parse(declared, owner, baseDir) {
|
|
1011
|
-
if (declared === undefined)
|
|
1012
|
-
return {};
|
|
1013
|
-
if (!declared || typeof declared !== "object" || Array.isArray(declared))
|
|
1014
|
-
throw new Error(`${owner}: bin maps each executable's name to its platforms`);
|
|
1015
|
-
return Object.fromEntries(Object.entries(declared).map(([name, platforms]) => [name, AkanBin.#platforms(name, platforms, owner, baseDir)]));
|
|
1016
|
-
}
|
|
1017
|
-
static isArchive(source) {
|
|
1018
|
-
return AkanBin.archive.test("url" in source ? new URL(source.url).pathname : source.path);
|
|
1019
|
-
}
|
|
1020
|
-
static #platforms(name, platforms, owner, baseDir) {
|
|
1021
|
-
if (!AkanBin.fileName.test(name))
|
|
1022
|
-
throw new Error(`${owner}: bin.${name} is not a file name`);
|
|
1023
|
-
if (!platforms || typeof platforms !== "object" || Array.isArray(platforms))
|
|
1024
|
-
throw new Error(`${owner}: bin.${name} maps platforms (${binPlatforms.join(", ")}) to where the file comes from`);
|
|
1025
|
-
const resolved = {};
|
|
1026
|
-
for (const [platform, source] of Object.entries(platforms)) {
|
|
1027
|
-
if (!binPlatforms.includes(platform))
|
|
1028
|
-
throw new Error(`${owner}: bin.${name}.${platform} is not one of ${binPlatforms.join(", ")}`);
|
|
1029
|
-
resolved[platform] = AkanBin.#source(`${owner}: bin.${name}.${platform}`, source, baseDir);
|
|
1030
|
-
}
|
|
1031
|
-
return resolved;
|
|
1032
|
-
}
|
|
1033
|
-
static #source(at, source, baseDir) {
|
|
1034
|
-
if (!source || typeof source !== "object" || Array.isArray(source))
|
|
1035
|
-
throw new Error(`${at} must be { url, sha256 } or { path }`);
|
|
1036
|
-
const { url, sha256, path: localPath, file, ...rest } = source;
|
|
1037
|
-
const extra = Object.keys(rest);
|
|
1038
|
-
if (extra.length)
|
|
1039
|
-
throw new Error(`${at}: unknown key ${extra.join(", ")}`);
|
|
1040
|
-
if (url === undefined === (localPath === undefined))
|
|
1041
|
-
throw new Error(`${at} takes either url and sha256, or path`);
|
|
1042
|
-
const resolved = url !== undefined ? AkanBin.#urlSource(at, url, sha256) : AkanBin.#pathSource(at, localPath, sha256, baseDir);
|
|
1043
|
-
const archive = AkanBin.isArchive(resolved);
|
|
1044
|
-
if (file === undefined) {
|
|
1045
|
-
if (archive)
|
|
1046
|
-
throw new Error(`${at} names an archive: file picks the executable inside it`);
|
|
1047
|
-
return resolved;
|
|
1048
|
-
}
|
|
1049
|
-
if (!archive)
|
|
1050
|
-
throw new Error(`${at}: file picks the executable out of an archive, and this source is none`);
|
|
1051
|
-
const parts = typeof file === "string" ? file.split(/[\\/]/).filter((part) => part && part !== ".") : [];
|
|
1052
|
-
if (!parts.length || path6.isAbsolute(file) || parts.includes(".."))
|
|
1053
|
-
throw new Error(`${at}: file must be a path inside the archive (got ${JSON.stringify(file)})`);
|
|
1054
|
-
return { ...resolved, file: parts.join("/") };
|
|
1055
|
-
}
|
|
1056
|
-
static #urlSource(at, url, sha256) {
|
|
1057
|
-
if (typeof url !== "string" || !URL.canParse(url) || !/^https?:$/.test(new URL(url).protocol))
|
|
1058
|
-
throw new Error(`${at}: url must be an http(s) address`);
|
|
1059
|
-
if (typeof sha256 !== "string" || !/^[0-9a-f]{64}$/i.test(sha256))
|
|
1060
|
-
throw new Error(`${at}: sha256 must be the downloaded file's 64-digit hex digest`);
|
|
1061
|
-
return { url, sha256: sha256.toLowerCase() };
|
|
1062
|
-
}
|
|
1063
|
-
static #pathSource(at, localPath, sha256, baseDir) {
|
|
1064
|
-
if (typeof localPath !== "string" || !localPath)
|
|
1065
|
-
throw new Error(`${at}: path must name a file`);
|
|
1066
|
-
if (sha256 !== undefined)
|
|
1067
|
-
throw new Error(`${at}: sha256 checks a download, and a path source downloads nothing`);
|
|
1068
|
-
return { path: path6.resolve(baseDir, localPath) };
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
// pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
|
|
1072
|
-
import fs2 from "fs";
|
|
1073
|
-
import path7 from "path";
|
|
1074
|
-
import { fileURLToPath } from "url";
|
|
1075
|
-
import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
|
|
1076
|
-
import { resolveAkanI18nConfig } from "akanjs/common";
|
|
1077
|
-
var DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui", "akanjs/server"];
|
|
1078
|
-
var DEFAULT_OPTIMIZE_IMPORTS = [
|
|
1079
|
-
"lucide-react date-fns lodash-es ramda antd react-bootstrap ahooks @ant-design/icons @headlessui/react",
|
|
1080
|
-
"@headlessui-float/react @heroicons/react/20/solid @heroicons/react/24/solid @heroicons/react/24/outline",
|
|
1081
|
-
"@visx/visx @tremor/react rxjs @mui/material @mui/icons-material recharts react-use @material-ui/core",
|
|
1082
|
-
"@material-ui/icons @tabler/icons-react mui-core react-icons/*"
|
|
1083
|
-
].flatMap((line) => line.split(" "));
|
|
1084
|
-
var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
|
|
1085
|
-
var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
|
|
1086
|
-
var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
|
|
1087
|
-
var DEFAULT_AKAN_IMAGE_CONFIG = {
|
|
1088
|
-
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
|
1089
|
-
imageSizes: [32, 48, 64, 96, 128, 256, 384],
|
|
1090
|
-
formats: ["image/webp"],
|
|
1091
|
-
qualities: [75],
|
|
1092
|
-
minimumCacheTTL: 14400,
|
|
1093
|
-
remotePatterns: [],
|
|
1094
|
-
localPatterns: [{ pathname: "/**" }],
|
|
1095
|
-
dangerouslyAllowSVG: false,
|
|
1096
|
-
maximumRedirects: 3,
|
|
1097
|
-
fetchTimeoutMs: 7000,
|
|
1098
|
-
maxRemoteBytes: 25 * 1024 * 1024,
|
|
1099
|
-
maxConcurrency: 0
|
|
1100
|
-
};
|
|
1101
|
-
var normalizeIndexPath = (indexPath) => {
|
|
1102
|
-
const normalized = indexPath?.trim();
|
|
1103
|
-
if (!normalized)
|
|
1104
|
-
return;
|
|
1105
|
-
const path = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
1106
|
-
return path === "/" ? "/" : path;
|
|
1107
|
-
};
|
|
1108
|
-
var normalizeStringList = (values) => {
|
|
1109
|
-
const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
1110
|
-
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
1111
|
-
};
|
|
1112
|
-
var sanitizeAppIdSegment = (value) => {
|
|
1113
|
-
const cleaned = value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
1114
|
-
if (!cleaned)
|
|
1115
|
-
return "app";
|
|
1116
|
-
return /^[a-z]/.test(cleaned) ? cleaned : `app${cleaned}`;
|
|
1117
|
-
};
|
|
1118
|
-
var deriveDefaultAppId = (orgName, appName) => `com.${sanitizeAppIdSegment(orgName)}.${sanitizeAppIdSegment(appName)}`;
|
|
1119
|
-
var normalizeDeepLinkDomain = (domain) => {
|
|
1120
|
-
const normalized = domain.trim();
|
|
1121
|
-
if (!normalized)
|
|
1122
|
-
return "";
|
|
1123
|
-
try {
|
|
1124
|
-
const url = new URL(normalized.includes("://") ? normalized : `https://${normalized}`);
|
|
1125
|
-
return url.host.toLowerCase();
|
|
1126
|
-
} catch {
|
|
1127
|
-
return normalized.replace(/^https?:\/\//, "").replace(/\/+$/g, "").toLowerCase();
|
|
1128
|
-
}
|
|
1129
|
-
};
|
|
1130
|
-
var normalizeDeepLinks = (deepLinks) => {
|
|
1131
|
-
const schemes = normalizeStringList(deepLinks?.schemes);
|
|
1132
|
-
const domains = normalizeStringList(deepLinks?.domains?.map(normalizeDeepLinkDomain));
|
|
1133
|
-
if (!schemes && !domains)
|
|
1134
|
-
return;
|
|
1135
|
-
return { ...schemes ? { schemes } : {}, ...domains ? { domains } : {} };
|
|
1136
|
-
};
|
|
1137
|
-
var emptyLibContributions = () => ({
|
|
1138
|
-
externalLibs: [],
|
|
1139
|
-
trustedDependencies: [],
|
|
1140
|
-
bin: [],
|
|
1141
|
-
docker: { preRuns: [], postRuns: [] },
|
|
1142
|
-
keepFonts: []
|
|
1143
|
-
});
|
|
1144
|
-
var normalizePackageNames = (names, owner) => {
|
|
1145
|
-
if (names === undefined)
|
|
1146
|
-
return [];
|
|
1147
|
-
if (!Array.isArray(names) || names.some((name) => typeof name !== "string" || !name.trim()))
|
|
1148
|
-
throw new Error(`${owner}: trustedDependencies lists package names`);
|
|
1149
|
-
return [...new Set(names.map((name) => name.trim()))];
|
|
1150
|
-
};
|
|
1151
|
-
var normalizeKeepFonts = (keepFonts) => [
|
|
1152
|
-
...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean))
|
|
1153
|
-
];
|
|
1154
|
-
var dedupeDockerRuns = (runs) => {
|
|
1155
|
-
const byKey = new Map;
|
|
1156
|
-
for (const run of runs)
|
|
1157
|
-
byKey.set(typeof run === "string" ? run : JSON.stringify(run), run);
|
|
1158
|
-
return [...byKey.values()];
|
|
1159
|
-
};
|
|
1160
|
-
|
|
1161
|
-
class LibConfigRefusal extends Error {
|
|
598
|
+
class LibConfigRefusal extends Error {
|
|
1162
599
|
}
|
|
1163
600
|
|
|
1164
601
|
class AkanAppConfig {
|
|
@@ -1495,7 +932,7 @@ class AkanAppConfig {
|
|
|
1495
932
|
}
|
|
1496
933
|
#applyRoutes(routes = []) {
|
|
1497
934
|
for (const route of routes) {
|
|
1498
|
-
const basePath = route.basePath ? route.basePath
|
|
935
|
+
const basePath = route.basePath ? trimSlashes(route.basePath) : null;
|
|
1499
936
|
if (basePath !== null)
|
|
1500
937
|
this.basePaths.add(basePath);
|
|
1501
938
|
const domains = basePath !== null ? this.subRoutes.getOrInsert(basePath, new Set) : this.domains;
|
|
@@ -1597,7 +1034,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
1597
1034
|
static async from(app, { bustImportCache = false } = {}) {
|
|
1598
1035
|
const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
|
|
1599
1036
|
AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
|
|
1600
|
-
WorkspaceExecutor.getBaseDevEnv(
|
|
1037
|
+
WorkspaceExecutor.getBaseDevEnv(path3.join(app.workspace.workspaceRoot, ".env")),
|
|
1601
1038
|
app.workspace.getLibs(),
|
|
1602
1039
|
app.workspace.getPackageJson()
|
|
1603
1040
|
]);
|
|
@@ -1693,143 +1130,579 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
1693
1130
|
};
|
|
1694
1131
|
}
|
|
1695
1132
|
}
|
|
1696
|
-
var akanPackageJson = null;
|
|
1697
|
-
function getAkanPackageJson() {
|
|
1698
|
-
if (akanPackageJson)
|
|
1699
|
-
return akanPackageJson;
|
|
1700
|
-
const sourceDir =
|
|
1701
|
-
const packageJsonPaths = [
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
];
|
|
1706
|
-
try {
|
|
1707
|
-
packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
|
|
1708
|
-
} catch {}
|
|
1709
|
-
for (const packageJsonPath of packageJsonPaths) {
|
|
1710
|
-
try {
|
|
1711
|
-
akanPackageJson = JSON.parse(
|
|
1712
|
-
return akanPackageJson;
|
|
1713
|
-
} catch {}
|
|
1133
|
+
var akanPackageJson = null;
|
|
1134
|
+
function getAkanPackageJson() {
|
|
1135
|
+
if (akanPackageJson)
|
|
1136
|
+
return akanPackageJson;
|
|
1137
|
+
const sourceDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
1138
|
+
const packageJsonPaths = [
|
|
1139
|
+
path3.join(sourceDir, "../../../akanjs/package.json"),
|
|
1140
|
+
path3.join(process.cwd(), "pkgs/akanjs/package.json"),
|
|
1141
|
+
path3.join(path3.dirname(Bun.main), "node_modules/akanjs/package.json")
|
|
1142
|
+
];
|
|
1143
|
+
try {
|
|
1144
|
+
packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
|
|
1145
|
+
} catch {}
|
|
1146
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
1147
|
+
try {
|
|
1148
|
+
akanPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
1149
|
+
return akanPackageJson;
|
|
1150
|
+
} catch {}
|
|
1151
|
+
}
|
|
1152
|
+
akanPackageJson = {
|
|
1153
|
+
name: "akanjs",
|
|
1154
|
+
version: "0.0.0",
|
|
1155
|
+
description: "akanjs",
|
|
1156
|
+
dependencies: {}
|
|
1157
|
+
};
|
|
1158
|
+
return akanPackageJson;
|
|
1159
|
+
}
|
|
1160
|
+
function mergeImageConfig(config = {}) {
|
|
1161
|
+
return {
|
|
1162
|
+
...DEFAULT_AKAN_IMAGE_CONFIG,
|
|
1163
|
+
...config,
|
|
1164
|
+
deviceSizes: config.deviceSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.deviceSizes,
|
|
1165
|
+
imageSizes: config.imageSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.imageSizes,
|
|
1166
|
+
formats: config.formats ?? DEFAULT_AKAN_IMAGE_CONFIG.formats,
|
|
1167
|
+
qualities: config.qualities ?? DEFAULT_AKAN_IMAGE_CONFIG.qualities,
|
|
1168
|
+
remotePatterns: config.remotePatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.remotePatterns,
|
|
1169
|
+
localPatterns: config.localPatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.localPatterns
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
class AkanLibConfig {
|
|
1174
|
+
lib;
|
|
1175
|
+
externalLibs;
|
|
1176
|
+
trustedDependencies;
|
|
1177
|
+
bin;
|
|
1178
|
+
docker;
|
|
1179
|
+
assets;
|
|
1180
|
+
plugins;
|
|
1181
|
+
constructor(lib, config, plugins = []) {
|
|
1182
|
+
this.lib = lib;
|
|
1183
|
+
this.externalLibs = config?.externalLibs ?? [];
|
|
1184
|
+
const owner = `libs/${lib.name}/akan.config.ts`;
|
|
1185
|
+
this.trustedDependencies = normalizePackageNames(config?.trustedDependencies, owner);
|
|
1186
|
+
this.bin = AkanBin.parse(config?.bin, owner, lib.cwdPath);
|
|
1187
|
+
this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
|
|
1188
|
+
this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
|
|
1189
|
+
this.plugins = plugins;
|
|
1190
|
+
}
|
|
1191
|
+
static async from(lib, { bustImportCache = false } = {}) {
|
|
1192
|
+
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
1193
|
+
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
1194
|
+
const { plugins, ...config } = resolved ?? {};
|
|
1195
|
+
if ("pipeline" in config)
|
|
1196
|
+
throw new LibConfigRefusal(`libs/${lib.name}/akan.config.ts declares \`pipeline\`, which a lib does not take: pipelines belong to apps and the workspace root, and a lib's build-time work goes in an akan build plugin.`);
|
|
1197
|
+
if ("subspace" in config)
|
|
1198
|
+
throw new LibConfigRefusal(`libs/${lib.name}/akan.config.ts declares \`subspace\`, a workspace setting: it belongs in the workspace root akan.config.ts.`);
|
|
1199
|
+
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
//! need to refactor
|
|
1203
|
+
var shiftBuildNum = async (app, delta) => {
|
|
1204
|
+
const { buildNum } = (await AkanAppConfig.from(app)).native;
|
|
1205
|
+
const akanConfigPath = path3.join(app.cwdPath, "akan.config.ts");
|
|
1206
|
+
const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
|
|
1207
|
+
fs.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
|
|
1208
|
+
};
|
|
1209
|
+
var increaseBuildNum = async (app) => await shiftBuildNum(app, 1);
|
|
1210
|
+
var decreaseBuildNum = async (app) => await shiftBuildNum(app, -1);
|
|
1211
|
+
// pkgs/@akanjs/devkit/akanConfig/akanWorkspaceConfig.ts
|
|
1212
|
+
import path4 from "path";
|
|
1213
|
+
class AkanWorkspaceConfig {
|
|
1214
|
+
static fileName = "akan.config.ts";
|
|
1215
|
+
static #keys = ["subspace", "pipeline", "cloud"];
|
|
1216
|
+
static #loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
|
|
1217
|
+
static async from(workspace, { bustImportCache = false } = {}) {
|
|
1218
|
+
const file = path4.join(workspace.workspaceRoot, AkanWorkspaceConfig.fileName);
|
|
1219
|
+
if (!await FileSys2.fileExists(file))
|
|
1220
|
+
return new AkanWorkspaceConfig(null, {});
|
|
1221
|
+
const input = await AkanAppConfig.importConfigModule(workspace.workspaceRoot, { bustImportCache });
|
|
1222
|
+
return new AkanWorkspaceConfig(file, input);
|
|
1223
|
+
}
|
|
1224
|
+
file;
|
|
1225
|
+
subspace;
|
|
1226
|
+
pipeline;
|
|
1227
|
+
cloudHost;
|
|
1228
|
+
constructor(file, input) {
|
|
1229
|
+
this.file = file;
|
|
1230
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
1231
|
+
throw new Error("The workspace root akan.config.ts must default-export a WorkspaceConfig object.");
|
|
1232
|
+
for (const key of Object.keys(input))
|
|
1233
|
+
if (!AkanWorkspaceConfig.#keys.includes(key))
|
|
1234
|
+
throw new Error(`The workspace root akan.config.ts declares \`${key}\`, which it does not take: it holds \`subspace\`, \`pipeline\` and \`cloud\`, and app or lib settings belong in apps/<app>/akan.config.ts or libs/<lib>/akan.config.ts.`);
|
|
1235
|
+
const config = input;
|
|
1236
|
+
this.subspace = config.subspace ?? null;
|
|
1237
|
+
this.pipeline = config.pipeline ?? null;
|
|
1238
|
+
this.cloudHost = AkanWorkspaceConfig.#cloudHost(config.cloud);
|
|
1239
|
+
}
|
|
1240
|
+
static #cloudHost(cloud) {
|
|
1241
|
+
if (cloud === undefined)
|
|
1242
|
+
return null;
|
|
1243
|
+
const refuse = (why) => {
|
|
1244
|
+
throw new Error(`The workspace root akan.config.ts has cloud ${why}.`);
|
|
1245
|
+
};
|
|
1246
|
+
if (typeof cloud !== "object" || cloud === null || Array.isArray(cloud))
|
|
1247
|
+
return refuse("that is not an object");
|
|
1248
|
+
const unknown = Object.keys(cloud).filter((key) => key !== "host");
|
|
1249
|
+
if (unknown.length)
|
|
1250
|
+
return refuse(`keys it does not take: ${unknown.join(", ")} (only \`host\`)`);
|
|
1251
|
+
const { host } = cloud;
|
|
1252
|
+
if (host === undefined)
|
|
1253
|
+
return null;
|
|
1254
|
+
const url = typeof host === "string" && URL.canParse(host) ? new URL(host) : null;
|
|
1255
|
+
if (!url || url.search || url.hash || url.username || url.password)
|
|
1256
|
+
return refuse(`.host ${JSON.stringify(host)}, which is not a URL like "https://cloud.example.com"`);
|
|
1257
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && AkanWorkspaceConfig.#loopbackHosts.includes(url.hostname)))
|
|
1258
|
+
return refuse(`.host ${JSON.stringify(host)}: https only, or http on this computer`);
|
|
1259
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
1263
|
+
import { readdir, rm } from "fs/promises";
|
|
1264
|
+
import path6 from "path";
|
|
1265
|
+
|
|
1266
|
+
// pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
|
|
1267
|
+
import fs2 from "fs";
|
|
1268
|
+
import path5 from "path";
|
|
1269
|
+
|
|
1270
|
+
class AsyncDefaultExportDetector {
|
|
1271
|
+
static #typescriptLoad;
|
|
1272
|
+
static #loadTypescript() {
|
|
1273
|
+
AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
|
|
1274
|
+
return AsyncDefaultExportDetector.#typescriptLoad;
|
|
1275
|
+
}
|
|
1276
|
+
static async detect(moduleAbsPath) {
|
|
1277
|
+
try {
|
|
1278
|
+
const typescript = await AsyncDefaultExportDetector.#loadTypescript();
|
|
1279
|
+
const source = fs2.readFileSync(path5.resolve(moduleAbsPath), "utf8");
|
|
1280
|
+
const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, /\.[tj]sx$/.test(moduleAbsPath) ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS);
|
|
1281
|
+
return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
|
|
1282
|
+
} catch {
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
#ts;
|
|
1287
|
+
constructor(typescript) {
|
|
1288
|
+
this.#ts = typescript;
|
|
1289
|
+
}
|
|
1290
|
+
detectInSourceFile(sourceFile) {
|
|
1291
|
+
const ts = this.#ts;
|
|
1292
|
+
const asyncBindings = new Map;
|
|
1293
|
+
let defaultIdentifier = null;
|
|
1294
|
+
for (const statement of sourceFile.statements) {
|
|
1295
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
1296
|
+
if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
|
|
1297
|
+
return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
|
|
1298
|
+
}
|
|
1299
|
+
if (statement.name) {
|
|
1300
|
+
asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
|
|
1301
|
+
}
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (ts.isVariableStatement(statement)) {
|
|
1305
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1306
|
+
if (!ts.isIdentifier(declaration.name))
|
|
1307
|
+
continue;
|
|
1308
|
+
asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
|
|
1309
|
+
}
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
if (ts.isExportAssignment(statement)) {
|
|
1313
|
+
if (this.#isAsyncFunctionExpression(statement.expression))
|
|
1314
|
+
return true;
|
|
1315
|
+
if (ts.isCallExpression(statement.expression))
|
|
1316
|
+
return true;
|
|
1317
|
+
if (ts.isIdentifier(statement.expression))
|
|
1318
|
+
defaultIdentifier = statement.expression.text;
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
1322
|
+
for (const specifier of statement.exportClause.elements) {
|
|
1323
|
+
if (specifier.name.text !== "default")
|
|
1324
|
+
continue;
|
|
1325
|
+
defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
|
|
1330
|
+
}
|
|
1331
|
+
#hasModifier(node, kind) {
|
|
1332
|
+
const ts = this.#ts;
|
|
1333
|
+
return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
|
|
1334
|
+
}
|
|
1335
|
+
#isAsyncFunctionExpression(node) {
|
|
1336
|
+
const ts = this.#ts;
|
|
1337
|
+
return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
1342
|
+
var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
1343
|
+
var IMPLICIT_LAYOUT_DIR = path6.join(".akan", "generated", "root-layouts");
|
|
1344
|
+
var IMPLICIT_DICT_DIR = path6.join(".akan", "generated", "dict");
|
|
1345
|
+
var IMPLICIT_OVERRIDES_DIR = path6.join(".akan", "generated", "overrides");
|
|
1346
|
+
var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
1347
|
+
var importSpecifier = (fromAbsPath, toAbsPath) => {
|
|
1348
|
+
const rel = path6.relative(path6.dirname(fromAbsPath), toAbsPath).split(path6.sep).join("/");
|
|
1349
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1350
|
+
};
|
|
1351
|
+
async function writeGeneratedOverridesLayoutFile(opts) {
|
|
1352
|
+
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
1353
|
+
const absPath = path6.join(path6.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
1354
|
+
const source = `"use client";
|
|
1355
|
+
import { UiOverrideProvider } from "akanjs/ui";
|
|
1356
|
+
import { createElement, type ReactNode } from "react";
|
|
1357
|
+
import value from ${JSON.stringify(importSpecifier(absPath, opts.userAbsPath))};
|
|
1358
|
+
|
|
1359
|
+
export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
|
|
1360
|
+
return createElement(UiOverrideProvider, { value }, children);
|
|
1361
|
+
}
|
|
1362
|
+
`;
|
|
1363
|
+
await Bun.write(absPath, source);
|
|
1364
|
+
return absPath;
|
|
1365
|
+
}
|
|
1366
|
+
async function pruneGeneratedFiles(dirAbsPath, keepAbsPaths) {
|
|
1367
|
+
const keep = new Set(keepAbsPaths);
|
|
1368
|
+
const names = await readdir(dirAbsPath).catch(() => []);
|
|
1369
|
+
await Promise.all(names.map((name) => path6.join(dirAbsPath, name)).filter((absPath) => !keep.has(absPath)).map((absPath) => rm(absPath, { force: true })));
|
|
1370
|
+
}
|
|
1371
|
+
function getRootBoundarySegments(key) {
|
|
1372
|
+
const match = LAYOUT_KEY_RE.exec(key);
|
|
1373
|
+
if (!match)
|
|
1374
|
+
return null;
|
|
1375
|
+
const prefix = match[1]?.replace(/\/$/, "");
|
|
1376
|
+
if (!prefix)
|
|
1377
|
+
return [];
|
|
1378
|
+
return prefix.split("/").filter(Boolean);
|
|
1379
|
+
}
|
|
1380
|
+
function implicitRootLayoutKey(segments) {
|
|
1381
|
+
return `./${[...segments, "__root_layout"].join("/")}.tsx`;
|
|
1382
|
+
}
|
|
1383
|
+
function implicitRootLayoutAbsPath(appCwdPath, segments) {
|
|
1384
|
+
const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
|
|
1385
|
+
return path6.join(path6.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
1386
|
+
}
|
|
1387
|
+
function isRootBoundarySegments(segments, basePaths) {
|
|
1388
|
+
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
1389
|
+
if (firstVisibleIndex === -1)
|
|
1390
|
+
return segments.length <= 1;
|
|
1391
|
+
if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
|
|
1392
|
+
return false;
|
|
1393
|
+
const visible = segments.slice(firstVisibleIndex);
|
|
1394
|
+
const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
|
|
1395
|
+
return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
|
|
1396
|
+
}
|
|
1397
|
+
function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
|
|
1398
|
+
const boundaries = new Map;
|
|
1399
|
+
for (const key of pageKeys) {
|
|
1400
|
+
const segments = getRootBoundarySegments(key);
|
|
1401
|
+
if (!segments)
|
|
1402
|
+
continue;
|
|
1403
|
+
if (!isRootBoundarySegments(segments, basePaths))
|
|
1404
|
+
continue;
|
|
1405
|
+
const id = segments.join("/");
|
|
1406
|
+
boundaries.set(id, {
|
|
1407
|
+
sourceKey: key,
|
|
1408
|
+
sourceAbsPath: path6.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
|
|
1409
|
+
segments
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
if (boundaries.size === 0)
|
|
1413
|
+
boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
1414
|
+
return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
|
|
1415
|
+
}
|
|
1416
|
+
function hasAncestorRootBoundary(boundary, boundaries) {
|
|
1417
|
+
return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
|
|
1418
|
+
}
|
|
1419
|
+
function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
|
|
1420
|
+
const rootLayoutKey = pageKeys.find((key) => {
|
|
1421
|
+
const segments = getRootBoundarySegments(key);
|
|
1422
|
+
return segments !== null && segments.length === 0;
|
|
1423
|
+
});
|
|
1424
|
+
return rootLayoutKey ? path6.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
1425
|
+
}
|
|
1426
|
+
async function assertEnvClientConvention(appCwdPath, appName) {
|
|
1427
|
+
const envPath = path6.join(appCwdPath, "env", "env.client.ts");
|
|
1428
|
+
if (!await Bun.file(envPath).exists()) {
|
|
1429
|
+
throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
|
|
1714
1430
|
}
|
|
1715
|
-
akanPackageJson = {
|
|
1716
|
-
name: "akanjs",
|
|
1717
|
-
version: "0.0.0",
|
|
1718
|
-
description: "akanjs",
|
|
1719
|
-
dependencies: {}
|
|
1720
|
-
};
|
|
1721
|
-
return akanPackageJson;
|
|
1722
1431
|
}
|
|
1723
|
-
function
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1432
|
+
async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
|
|
1433
|
+
const absPath = path6.join(path6.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
1434
|
+
await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
1435
|
+
|
|
1436
|
+
export const allDictionary = getAllDictionary();
|
|
1437
|
+
`);
|
|
1438
|
+
return absPath;
|
|
1439
|
+
}
|
|
1440
|
+
async function writeGeneratedRootLayoutFile(opts) {
|
|
1441
|
+
await assertEnvClientConvention(opts.appCwdPath, opts.appName);
|
|
1442
|
+
const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
|
|
1443
|
+
const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
|
|
1444
|
+
const dictMacroSpecifier = dictMacroAbsPath ? importSpecifier(absPath, dictMacroAbsPath) : null;
|
|
1445
|
+
const sourceSpecifier = opts.boundary.sourceAbsPath ? importSpecifier(absPath, opts.boundary.sourceAbsPath) : null;
|
|
1446
|
+
const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
|
|
1447
|
+
const inheritedSourceSpecifier = inheritedSourceAbsPath ? importSpecifier(absPath, inheritedSourceAbsPath) : null;
|
|
1448
|
+
const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
|
|
1449
|
+
void st;
|
|
1450
|
+
` : `import "@apps/${opts.appName}/client";
|
|
1451
|
+
`;
|
|
1452
|
+
const inheritedLabel = inheritedSourceAbsPath ? path6.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
|
|
1453
|
+
const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
|
|
1454
|
+
const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
|
|
1455
|
+
` : `const inheritedLayout: LayoutModule = {};
|
|
1456
|
+
`;
|
|
1457
|
+
const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
|
|
1458
|
+
const userLabel = opts.boundary.sourceAbsPath ? path6.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
|
|
1459
|
+
const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
|
|
1460
|
+
const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
|
|
1461
|
+
const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
|
|
1462
|
+
` : `const UserLayout = ({ children }: LayoutProps) => children;
|
|
1463
|
+
const userLayout: LayoutModule = {};
|
|
1464
|
+
`;
|
|
1465
|
+
const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
|
|
1466
|
+
const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
|
|
1467
|
+
const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
|
|
1468
|
+
const layoutBinding = isAsyncUserLayout ? ` const layout =
|
|
1469
|
+
process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
|
|
1470
|
+
? await UserLayout({ params, searchParams, children })
|
|
1471
|
+
: ${userLayoutElement};
|
|
1472
|
+
` : "";
|
|
1473
|
+
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
1474
|
+
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
1475
|
+
const headLayouts = opts.includeSystemProvider ? "[inheritedLayout, userLayout]" : "[userLayout]";
|
|
1476
|
+
const layoutExports = `export async function generateHead(props: PageProps) {
|
|
1477
|
+
const heads = await Promise.all(
|
|
1478
|
+
${headLayouts}.map((layout) => (layout.generateHead ? layout.generateHead(props) : layout.head)),
|
|
1479
|
+
);
|
|
1480
|
+
return RouteHead.mergeNodes(heads);
|
|
1734
1481
|
}
|
|
1735
1482
|
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1483
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
1484
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
1485
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
1486
|
+
export const Loading = userLayout.Loading;
|
|
1487
|
+
|
|
1488
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
1489
|
+
${layoutBinding} return `;
|
|
1490
|
+
const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
1491
|
+
import { loadFonts, resolveRouteModule, RouteHead } from "akanjs/client";
|
|
1492
|
+
import type { ReactNode } from "react";
|
|
1493
|
+
import { System } from "akanjs/ui";
|
|
1494
|
+
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
1495
|
+
import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
|
|
1496
|
+
${clientImport}${inheritedImport}${userImport}
|
|
1497
|
+
const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
|
|
1498
|
+
const defaultFonts = userFonts.filter((font) => font.default);
|
|
1499
|
+
if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
|
|
1500
|
+
const defaultFont = defaultFonts[0];
|
|
1501
|
+
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
1502
|
+
export const theme = userLayout.theme ?? inheritedLayout.theme;
|
|
1503
|
+
export const viewport = userLayout.viewport ?? inheritedLayout.viewport;
|
|
1504
|
+
export const manifest = userLayout.manifest ?? inheritedLayout.manifest;
|
|
1505
|
+
|
|
1506
|
+
${layoutExports}(
|
|
1507
|
+
<System.Provider
|
|
1508
|
+
of={GeneratedLayout as never}
|
|
1509
|
+
appName=${JSON.stringify(opts.appName)}
|
|
1510
|
+
${prefix ? `prefix=${JSON.stringify(prefix)}
|
|
1511
|
+
` : ""}params={params}
|
|
1512
|
+
manifest={manifest}
|
|
1513
|
+
env={env}
|
|
1514
|
+
theme={theme}
|
|
1515
|
+
viewport={viewport}
|
|
1516
|
+
fonts={loadFonts(userFonts)}
|
|
1517
|
+
className={defaultFontClassName}
|
|
1518
|
+
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
1519
|
+
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
1520
|
+
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
|
|
1521
|
+
allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
|
|
1522
|
+
>
|
|
1523
|
+
${layoutChild}
|
|
1524
|
+
</System.Provider>
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
1528
|
+
import { resolveRouteModule, RouteHead } from "akanjs/client";
|
|
1529
|
+
import type { ReactNode } from "react";
|
|
1530
|
+
${inheritedImport}${userImport}
|
|
1531
|
+
${layoutExports}${layoutReturn};
|
|
1532
|
+
}
|
|
1533
|
+
`;
|
|
1534
|
+
await Bun.write(absPath, source);
|
|
1535
|
+
return absPath;
|
|
1536
|
+
}
|
|
1537
|
+
async function resolveSsrPageEntries(opts) {
|
|
1538
|
+
const absPageDir = path6.resolve(opts.appCwdPath, "page");
|
|
1539
|
+
const hasSt = await Bun.file(path6.join(opts.appCwdPath, "lib", "st.ts")).exists();
|
|
1540
|
+
const basePaths = opts.basePaths ?? [];
|
|
1541
|
+
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
1542
|
+
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
1543
|
+
const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
|
|
1544
|
+
const segments = getRootBoundarySegments(key);
|
|
1545
|
+
return segments !== null && isRootBoundarySegments(segments, basePaths);
|
|
1546
|
+
}));
|
|
1547
|
+
const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
|
|
1548
|
+
const userAbsPath = path6.resolve(absPageDir, key);
|
|
1549
|
+
if (OVERRIDES_KEY_RE.test(key)) {
|
|
1550
|
+
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
1551
|
+
appCwdPath: opts.appCwdPath,
|
|
1552
|
+
key,
|
|
1553
|
+
userAbsPath
|
|
1554
|
+
});
|
|
1555
|
+
return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
|
|
1556
|
+
}
|
|
1557
|
+
return { key, moduleAbsPath: userAbsPath };
|
|
1558
|
+
}));
|
|
1559
|
+
const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
|
|
1560
|
+
key: implicitRootLayoutKey(boundary.segments),
|
|
1561
|
+
moduleAbsPath: await writeGeneratedRootLayoutFile({
|
|
1562
|
+
appCwdPath: opts.appCwdPath,
|
|
1563
|
+
appName: opts.appName,
|
|
1564
|
+
boundary,
|
|
1565
|
+
rootSourceAbsPath,
|
|
1566
|
+
includeStInit: hasSt && boundary.segments.length === 0,
|
|
1567
|
+
includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
|
|
1568
|
+
}),
|
|
1569
|
+
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
|
|
1570
|
+
})));
|
|
1571
|
+
const appCwdAbsPath = path6.resolve(opts.appCwdPath);
|
|
1572
|
+
await Promise.all([
|
|
1573
|
+
pruneGeneratedFiles(path6.join(appCwdAbsPath, IMPLICIT_OVERRIDES_DIR), base.filter(({ key }) => OVERRIDES_KEY_RE.test(key)).map(({ moduleAbsPath }) => moduleAbsPath)),
|
|
1574
|
+
pruneGeneratedFiles(path6.join(appCwdAbsPath, IMPLICIT_LAYOUT_DIR), generated.map(({ moduleAbsPath }) => moduleAbsPath))
|
|
1575
|
+
]);
|
|
1576
|
+
return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
|
|
1577
|
+
}
|
|
1578
|
+
async function resolveSsrPageEntriesForApp(app, pageKeys) {
|
|
1579
|
+
const config = await app.getConfig();
|
|
1580
|
+
return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// pkgs/@akanjs/devkit/codegenLock.ts
|
|
1584
|
+
import { mkdir, open, readFile, rm as rm2, stat } from "fs/promises";
|
|
1585
|
+
import path7 from "path";
|
|
1586
|
+
import { Logger as Logger2 } from "akanjs/common";
|
|
1587
|
+
|
|
1588
|
+
class CodegenLock {
|
|
1589
|
+
static fileName = "codegen.lock";
|
|
1590
|
+
static waitTimeoutMs = 1e4;
|
|
1591
|
+
static unknownHolderStaleMs = 60000;
|
|
1592
|
+
static #pollMs = 25;
|
|
1593
|
+
static #logger = new Logger2("CodegenLock");
|
|
1594
|
+
static #queue = Promise.resolve();
|
|
1595
|
+
static pathIn(workspaceRoot) {
|
|
1596
|
+
return path7.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
|
|
1753
1597
|
}
|
|
1754
|
-
static async
|
|
1755
|
-
const
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1598
|
+
static async run(workspaceRoot, label, fn) {
|
|
1599
|
+
const ahead = CodegenLock.#queue;
|
|
1600
|
+
let done;
|
|
1601
|
+
CodegenLock.#queue = new Promise((resolve) => {
|
|
1602
|
+
done = resolve;
|
|
1603
|
+
});
|
|
1604
|
+
try {
|
|
1605
|
+
await CodegenLock.#waitForQueue(ahead, label);
|
|
1606
|
+
return await CodegenLock.#withFileLock(workspaceRoot, label, fn);
|
|
1607
|
+
} finally {
|
|
1608
|
+
done();
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
static async#waitForQueue(ahead, label) {
|
|
1612
|
+
let timer = null;
|
|
1613
|
+
const expired = new Promise((resolve) => {
|
|
1614
|
+
timer = setTimeout(() => resolve("expired"), CodegenLock.waitTimeoutMs);
|
|
1615
|
+
});
|
|
1616
|
+
try {
|
|
1617
|
+
if (await Promise.race([ahead.then(() => "done"), expired]) === "expired")
|
|
1618
|
+
CodegenLock.#logger.warn(`codegen lock queued past ${CodegenLock.waitTimeoutMs}ms in this process; continuing without waiting (${label})`);
|
|
1619
|
+
} finally {
|
|
1620
|
+
if (timer)
|
|
1621
|
+
clearTimeout(timer);
|
|
1622
|
+
}
|
|
1763
1623
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
static #keys = ["subspace", "pipeline", "cloud"];
|
|
1779
|
-
static #loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
|
|
1780
|
-
static async from(workspace, { bustImportCache = false } = {}) {
|
|
1781
|
-
const file = path8.join(workspace.workspaceRoot, AkanWorkspaceConfig.fileName);
|
|
1782
|
-
if (!await FileSys2.fileExists(file))
|
|
1783
|
-
return new AkanWorkspaceConfig(null, {});
|
|
1784
|
-
const input = await AkanAppConfig.importConfigModule(workspace.workspaceRoot, { bustImportCache });
|
|
1785
|
-
return new AkanWorkspaceConfig(file, input);
|
|
1624
|
+
static async#withFileLock(workspaceRoot, label, fn) {
|
|
1625
|
+
const lockPath = CodegenLock.pathIn(workspaceRoot);
|
|
1626
|
+
await mkdir(path7.dirname(lockPath), { recursive: true }).catch(() => {
|
|
1627
|
+
return;
|
|
1628
|
+
});
|
|
1629
|
+
const held = await CodegenLock.#acquire(lockPath, label);
|
|
1630
|
+
try {
|
|
1631
|
+
return await fn();
|
|
1632
|
+
} finally {
|
|
1633
|
+
if (held)
|
|
1634
|
+
await rm2(lockPath, { force: true }).catch(() => {
|
|
1635
|
+
return;
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1786
1638
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1639
|
+
static async#acquire(lockPath, label) {
|
|
1640
|
+
const deadline = Date.now() + CodegenLock.waitTimeoutMs;
|
|
1641
|
+
for (;; ) {
|
|
1642
|
+
const handle = await open(lockPath, "wx").catch(() => null);
|
|
1643
|
+
if (handle) {
|
|
1644
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, at: Date.now(), label })).catch(() => {
|
|
1645
|
+
return;
|
|
1646
|
+
});
|
|
1647
|
+
await handle.close().catch(() => {
|
|
1648
|
+
return;
|
|
1649
|
+
});
|
|
1650
|
+
return true;
|
|
1651
|
+
}
|
|
1652
|
+
if (await CodegenLock.#reclaimIfAbandoned(lockPath))
|
|
1653
|
+
continue;
|
|
1654
|
+
if (Date.now() >= deadline) {
|
|
1655
|
+
CodegenLock.#logger.warn(`codegen lock at ${lockPath} held past ${CodegenLock.waitTimeoutMs}ms; continuing without it (${label})`);
|
|
1656
|
+
return false;
|
|
1657
|
+
}
|
|
1658
|
+
await Bun.sleep(CodegenLock.#pollMs);
|
|
1659
|
+
}
|
|
1802
1660
|
}
|
|
1803
|
-
static #
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1661
|
+
static async#reclaimIfAbandoned(lockPath) {
|
|
1662
|
+
const info = await stat(lockPath).catch(() => null);
|
|
1663
|
+
if (!info)
|
|
1664
|
+
return true;
|
|
1665
|
+
const holder = CodegenLock.#parseHolder(await readFile(lockPath, "utf8").catch(() => ""));
|
|
1666
|
+
if (holder) {
|
|
1667
|
+
if (CodegenLock.#isAlive(holder.pid))
|
|
1668
|
+
return false;
|
|
1669
|
+
} else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
|
|
1670
|
+
return false;
|
|
1671
|
+
await rm2(lockPath, { force: true }).catch(() => {
|
|
1672
|
+
return;
|
|
1673
|
+
});
|
|
1674
|
+
return true;
|
|
1675
|
+
}
|
|
1676
|
+
static #parseHolder(raw) {
|
|
1677
|
+
try {
|
|
1678
|
+
const parsed = JSON.parse(raw);
|
|
1679
|
+
if (typeof parsed.pid !== "number" || typeof parsed.at !== "number")
|
|
1680
|
+
return null;
|
|
1681
|
+
return { pid: parsed.pid, at: parsed.at, label: typeof parsed.label === "string" ? parsed.label : "" };
|
|
1682
|
+
} catch {
|
|
1816
1683
|
return null;
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
if (
|
|
1821
|
-
return
|
|
1822
|
-
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
static #isAlive(pid) {
|
|
1687
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
1688
|
+
return false;
|
|
1689
|
+
try {
|
|
1690
|
+
process.kill(pid, 0);
|
|
1691
|
+
return true;
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
return error.code === "EPERM";
|
|
1694
|
+
}
|
|
1823
1695
|
}
|
|
1824
1696
|
}
|
|
1697
|
+
|
|
1825
1698
|
// pkgs/@akanjs/devkit/jsonFormatter.ts
|
|
1826
|
-
import
|
|
1699
|
+
import path8 from "path";
|
|
1827
1700
|
|
|
1828
1701
|
class JsonFormatter {
|
|
1829
1702
|
static lineWidth = 120;
|
|
1830
1703
|
static stringify(value, filePath) {
|
|
1831
1704
|
const json = JSON.stringify(value, null, 2);
|
|
1832
|
-
if (
|
|
1705
|
+
if (path8.basename(filePath) === "package.json")
|
|
1833
1706
|
return `${json}
|
|
1834
1707
|
`;
|
|
1835
1708
|
return `${JsonFormatter.#print(JSON.parse(json), 0, { column: 0, trailing: 0 })}
|
|
@@ -1900,7 +1773,7 @@ ${" ".repeat(depth)}]`;
|
|
|
1900
1773
|
// pkgs/@akanjs/devkit/linter.ts
|
|
1901
1774
|
import { spawn } from "child_process";
|
|
1902
1775
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
1903
|
-
import
|
|
1776
|
+
import path9 from "path";
|
|
1904
1777
|
import chalk from "chalk";
|
|
1905
1778
|
var parseBiomeReport = (output) => {
|
|
1906
1779
|
for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
|
|
@@ -1966,13 +1839,13 @@ class Linter {
|
|
|
1966
1839
|
#biomeBin;
|
|
1967
1840
|
constructor(cwdPath) {
|
|
1968
1841
|
this.lintRoot = this.#findBiomeRootPath(cwdPath);
|
|
1969
|
-
this.configPath = Linter.#configPathIn(this.lintRoot) ??
|
|
1970
|
-
const localBiomeBin =
|
|
1842
|
+
this.configPath = Linter.#configPathIn(this.lintRoot) ?? path9.join(this.lintRoot, "biome.json");
|
|
1843
|
+
const localBiomeBin = path9.join(this.lintRoot, "node_modules", ".bin", Linter.biomeBinName);
|
|
1971
1844
|
this.#biomeBin = existsSync2(localBiomeBin) ? localBiomeBin : "biome";
|
|
1972
1845
|
}
|
|
1973
1846
|
static #configPathIn(dir) {
|
|
1974
1847
|
for (const fileName of BIOME_CONFIG_FILES) {
|
|
1975
|
-
const configPath =
|
|
1848
|
+
const configPath = path9.join(dir, fileName);
|
|
1976
1849
|
if (existsSync2(configPath))
|
|
1977
1850
|
return configPath;
|
|
1978
1851
|
}
|
|
@@ -1981,19 +1854,19 @@ class Linter {
|
|
|
1981
1854
|
#findBiomeRootPath(dir) {
|
|
1982
1855
|
if (Linter.#configPathIn(dir))
|
|
1983
1856
|
return dir;
|
|
1984
|
-
const parentDir =
|
|
1857
|
+
const parentDir = path9.dirname(dir);
|
|
1985
1858
|
if (parentDir === dir)
|
|
1986
1859
|
throw new Error(`biome.json not found from ${dir}`);
|
|
1987
1860
|
return this.#findBiomeRootPath(parentDir);
|
|
1988
1861
|
}
|
|
1989
1862
|
#toBiomePath(filePath) {
|
|
1990
|
-
const relativePath =
|
|
1991
|
-
if (!relativePath.startsWith("..") && !
|
|
1863
|
+
const relativePath = path9.relative(this.lintRoot, filePath);
|
|
1864
|
+
if (!relativePath.startsWith("..") && !path9.isAbsolute(relativePath))
|
|
1992
1865
|
return relativePath;
|
|
1993
1866
|
return filePath;
|
|
1994
1867
|
}
|
|
1995
1868
|
#resolveFilePath(filePath) {
|
|
1996
|
-
return
|
|
1869
|
+
return path9.isAbsolute(filePath) ? filePath : path9.join(this.lintRoot, filePath);
|
|
1997
1870
|
}
|
|
1998
1871
|
async#runBiome(args, input) {
|
|
1999
1872
|
return await new Promise((resolve, reject) => {
|
|
@@ -2018,7 +1891,7 @@ class Linter {
|
|
|
2018
1891
|
const diagnosticPath = diagnostic.location?.path;
|
|
2019
1892
|
if (!diagnosticPath)
|
|
2020
1893
|
return fallbackFilePath;
|
|
2021
|
-
return
|
|
1894
|
+
return path9.isAbsolute(diagnosticPath) ? diagnosticPath : path9.join(this.lintRoot, diagnosticPath);
|
|
2022
1895
|
}
|
|
2023
1896
|
#createLintMessage(diagnostic) {
|
|
2024
1897
|
const start = diagnostic.location?.start;
|
|
@@ -2354,7 +2227,7 @@ class ManifestDependencies {
|
|
|
2354
2227
|
|
|
2355
2228
|
// pkgs/@akanjs/devkit/repoIdentity.ts
|
|
2356
2229
|
import { execFileSync } from "child_process";
|
|
2357
|
-
import
|
|
2230
|
+
import path10 from "path";
|
|
2358
2231
|
var resolved = new Map;
|
|
2359
2232
|
var readRemoteName = (workspaceRoot) => {
|
|
2360
2233
|
try {
|
|
@@ -2372,13 +2245,14 @@ var resolveRepoName = (workspaceRoot) => {
|
|
|
2372
2245
|
const cached = resolved.get(workspaceRoot);
|
|
2373
2246
|
if (cached)
|
|
2374
2247
|
return cached;
|
|
2375
|
-
const repoName = readRemoteName(workspaceRoot) ??
|
|
2248
|
+
const repoName = readRemoteName(workspaceRoot) ?? path10.basename(workspaceRoot);
|
|
2376
2249
|
resolved.set(workspaceRoot, repoName);
|
|
2377
2250
|
return repoName;
|
|
2378
2251
|
};
|
|
2379
2252
|
|
|
2380
2253
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
2381
|
-
import
|
|
2254
|
+
import { createHash } from "crypto";
|
|
2255
|
+
import path11 from "path";
|
|
2382
2256
|
|
|
2383
2257
|
// pkgs/@akanjs/devkit/workspaceLayout.ts
|
|
2384
2258
|
var appRootAllowedFiles = new Set([
|
|
@@ -2499,10 +2373,10 @@ var moduleUiFileTypes = {
|
|
|
2499
2373
|
service: new Set(["Util", "Zone"]),
|
|
2500
2374
|
scalar: new Set(["Template", "Unit"])
|
|
2501
2375
|
};
|
|
2502
|
-
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-
|
|
2503
|
-
var getScanPath = (exec, relativePath) =>
|
|
2376
|
+
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-3afj2vt1.js")).TypeScriptDependencyScanner.from(exec);
|
|
2377
|
+
var getScanPath = (exec, relativePath) => path11.posix.join(`${exec.type}s`, exec.name, relativePath.split(path11.sep).join("/"));
|
|
2504
2378
|
var getModuleNameFromPath = (kind, modulePath) => {
|
|
2505
|
-
const dirname =
|
|
2379
|
+
const dirname = path11.basename(modulePath);
|
|
2506
2380
|
return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
|
|
2507
2381
|
};
|
|
2508
2382
|
async function assertScanConvention(exec, libRoot) {
|
|
@@ -2523,28 +2397,28 @@ async function assertScanConvention(exec, libRoot) {
|
|
|
2523
2397
|
for (const filename of await exec.readdir(facet)) {
|
|
2524
2398
|
const shadow = facetIndexShadowOf(`${facet}/${filename}`);
|
|
2525
2399
|
if (shadow)
|
|
2526
|
-
addViolation(
|
|
2400
|
+
addViolation(path11.join(facet, filename), shadow);
|
|
2527
2401
|
}
|
|
2528
2402
|
if (exec.type === "lib")
|
|
2529
2403
|
for (const { relativePath, reason } of await exec.getPageConventionViolations())
|
|
2530
2404
|
addViolation(relativePath, reason);
|
|
2531
2405
|
for (const filename of libRoot.files)
|
|
2532
2406
|
if (!isAllowedLibFacetRootFile(filename))
|
|
2533
|
-
addViolation(
|
|
2407
|
+
addViolation(path11.join("lib", filename), libFacetRootHintOf(filename) ?? "unsupported lib facet root file");
|
|
2534
2408
|
for (const dirname of libRoot.dirs)
|
|
2535
2409
|
if (dirname.startsWith("__") && !internalLibDirs.has(dirname))
|
|
2536
|
-
addViolation(
|
|
2410
|
+
addViolation(path11.join("lib", dirname), "unsupported internal lib folder");
|
|
2537
2411
|
const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
|
|
2538
2412
|
const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
|
|
2539
2413
|
const scalarDirs = await exec.readdir("lib/__scalar");
|
|
2540
2414
|
await Promise.all([
|
|
2541
|
-
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database",
|
|
2542
|
-
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service",
|
|
2543
|
-
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar",
|
|
2415
|
+
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path11.join("lib", dirname))),
|
|
2416
|
+
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path11.join("lib", dirname))),
|
|
2417
|
+
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path11.join("lib/__scalar", dirname)))
|
|
2544
2418
|
]);
|
|
2545
2419
|
if (violations.length > 0) {
|
|
2546
2420
|
throw new Error(`[scan-convention]
|
|
2547
|
-
${violations.sort().map((violation) => `- ${violation}`).join(`
|
|
2421
|
+
${violations.sort((a, b) => a < b ? -1 : a > b ? 1 : 0).map((violation) => `- ${violation}`).join(`
|
|
2548
2422
|
`)}`);
|
|
2549
2423
|
}
|
|
2550
2424
|
}
|
|
@@ -2552,11 +2426,11 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
|
|
|
2552
2426
|
const { files, dirs } = await exec.getFilesAndDirs(modulePath);
|
|
2553
2427
|
const moduleName = getModuleNameFromPath(kind, modulePath);
|
|
2554
2428
|
dirs.forEach((dirname) => {
|
|
2555
|
-
violations.push(`${getScanPath(exec,
|
|
2429
|
+
violations.push(`${getScanPath(exec, path11.join(modulePath, dirname))}: unsupported module folder`);
|
|
2556
2430
|
});
|
|
2557
2431
|
const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
|
|
2558
2432
|
for (const filename of files) {
|
|
2559
|
-
const scanPath = getScanPath(exec,
|
|
2433
|
+
const scanPath = getScanPath(exec, path11.join(modulePath, filename));
|
|
2560
2434
|
if (filename === "index.ts" || filename === "index.tsx")
|
|
2561
2435
|
continue;
|
|
2562
2436
|
if (filename === `${moduleName}.abstract.md`)
|
|
@@ -2631,16 +2505,16 @@ class ScanInfo {
|
|
|
2631
2505
|
}
|
|
2632
2506
|
};
|
|
2633
2507
|
await Promise.all([
|
|
2634
|
-
...databaseDirs.map((name) => collect(
|
|
2635
|
-
...serviceDirs.map((dirname) => collect(
|
|
2636
|
-
...scalarDirs.map((name) => collect(
|
|
2508
|
+
...databaseDirs.map((name) => collect(path11.join("lib", name), name, "databases")),
|
|
2509
|
+
...serviceDirs.map((dirname) => collect(path11.join("lib", dirname), dirname.slice(1), "services")),
|
|
2510
|
+
...scalarDirs.map((name) => collect(path11.join("lib/__scalar", name), name, "scalars"))
|
|
2637
2511
|
]);
|
|
2638
2512
|
const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
|
|
2639
2513
|
const common = {
|
|
2640
2514
|
name: exec.name,
|
|
2641
2515
|
type: exec.type,
|
|
2642
2516
|
repoName: exec.workspace.repoName,
|
|
2643
|
-
serveDomain: WorkspaceExecutor.getBaseDevEnv(
|
|
2517
|
+
serveDomain: WorkspaceExecutor.getBaseDevEnv(path11.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
|
|
2644
2518
|
files,
|
|
2645
2519
|
libDeps,
|
|
2646
2520
|
pkgDeps,
|
|
@@ -2701,10 +2575,13 @@ class ScanInfo {
|
|
|
2701
2575
|
var isAkanFrameworkDependency = (dep) => dep === "akanjs" || dep.startsWith("akanjs/");
|
|
2702
2576
|
|
|
2703
2577
|
class AppInfo extends ScanInfo {
|
|
2578
|
+
static migrationDir = "script/migration";
|
|
2579
|
+
static #migrationIdRe = /^\d{12}_[A-Za-z][A-Za-z0-9]*$/;
|
|
2704
2580
|
type = "app";
|
|
2705
2581
|
exec;
|
|
2706
2582
|
akanConfig;
|
|
2707
2583
|
libDeps;
|
|
2584
|
+
migrations;
|
|
2708
2585
|
static appInfos = new Map;
|
|
2709
2586
|
static async fromExecutor(exec, options = {}) {
|
|
2710
2587
|
const existingAppInfo = AppInfo.appInfos.get(exec.name);
|
|
@@ -2716,16 +2593,20 @@ class AppInfo extends ScanInfo {
|
|
|
2716
2593
|
const libExecutor = LibExecutor.from(exec, libName);
|
|
2717
2594
|
LibInfo.libInfos.set(libName, await LibInfo.fromExecutor(libExecutor));
|
|
2718
2595
|
}));
|
|
2719
|
-
const libDeps = await
|
|
2720
|
-
|
|
2596
|
+
const [libDeps, migrations] = await Promise.all([
|
|
2597
|
+
AppInfo.#getAllLibDeps(exec, scanResult.libDeps),
|
|
2598
|
+
AppInfo.scanMigrations(exec)
|
|
2599
|
+
]);
|
|
2600
|
+
const appInfo = new AppInfo(exec, scanResult, libDeps, migrations);
|
|
2721
2601
|
AppInfo.appInfos.set(exec.name, appInfo);
|
|
2722
2602
|
return appInfo;
|
|
2723
2603
|
}
|
|
2724
|
-
constructor(exec, scanResult, libDeps) {
|
|
2604
|
+
constructor(exec, scanResult, libDeps, migrations = []) {
|
|
2725
2605
|
super(scanResult);
|
|
2726
2606
|
this.exec = exec;
|
|
2727
2607
|
this.akanConfig = scanResult.akanConfig;
|
|
2728
2608
|
this.libDeps = libDeps;
|
|
2609
|
+
this.migrations = migrations;
|
|
2729
2610
|
}
|
|
2730
2611
|
getScanResult() {
|
|
2731
2612
|
return this.scanResult;
|
|
@@ -2733,6 +2614,18 @@ class AppInfo extends ScanInfo {
|
|
|
2733
2614
|
setRoutes(routes) {
|
|
2734
2615
|
this.scanResult.routes = routes;
|
|
2735
2616
|
}
|
|
2617
|
+
static async scanMigrations(exec) {
|
|
2618
|
+
const files = (await exec.readdir(AppInfo.migrationDir)).filter((file) => file.endsWith(".ts") && !/\.(test|spec|d)\.ts$/.test(file));
|
|
2619
|
+
return await Promise.all(files.map(async (file) => {
|
|
2620
|
+
const id = file.slice(0, -".ts".length);
|
|
2621
|
+
if (!AppInfo.#migrationIdRe.test(id))
|
|
2622
|
+
throw new Error(`${AppInfo.migrationDir}/${file}: a migration is named <yyyyMMddHHmm>_<camelName>.ts; create one with \`akan create-migration\``);
|
|
2623
|
+
const source = (await exec.readFile(`${AppInfo.migrationDir}/${file}`)).replace(/^\uFEFF/, "").replaceAll(`\r
|
|
2624
|
+
`, `
|
|
2625
|
+
`);
|
|
2626
|
+
return { id, checksum: createHash("sha256").update(source).digest("hex") };
|
|
2627
|
+
}));
|
|
2628
|
+
}
|
|
2736
2629
|
static async#getAllLibDeps(exec, libDeps, libSet = new Set) {
|
|
2737
2630
|
await Promise.all(libDeps.map(async (libName) => {
|
|
2738
2631
|
if (libSet.has(libName))
|
|
@@ -2868,6 +2761,136 @@ class WorkspaceInfo {
|
|
|
2868
2761
|
}
|
|
2869
2762
|
}
|
|
2870
2763
|
|
|
2764
|
+
// pkgs/@akanjs/devkit/spinner.ts
|
|
2765
|
+
import ora from "ora";
|
|
2766
|
+
|
|
2767
|
+
class Spinner {
|
|
2768
|
+
static padding = 12;
|
|
2769
|
+
static oraOptions = { discardStdin: false };
|
|
2770
|
+
static canAnimate(stream = process.stderr) {
|
|
2771
|
+
return !stream.isTTY || stream.columns > 0;
|
|
2772
|
+
}
|
|
2773
|
+
spinner;
|
|
2774
|
+
stopWatch = null;
|
|
2775
|
+
startAt = new Date;
|
|
2776
|
+
prefix;
|
|
2777
|
+
message;
|
|
2778
|
+
enableSpin;
|
|
2779
|
+
constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
|
|
2780
|
+
Spinner.padding = Math.max(Spinner.padding, prefix.length);
|
|
2781
|
+
this.prefix = prefix;
|
|
2782
|
+
this.message = message;
|
|
2783
|
+
this.spinner = ora({ ...Spinner.oraOptions, text: message });
|
|
2784
|
+
this.spinner.prefixText = prefix.padStart(Spinner.padding, " ");
|
|
2785
|
+
this.spinner.indent = indent;
|
|
2786
|
+
this.enableSpin = enableSpin && Spinner.canAnimate();
|
|
2787
|
+
}
|
|
2788
|
+
start() {
|
|
2789
|
+
this.startAt = new Date;
|
|
2790
|
+
if (this.enableSpin) {
|
|
2791
|
+
this.spinner.start();
|
|
2792
|
+
this.stopWatch = setInterval(() => {
|
|
2793
|
+
this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
|
|
2794
|
+
this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
|
|
2795
|
+
}, 1000);
|
|
2796
|
+
} else
|
|
2797
|
+
this.spinner.info();
|
|
2798
|
+
return this;
|
|
2799
|
+
}
|
|
2800
|
+
succeed(message) {
|
|
2801
|
+
this.spinner.succeed(`${message} (${this.#getElapsedTimeStr()})`);
|
|
2802
|
+
this.#reset();
|
|
2803
|
+
}
|
|
2804
|
+
fail(message) {
|
|
2805
|
+
this.spinner.fail(`${message} (${this.#getElapsedTimeStr()})`);
|
|
2806
|
+
this.#reset();
|
|
2807
|
+
}
|
|
2808
|
+
isSpinning() {
|
|
2809
|
+
return this.spinner.isSpinning;
|
|
2810
|
+
}
|
|
2811
|
+
#reset() {
|
|
2812
|
+
if (this.stopWatch)
|
|
2813
|
+
clearInterval(this.stopWatch);
|
|
2814
|
+
this.stopWatch = null;
|
|
2815
|
+
}
|
|
2816
|
+
#getElapsedTimeStr({ floor = false } = {}) {
|
|
2817
|
+
const ms = Date.now() - this.startAt.getTime();
|
|
2818
|
+
if (ms < 1000)
|
|
2819
|
+
return `${ms}ms`;
|
|
2820
|
+
const s = Math.floor(ms / 1000);
|
|
2821
|
+
if (s < 60)
|
|
2822
|
+
return `${floor ? Math.floor(ms / 1000) : Math.floor(ms / 100) / 10}s`;
|
|
2823
|
+
const m = Math.floor(s / 60);
|
|
2824
|
+
return `${m}m ${s % 60}s`;
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// pkgs/@akanjs/devkit/typecheck/projectTypecheck.ts
|
|
2829
|
+
import { mkdir as mkdir2, rm as rm3 } from "fs/promises";
|
|
2830
|
+
import path12 from "path";
|
|
2831
|
+
|
|
2832
|
+
class ProjectTypecheck2 {
|
|
2833
|
+
projectDir;
|
|
2834
|
+
workspaceRoot;
|
|
2835
|
+
env;
|
|
2836
|
+
static #testIncludes = ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"];
|
|
2837
|
+
static #excludes = [".akan/**", "node_modules/**", "local/**"];
|
|
2838
|
+
constructor(projectDir, workspaceRoot, env) {
|
|
2839
|
+
this.projectDir = projectDir;
|
|
2840
|
+
this.workspaceRoot = workspaceRoot;
|
|
2841
|
+
this.env = env;
|
|
2842
|
+
}
|
|
2843
|
+
get typecheckDir() {
|
|
2844
|
+
return path12.join(this.projectDir, ".akan", "typecheck");
|
|
2845
|
+
}
|
|
2846
|
+
async run(entries, { clean = false, incremental = true, tests = false } = {}) {
|
|
2847
|
+
const name = tests ? "tsconfig.tests" : "tsconfig";
|
|
2848
|
+
await mkdir2(this.typecheckDir, { recursive: true });
|
|
2849
|
+
const tsconfig = {
|
|
2850
|
+
extends: "../../tsconfig.json",
|
|
2851
|
+
compilerOptions: { noEmit: true, incremental, tsBuildInfoFile: `./${name}.tsbuildinfo` },
|
|
2852
|
+
include: [...entries, ...tests ? ProjectTypecheck2.#testIncludes.map((glob) => `../../${glob}`) : []],
|
|
2853
|
+
...tests ? { exclude: ProjectTypecheck2.#excludes.map((glob) => `../../${glob}`) } : {},
|
|
2854
|
+
references: []
|
|
2855
|
+
};
|
|
2856
|
+
const tsconfigPath = path12.join(this.typecheckDir, `${name}.json`);
|
|
2857
|
+
await Bun.write(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
|
|
2858
|
+
`);
|
|
2859
|
+
if (clean)
|
|
2860
|
+
await rm3(path12.join(this.typecheckDir, `${name}.tsbuildinfo`), { force: true });
|
|
2861
|
+
await this.#check(tsconfigPath);
|
|
2862
|
+
return tsconfigPath;
|
|
2863
|
+
}
|
|
2864
|
+
async#check(tsconfigPath) {
|
|
2865
|
+
const proc = Bun.spawn([process.execPath, await ProjectTypecheck2.workerEntry(this.workspaceRoot)], {
|
|
2866
|
+
cwd: this.workspaceRoot,
|
|
2867
|
+
env: { ...this.env, AKAN_COMMAND_TYPE: "typecheck", AKAN_TYPECHECK_TSCONFIG: tsconfigPath },
|
|
2868
|
+
stdout: "pipe",
|
|
2869
|
+
stderr: "pipe"
|
|
2870
|
+
});
|
|
2871
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
2872
|
+
new Response(proc.stdout).text(),
|
|
2873
|
+
new Response(proc.stderr).text(),
|
|
2874
|
+
proc.exited
|
|
2875
|
+
]);
|
|
2876
|
+
if (exitCode !== 0)
|
|
2877
|
+
throw new Error((stderr || stdout).trim() || `Typecheck failed with exit code ${exitCode}`);
|
|
2878
|
+
}
|
|
2879
|
+
static async workerEntry(workspaceRoot) {
|
|
2880
|
+
const candidates = [
|
|
2881
|
+
path12.join(workspaceRoot, "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2882
|
+
path12.join(workspaceRoot, "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
2883
|
+
path12.join(import.meta.dir, "typecheck.proc.js"),
|
|
2884
|
+
path12.join(import.meta.dir, "typecheck.proc.ts"),
|
|
2885
|
+
path12.join(import.meta.dir, "..", "typecheck.proc.js")
|
|
2886
|
+
];
|
|
2887
|
+
for (const candidate of candidates)
|
|
2888
|
+
if (await Bun.file(candidate).exists())
|
|
2889
|
+
return candidate;
|
|
2890
|
+
throw new Error(`[devkit] typecheck worker entry not found; looked in: ${candidates.join(", ")}`);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2871
2894
|
// pkgs/@akanjs/devkit/executors.ts
|
|
2872
2895
|
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(" ")));
|
|
2873
2896
|
var plainCommandArgPattern = process.platform === "win32" ? /^[\w@%+=:,./\\-]+$/ : /^[\w@%+=:,./-]+$/;
|
|
@@ -3052,20 +3075,20 @@ class Executor {
|
|
|
3052
3075
|
const readPath = this.getPath(dirPath);
|
|
3053
3076
|
try {
|
|
3054
3077
|
const glob = new Bun.Glob("*");
|
|
3055
|
-
return Array.from(glob.scanSync({ cwd: readPath, onlyFiles: false }));
|
|
3078
|
+
return Array.from(glob.scanSync({ cwd: readPath, onlyFiles: false })).sort();
|
|
3056
3079
|
} catch {
|
|
3057
3080
|
return [];
|
|
3058
3081
|
}
|
|
3059
3082
|
}
|
|
3060
3083
|
async getAllFiles(pattern = "**/*", { cwd } = {}) {
|
|
3061
3084
|
const glob = new Bun.Glob(pattern);
|
|
3062
|
-
return Array.from(glob.scanSync({ cwd: cwd ?? this.cwdPath, onlyFiles: true }));
|
|
3085
|
+
return Array.from(glob.scanSync({ cwd: cwd ?? this.cwdPath, onlyFiles: true })).sort();
|
|
3063
3086
|
}
|
|
3064
3087
|
async getFilesAndDirs(dirPath) {
|
|
3065
3088
|
const fullDirPath = this.getPath(dirPath);
|
|
3066
3089
|
const glob = new Bun.Glob("*");
|
|
3067
|
-
const files = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: true }));
|
|
3068
|
-
const allEntries = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: false }));
|
|
3090
|
+
const files = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: true })).sort();
|
|
3091
|
+
const allEntries = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: false })).sort();
|
|
3069
3092
|
const dirs = allEntries.filter((entry) => !files.includes(entry));
|
|
3070
3093
|
return { files, dirs };
|
|
3071
3094
|
}
|
|
@@ -3370,6 +3393,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
3370
3393
|
return new WorkspaceExecutor({ workspaceRoot, repoName });
|
|
3371
3394
|
}
|
|
3372
3395
|
static publicEnvPin = null;
|
|
3396
|
+
static localServeDomain = "localhost";
|
|
3373
3397
|
static getBaseDevEnv(envPath) {
|
|
3374
3398
|
const sourceEnv = {
|
|
3375
3399
|
...envPath ? { ...process.env, ...parseEnvFile(envPath) } : process.env,
|
|
@@ -3378,12 +3402,9 @@ class WorkspaceExecutor extends Executor {
|
|
|
3378
3402
|
const appName = sourceEnv.AKAN_PUBLIC_APP_NAME;
|
|
3379
3403
|
const workspaceRoot = sourceEnv.AKAN_WORKSPACE_ROOT;
|
|
3380
3404
|
const workspaceId = sourceEnv.AKAN_WORKSPACE_ID;
|
|
3381
|
-
const repoName = sourceEnv.AKAN_PUBLIC_REPO_NAME;
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
const serveDomain = sourceEnv.AKAN_PUBLIC_SERVE_DOMAIN;
|
|
3385
|
-
if (!serveDomain)
|
|
3386
|
-
throw new Error("AKAN_PUBLIC_SERVE_DOMAIN is not set");
|
|
3405
|
+
const repoName = sourceEnv.AKAN_PUBLIC_REPO_NAME || resolveRepoName(envPath ? path13.dirname(envPath) : workspaceRoot ?? process.cwd());
|
|
3406
|
+
const serveDomain = sourceEnv.AKAN_PUBLIC_SERVE_DOMAIN || WorkspaceExecutor.localServeDomain;
|
|
3407
|
+
const defaulted = ["AKAN_PUBLIC_REPO_NAME", "AKAN_PUBLIC_SERVE_DOMAIN"].filter((key) => !sourceEnv[key]);
|
|
3387
3408
|
const portOffset = parseInt(sourceEnv.PORT_OFFSET ?? "0");
|
|
3388
3409
|
const env = sourceEnv.AKAN_PUBLIC_ENV ?? "debug";
|
|
3389
3410
|
if (!env)
|
|
@@ -3395,9 +3416,18 @@ class WorkspaceExecutor extends Executor {
|
|
|
3395
3416
|
serveDomain,
|
|
3396
3417
|
env,
|
|
3397
3418
|
portOffset,
|
|
3398
|
-
workspaceId
|
|
3419
|
+
workspaceId,
|
|
3420
|
+
defaulted
|
|
3399
3421
|
};
|
|
3400
3422
|
}
|
|
3423
|
+
static assertDeployableEnv(baseDevEnv, environment, purpose) {
|
|
3424
|
+
const defaulted = environment === "local" ? [] : baseDevEnv.defaulted ?? [];
|
|
3425
|
+
if (!defaulted.length)
|
|
3426
|
+
return;
|
|
3427
|
+
const values = { AKAN_PUBLIC_REPO_NAME: baseDevEnv.repoName, AKAN_PUBLIC_SERVE_DOMAIN: baseDevEnv.serveDomain };
|
|
3428
|
+
const standIns = defaulted.map((key) => `${key} (would be "${values[key]}")`).join(" and ");
|
|
3429
|
+
throw new Error(`${purpose} for the "${environment}" environment needs ${standIns} set, not a local default. Set ${defaulted.length > 1 ? "them" : "it"} in the workspace .env or the shell, or use AKAN_PUBLIC_ENV=local.`);
|
|
3430
|
+
}
|
|
3401
3431
|
getWorkspaceId({
|
|
3402
3432
|
allowEmpty
|
|
3403
3433
|
} = {}) {
|
|
@@ -3774,10 +3804,12 @@ class AppExecutor extends SysExecutor {
|
|
|
3774
3804
|
return basePort + appIndex + portOffset;
|
|
3775
3805
|
}
|
|
3776
3806
|
getCommandEnv(env = {}) {
|
|
3777
|
-
const { portOffset } = WorkspaceExecutor.getBaseDevEnv();
|
|
3807
|
+
const { portOffset, repoName, serveDomain } = WorkspaceExecutor.getBaseDevEnv();
|
|
3778
3808
|
const PORT = (8282 + portOffset).toString();
|
|
3779
3809
|
return {
|
|
3780
3810
|
...process.env,
|
|
3811
|
+
AKAN_PUBLIC_REPO_NAME: repoName,
|
|
3812
|
+
AKAN_PUBLIC_SERVE_DOMAIN: serveDomain,
|
|
3781
3813
|
AKAN_PUBLIC_APP_NAME: this.name,
|
|
3782
3814
|
AKAN_WORKSPACE_ROOT: this.workspace.workspaceRoot,
|
|
3783
3815
|
NODE_NO_WARNINGS: "1",
|
|
@@ -3928,7 +3960,7 @@ class AppExecutor extends SysExecutor {
|
|
|
3928
3960
|
pageKeys.push(key);
|
|
3929
3961
|
}
|
|
3930
3962
|
}
|
|
3931
|
-
pageKeys.sort();
|
|
3963
|
+
pageKeys.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
3932
3964
|
this.#pageKeys = this.#excludeDevOnlyPages ? this.#dropDevOnlyPages(pageKeys, devOnlyKeys, devOnlyDirs) : pageKeys;
|
|
3933
3965
|
return this.#pageKeys;
|
|
3934
3966
|
}
|
|
@@ -4174,7 +4206,7 @@ class PkgExecutor extends Executor {
|
|
|
4174
4206
|
}
|
|
4175
4207
|
async#toDependencyMap(rootPackageJson, dependencies = [], devDependencies = []) {
|
|
4176
4208
|
const dependencyNames = [...new Set(dependencies)].sort();
|
|
4177
|
-
const devDependencyNames = [...new Set(devDependencies)].filter((dep) => !dependencyNames.includes(dep)).sort();
|
|
4209
|
+
const devDependencyNames = [...new Set(devDependencies)].filter((dep) => !dependencyNames.includes(dep)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
4178
4210
|
const dependencyVersions = new Map;
|
|
4179
4211
|
const missingDeps = [];
|
|
4180
4212
|
for (const dep of [...dependencyNames, ...devDependencyNames]) {
|
|
@@ -4256,4 +4288,4 @@ class ModuleExecutor extends Executor {
|
|
|
4256
4288
|
}
|
|
4257
4289
|
}
|
|
4258
4290
|
|
|
4259
|
-
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, binPlatforms, ServerEnvProfile, AkanAppConfig, AkanWorkspaceConfig, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, LibSource, formatLibStatuses, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, libFacetRootHintOf, barrelFacetDirs, facetIndexShadowOf, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, ProjectTypecheck2, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
|
|
4291
|
+
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, binPlatforms, ServerEnvProfile, AkanAppConfig, AkanWorkspaceConfig, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, LibSource, formatLibStatuses, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, libFacetRootHintOf, barrelFacetDirs, facetIndexShadowOf, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, ProjectTypecheck2, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
|