@akanjs/cli 3.0.0-beta.8 → 3.0.0
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-2vmp9qe0.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-5ed6bg2a.js +1457 -0
- package/README.ko.md +83 -28
- package/README.md +70 -27
- package/TunnelShare-cwh9ezsm.js +9 -0
- package/{agent.command-0q17pnrs.js → agent.command-gn73rhe7.js} +8 -9
- package/akanCode.js +102 -0
- package/application.command-e968t0hw.js +246 -0
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-64taxseg.js} +121 -77
- package/buildBatch.proc.js +120 -32
- package/cloud.command-0jm301rr.js +95 -0
- package/cloud.script-nemnzjpv.js +20 -0
- package/code.command-18kcm7hq.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-trd9bn18.js +48 -0
- package/context.runner-y5hwh673.js +21 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
- package/{devTui-m1atgkc9.js → devTui-q7k7bakd.js} +122 -40
- package/fileSys-wzzrntkw.js +9 -0
- package/guideline.command-bdmdn2de.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +154 -26
- package/guidelines/conventions/conventions.instruction.md +134 -48
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/guidelines/queryRule/queryRule.instruction.md +21 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +291 -6
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +10 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
- package/incrementalBuilder.proc.js +564 -109
- package/index-0wpybxtr.js +2729 -0
- package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
- package/index-2wc41k5w.js +49 -0
- package/index-3hx1z96c.js +87 -0
- package/{index-2tv3fdx4.js → index-3w94e7n3.js} +1228 -1458
- package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
- package/index-4npz05nm.js +187 -0
- package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
- package/index-6he6gvfg.js +4300 -0
- package/index-7md2g9vw.js +6 -0
- package/{index-04bk599g.js → index-93j60em0.js} +10 -17
- package/index-94s080qm.js +266 -0
- package/index-9dp031by.js +12 -0
- package/{index-ktb5njgy.js → index-9z771ptz.js} +49 -99
- package/{index-y6n8nmny.js → index-abxpehf8.js} +75 -203
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/{index-9077wzd2.js → index-by9pxkr0.js} +43 -26
- package/index-c6eggbee.js +123 -0
- package/index-d2242ksy.js +99 -0
- package/index-drehwpdt.js +1119 -0
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-ffvk10zz.js → index-f3c78nd6.js} +107 -241
- package/index-gbggbqcp.js +1058 -0
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/index-hsmtysz4.js +53 -0
- package/{index-j9yhg404.js → index-jmrn54tt.js} +4 -7
- package/{index-a64esmtb.js → index-kcdj1z5v.js} +523 -1022
- package/{index-d43r08tf.js → index-kqabytj7.js} +77 -150
- package/{index-vyvre9en.js → index-mbxbhay5.js} +6 -2
- package/{index-vxtad5bd.js → index-mgxpx3f2.js} +5 -8
- package/index-p6pbz9n5.js +25 -0
- package/{index-sj5vxr8f.js → index-p993efkq.js} +981 -897
- package/{index-2xp0kfkt.js → index-pmhqttv3.js} +37 -25
- package/index-tqm7jjcn.js +700 -0
- package/{index-0g7r8xz3.js → index-vpj42xwt.js} +114 -232
- package/index-wa5jbxj1.js +760 -0
- package/{index-k2j1hc4r.js → index-wprnsjms.js} +12 -12
- package/{index-gyd220p2.js → index-y3mphr1m.js} +554 -577
- package/index-yg7jhz8x.js +69 -0
- package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
- package/index.js +23 -22
- package/{library.command-qzzbj6d8.js → library.command-9h749a1x.js} +4 -5
- package/{localRegistry.command-vyrgg4xw.js → localRegistry.command-2bqqhxyz.js} +23 -43
- package/{module.command-rq5hq3ax.js → module.command-nv8nzgft.js} +8 -12
- package/{package.command-d9hd6v9n.js → package.command-mq4bggp6.js} +6 -7
- package/package.json +15 -7
- package/{page.command-126d56te.js → page.command-j639n707.js} +4 -5
- package/{primitive.command-taxd1538.js → primitive.command-kz6849th.js} +9 -23
- package/prompter-76k56b37.js +8 -0
- package/{quality.command-6gsna0a3.js → quality.command-4hyabwkb.js} +205 -377
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-tq2dgy72.js → repair.command-a9awpbqr.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-3bcmrrkv.js → scalar.command-ma4fpm9m.js} +7 -11
- package/skills/akan-code/SKILL.md +130 -0
- package/skills/akan-debug/SKILL.md +42 -0
- package/skills/akan-deps/SKILL.md +50 -0
- package/skills/akan-err/SKILL.md +39 -0
- package/skills/akan-module/SKILL.md +45 -0
- package/skills/akan-scaffold/SKILL.md +76 -0
- package/skills/akan-scalar/SKILL.md +52 -0
- package/skills/akan-service/SKILL.md +34 -0
- package/skills/akan-store/SKILL.md +67 -0
- package/skills/akan-ui/SKILL.md +63 -0
- package/skills/akan-validate/SKILL.md +44 -0
- package/{subspace.command-eddzqpx3.js → subspace.command-3k2pmrs4.js} +53 -62
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/app/page/_index.tsx +257 -258
- package/templates/app/page/_layout.tsx +5 -1
- package/templates/app/page/_overrides.tsx +12 -0
- package/templates/app/page/styles.css.template +316 -14
- package/templates/app/public/apple-touch-icon.png +0 -0
- package/templates/app/public/favicon.ico +0 -0
- package/templates/app/public/jelly/cloud.webp +0 -0
- package/templates/app/public/jelly/comet.webp +0 -0
- package/templates/app/public/jelly/moon.webp +0 -0
- package/templates/app/public/jelly/planet.webp +0 -0
- package/templates/app/public/jelly/rocket.webp +0 -0
- package/templates/app/public/jelly/star.webp +0 -0
- package/templates/app/public/jelly/sugar.webp +0 -0
- package/templates/app/public/logo.png +0 -0
- package/templates/app/ui/Recipe/index.ts +9 -0
- package/templates/app/ui/Recipe/jellyButton.ts +63 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/Task.Unit.tsx +1 -1
- package/templates/appSample/lib/task/Task.Util.tsx +10 -13
- package/templates/appSample/lib/task/Task.View.tsx +2 -2
- package/templates/appSample/lib/task/Task.Zone.tsx +5 -2
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +1 -15
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +10 -8
- package/templates/appSample/page/task/[taskId]/_index.tsx +6 -6
- package/templates/appSample/page/task/[taskId]/edit.tsx +5 -4
- package/templates/appSample/page/task/_index.tsx +4 -3
- package/templates/appSample/page/task/_layout.tsx +1 -1
- package/templates/appSample/page/task/new.tsx +5 -4
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
- package/templates/appSample/ui/GlobalLoading.tsx +2 -1
- package/templates/appSample/ui/QuantityControl.tsx +3 -3
- package/templates/client.ts +7 -30
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/server.ts +1 -3
- package/templates/service/__model__.abstract.md.template +8 -0
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/AGENTS.md.template +6 -0
- package/templates/workspaceRoot/package.json.template +1 -3
- package/tunnel.command-xwr46b66.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-t7wqe4h7.js +79 -0
- package/{workspace.command-06wvstgr.js → workspace.command-4derhzrh.js} +79 -109
- package/abstractCompactor-gmkc0z8j.js +0 -102
- package/application.command-18y0s485.js +0 -204
- package/applicationReleasePackager-4yasxwv9.js +0 -244
- package/capacitorApp-36rbr858.js +0 -12
- package/cloud.command-av97ndh6.js +0 -94
- package/cloud.script-cm11xjdz.js +0 -20
- package/context.command-5v37ef3n.js +0 -81
- package/devTuiView-0hc2emhe.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/guideline.command-4pz9g1rt.js +0 -360
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
- package/index-47pj3aee.js +0 -76
- package/index-5ystnsd5.js +0 -841
- package/index-6wwzs1bc.js +0 -7
- package/index-a17x49vg.js +0 -229
- package/index-m0xccmmm.js +0 -228
- package/index-meh558jy.js +0 -65
- package/index-rtxxan88.js +0 -471
- package/index-te54x9zm.js +0 -338
- package/index-w8kmx1b6.js +0 -2176
- package/index-wt7qrtd4.js +0 -1516
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
- package/workflow.command-gtdnzvbz.js +0 -108
|
@@ -0,0 +1,4300 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
SOURCE_EXTS,
|
|
4
|
+
WatchRootResolver
|
|
5
|
+
} from "./index-48k96p55.js";
|
|
6
|
+
import {
|
|
7
|
+
FileSys2
|
|
8
|
+
} from "./index-52w0fvh8.js";
|
|
9
|
+
import {
|
|
10
|
+
AsyncDefaultExportDetector,
|
|
11
|
+
resolveSsrPageEntriesForApp
|
|
12
|
+
} from "./index-3w94e7n3.js";
|
|
13
|
+
|
|
14
|
+
// pkgs/@akanjs/devkit/artifact/routeSeedIndex.ts
|
|
15
|
+
import path from "path";
|
|
16
|
+
import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
|
|
17
|
+
import {
|
|
18
|
+
ROUTE_SEED_INDEX_JSON
|
|
19
|
+
} from "akanjs/server/artifact/routeSeedIndexStore";
|
|
20
|
+
function computeRouteSeedIndex(pageEntries) {
|
|
21
|
+
const layoutsByPrefix = new Map;
|
|
22
|
+
const pagesBySegments = [];
|
|
23
|
+
for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
|
|
24
|
+
const parsed = parseRouteModuleKey(key);
|
|
25
|
+
const files = [path.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path.resolve(seed))];
|
|
26
|
+
if (parsed.kind === "layout" || parsed.kind === "overrides") {
|
|
27
|
+
layoutsByPrefix.getOrInsert(parsed.routeSegments.join("/"), []).push(...files);
|
|
28
|
+
} else if (parsed.kind === "page") {
|
|
29
|
+
pagesBySegments.push({
|
|
30
|
+
key,
|
|
31
|
+
pattern: parsed.pattern,
|
|
32
|
+
segments: parsed.routeSegments,
|
|
33
|
+
files,
|
|
34
|
+
includeOwnLayout: parsed.leaf === "_index"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
assertUniqueRoutePatterns(pagesBySegments);
|
|
39
|
+
const globalLayoutFiles = layoutsByPrefix.get("") ?? [];
|
|
40
|
+
const seedEntries = [];
|
|
41
|
+
for (const { pattern, segments, files, includeOwnLayout } of pagesBySegments) {
|
|
42
|
+
const layouts = [];
|
|
43
|
+
const maxPrefixLength = includeOwnLayout ? segments.length : Math.max(segments.length - 1, 0);
|
|
44
|
+
for (let i = 0;i <= maxPrefixLength; i++) {
|
|
45
|
+
const prefix = segments.slice(0, i).join("/");
|
|
46
|
+
const layoutFiles = layoutsByPrefix.get(prefix);
|
|
47
|
+
if (layoutFiles)
|
|
48
|
+
layouts.push(...layoutFiles);
|
|
49
|
+
}
|
|
50
|
+
const seeds = Array.from(new Set([...layouts, ...files]));
|
|
51
|
+
const routeId = pattern || "/";
|
|
52
|
+
seedEntries.push({ routeId, pattern: routeId, seeds });
|
|
53
|
+
}
|
|
54
|
+
seedEntries.sort((a, b) => compareRouteSpecificity(a.pattern, b.pattern));
|
|
55
|
+
return { entries: seedEntries, globalLayoutFiles };
|
|
56
|
+
}
|
|
57
|
+
function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
|
|
58
|
+
const normalizedArtifactDir = path.resolve(artifactDir);
|
|
59
|
+
if (options.production)
|
|
60
|
+
return { entries: index.entries.map((entry) => ({ routeId: entry.routeId })) };
|
|
61
|
+
return {
|
|
62
|
+
entries: index.entries.map((entry) => ({
|
|
63
|
+
...entry,
|
|
64
|
+
seeds: entry.seeds.map((seed) => serializeArtifactPath(seed, normalizedArtifactDir))
|
|
65
|
+
})),
|
|
66
|
+
globalLayoutFiles: index.globalLayoutFiles.map((file) => serializeArtifactPath(file, normalizedArtifactDir))
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function saveRouteSeedIndex(artifactDir, index, options = {}) {
|
|
70
|
+
const absPath = path.join(path.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
|
|
71
|
+
await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
|
|
72
|
+
`);
|
|
73
|
+
return absPath;
|
|
74
|
+
}
|
|
75
|
+
function serializeArtifactPath(artifactPath, artifactDir) {
|
|
76
|
+
if (!path.isAbsolute(artifactPath))
|
|
77
|
+
return artifactPath;
|
|
78
|
+
return path.relative(artifactDir, artifactPath).split(path.sep).join("/");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// pkgs/@akanjs/devkit/frontendBuild/clientEntryDiscovery.ts
|
|
82
|
+
import fs from "fs";
|
|
83
|
+
import path4 from "path";
|
|
84
|
+
|
|
85
|
+
// pkgs/@akanjs/devkit/transforms/barrelAnalyzer.ts
|
|
86
|
+
import path2 from "path";
|
|
87
|
+
import { Logger } from "akanjs/common";
|
|
88
|
+
|
|
89
|
+
// pkgs/@akanjs/devkit/transforms/moduleSyntax.ts
|
|
90
|
+
var loaderFor = (absPath) => {
|
|
91
|
+
if (absPath.endsWith(".tsx"))
|
|
92
|
+
return "tsx";
|
|
93
|
+
if (absPath.endsWith(".jsx"))
|
|
94
|
+
return "jsx";
|
|
95
|
+
if (absPath.endsWith(".ts"))
|
|
96
|
+
return "ts";
|
|
97
|
+
return "js";
|
|
98
|
+
};
|
|
99
|
+
var parseNamedList = (listBody) => {
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const raw of listBody.split(",")) {
|
|
102
|
+
let rest = raw.trim();
|
|
103
|
+
if (!rest)
|
|
104
|
+
continue;
|
|
105
|
+
let isType = false;
|
|
106
|
+
if (rest.startsWith("type ")) {
|
|
107
|
+
isType = true;
|
|
108
|
+
rest = rest.slice(5).trim();
|
|
109
|
+
}
|
|
110
|
+
const asMatch = /^(\w+)\s+as\s+(\w+)$/.exec(rest);
|
|
111
|
+
if (asMatch) {
|
|
112
|
+
out.push({ imported: asMatch[1] ?? "", local: asMatch[2] ?? "", isType });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (/^\w+$/.test(rest))
|
|
116
|
+
out.push({ imported: rest, local: rest, isType });
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// pkgs/@akanjs/devkit/transforms/barrelAnalyzer.ts
|
|
122
|
+
var REEXPORT_RE = /(?:^|\n)\s*export\s+(?:type\s+)?(?:(\*)(?:\s+as\s+(\w+))?|\{\s*([^}]*?)\s*\})\s+from\s+(["'])([^"']+)\4;?/g;
|
|
123
|
+
var LOCAL_NAMED_RE = /(?:^|\n)\s*export\s+\{\s*([^}]*?)\s*\}(?!\s*from)/g;
|
|
124
|
+
var CANDIDATE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
125
|
+
|
|
126
|
+
class BarrelAnalyzer {
|
|
127
|
+
#logger = new Logger("BarrelAnalyzer");
|
|
128
|
+
#opts;
|
|
129
|
+
#cache = new Map;
|
|
130
|
+
#tsTranspiler = new Bun.Transpiler({ loader: "ts" });
|
|
131
|
+
#tsxTranspiler = new Bun.Transpiler({ loader: "tsx" });
|
|
132
|
+
constructor(opts) {
|
|
133
|
+
this.#opts = opts;
|
|
134
|
+
}
|
|
135
|
+
analyze(pkgName) {
|
|
136
|
+
const cached = this.#cache.get(pkgName);
|
|
137
|
+
if (cached)
|
|
138
|
+
return cached;
|
|
139
|
+
const promise = this.#analyzeSafe(pkgName);
|
|
140
|
+
this.#cache.set(pkgName, promise);
|
|
141
|
+
return promise;
|
|
142
|
+
}
|
|
143
|
+
async#analyzeSafe(pkgName) {
|
|
144
|
+
try {
|
|
145
|
+
const pkg = await this.#opts.resolvePackage(pkgName);
|
|
146
|
+
if (!pkg)
|
|
147
|
+
return null;
|
|
148
|
+
const map = new Map;
|
|
149
|
+
await this.#walk(pkg.entryFile, pkg, map, new Set);
|
|
150
|
+
return map;
|
|
151
|
+
} catch (err) {
|
|
152
|
+
this.#logger.error(`analyze failed for ${pkgName}: ${err.message}`);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async#walk(absFile, pkg, map, visited) {
|
|
157
|
+
if (visited.has(absFile))
|
|
158
|
+
return;
|
|
159
|
+
visited.add(absFile);
|
|
160
|
+
const file = Bun.file(absFile);
|
|
161
|
+
if (!await file.exists())
|
|
162
|
+
return;
|
|
163
|
+
const source = await file.text();
|
|
164
|
+
const currentSubpath = this.#subpathFor(pkg, absFile);
|
|
165
|
+
if (!currentSubpath)
|
|
166
|
+
return;
|
|
167
|
+
const authoritative = this.#scanExports(source, absFile);
|
|
168
|
+
authoritative.delete("default");
|
|
169
|
+
const attributed = new Set;
|
|
170
|
+
const attribute = (listBody, subpath) => {
|
|
171
|
+
for (const item of parseNamedList(listBody)) {
|
|
172
|
+
if (item.isType || item.imported === "default" || !authoritative.has(item.local))
|
|
173
|
+
continue;
|
|
174
|
+
attributed.add(item.local);
|
|
175
|
+
if (!map.has(item.local))
|
|
176
|
+
map.set(item.local, { subpath, originalName: item.imported });
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const starSpecs = [];
|
|
180
|
+
for (const m of source.matchAll(REEXPORT_RE)) {
|
|
181
|
+
const star = m[1];
|
|
182
|
+
const nsAs = m[2];
|
|
183
|
+
const namedList = m[3];
|
|
184
|
+
const spec = m[5] ?? "";
|
|
185
|
+
if (!isRelative(spec))
|
|
186
|
+
continue;
|
|
187
|
+
if (star) {
|
|
188
|
+
if (nsAs)
|
|
189
|
+
authoritative.delete(nsAs);
|
|
190
|
+
else
|
|
191
|
+
starSpecs.push(spec);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (namedList !== undefined) {
|
|
195
|
+
const targetAbs = await this.#resolveRel(absFile, spec);
|
|
196
|
+
if (!targetAbs)
|
|
197
|
+
continue;
|
|
198
|
+
const targetSubpath = this.#subpathFor(pkg, targetAbs);
|
|
199
|
+
if (targetSubpath)
|
|
200
|
+
attribute(namedList, targetSubpath);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
LOCAL_NAMED_RE.lastIndex = 0;
|
|
204
|
+
let n = LOCAL_NAMED_RE.exec(source);
|
|
205
|
+
while (n !== null) {
|
|
206
|
+
const body = n[1] ?? "";
|
|
207
|
+
n = LOCAL_NAMED_RE.exec(source);
|
|
208
|
+
attribute(body, currentSubpath);
|
|
209
|
+
}
|
|
210
|
+
for (const name of authoritative) {
|
|
211
|
+
if (attributed.has(name))
|
|
212
|
+
continue;
|
|
213
|
+
if (map.has(name))
|
|
214
|
+
continue;
|
|
215
|
+
map.set(name, { subpath: currentSubpath, originalName: name });
|
|
216
|
+
}
|
|
217
|
+
for (const spec of starSpecs) {
|
|
218
|
+
const targetAbs = await this.#resolveRel(absFile, spec);
|
|
219
|
+
if (targetAbs)
|
|
220
|
+
await this.#walk(targetAbs, pkg, map, visited);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
#scanExports(source, absFile) {
|
|
224
|
+
try {
|
|
225
|
+
const transpiler = [".tsx", ".jsx"].includes(path2.extname(absFile)) ? this.#tsxTranspiler : this.#tsTranspiler;
|
|
226
|
+
const { exports } = transpiler.scan(source);
|
|
227
|
+
return new Set(exports);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
this.#logger.error(`scan failed: ${err.message}`);
|
|
230
|
+
return new Set;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
#subpathFor(pkg, absFile) {
|
|
234
|
+
const rel = path2.relative(pkg.pkgDir, absFile);
|
|
235
|
+
if (!rel || rel.startsWith("..") || path2.isAbsolute(rel))
|
|
236
|
+
return null;
|
|
237
|
+
if (pkg.preserveFilePath)
|
|
238
|
+
return `${pkg.pkgName}/${rel.split(path2.sep).join("/")}`;
|
|
239
|
+
const ext = CANDIDATE_EXTS.find((candidate) => rel.endsWith(candidate));
|
|
240
|
+
const parts = (ext ? rel.slice(0, -ext.length) : rel).split(path2.sep);
|
|
241
|
+
if (parts[parts.length - 1] === "index")
|
|
242
|
+
parts.pop();
|
|
243
|
+
const tail = parts.join("/");
|
|
244
|
+
return tail === "" ? pkg.pkgName : `${pkg.pkgName}/${tail}`;
|
|
245
|
+
}
|
|
246
|
+
async#resolveRel(fromFile, relSpec) {
|
|
247
|
+
if (this.#opts.resolveRelative)
|
|
248
|
+
return this.#opts.resolveRelative(fromFile, relSpec);
|
|
249
|
+
return defaultResolveRelative(fromFile, relSpec);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
var isRelative = (spec) => {
|
|
253
|
+
return spec.startsWith("./") || spec.startsWith("../") || spec === "." || spec === "..";
|
|
254
|
+
};
|
|
255
|
+
var defaultResolveRelative = async (fromFile, relSpec) => {
|
|
256
|
+
const baseDir = path2.dirname(fromFile);
|
|
257
|
+
const joined = path2.resolve(baseDir, relSpec);
|
|
258
|
+
if (path2.extname(joined)) {
|
|
259
|
+
if (await Bun.file(joined).exists())
|
|
260
|
+
return joined;
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
for (const ext of CANDIDATE_EXTS) {
|
|
264
|
+
const cand = joined + ext;
|
|
265
|
+
if (await Bun.file(cand).exists())
|
|
266
|
+
return cand;
|
|
267
|
+
}
|
|
268
|
+
for (const ext of CANDIDATE_EXTS) {
|
|
269
|
+
const cand = path2.join(joined, `index${ext}`);
|
|
270
|
+
if (await Bun.file(cand).exists())
|
|
271
|
+
return cand;
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// pkgs/@akanjs/devkit/transforms/barrelImportsPlugin.ts
|
|
277
|
+
import path3 from "path";
|
|
278
|
+
import ts from "typescript";
|
|
279
|
+
var createBarrelImportsPlugin = async (app, { skipPath = defaultSkipPath, pipeAfter } = {}) => {
|
|
280
|
+
const akanConfig = await app.getConfig();
|
|
281
|
+
const barrels = [...new Set(akanConfig.barrelImports)].filter(Boolean);
|
|
282
|
+
const analyzer = new BarrelAnalyzer({
|
|
283
|
+
resolvePackage: await createTsconfigPackageResolver(app)
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
name: "barrel-imports",
|
|
287
|
+
setup(build) {
|
|
288
|
+
build.onLoad({
|
|
289
|
+
filter: /^(?:(?!.*[\\/]node_modules[\\/]).*|.*[\\/]node_modules[\\/]akanjs[\\/].*)\.(tsx|ts|jsx|js)(\?v=\d+)?$/
|
|
290
|
+
}, async (args) => {
|
|
291
|
+
const realPath = args.path.replace(/\?v=\d+$/, "");
|
|
292
|
+
const loader = loaderFor(realPath);
|
|
293
|
+
let source = await Bun.file(realPath).text();
|
|
294
|
+
if (skipPath(realPath))
|
|
295
|
+
return { contents: source, loader };
|
|
296
|
+
if (!MACRO_ATTR_RE.test(source) && barrels.length > 0) {
|
|
297
|
+
const rewritten = await rewriteBarrelImports(source, barrels, analyzer);
|
|
298
|
+
if (rewritten !== null)
|
|
299
|
+
source = rewritten;
|
|
300
|
+
}
|
|
301
|
+
if (pipeAfter) {
|
|
302
|
+
const piped = await pipeAfter(source, { path: realPath });
|
|
303
|
+
if (piped !== null)
|
|
304
|
+
source = piped;
|
|
305
|
+
}
|
|
306
|
+
return { contents: source, loader };
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
var createTsconfigPackageResolver = async (app) => {
|
|
312
|
+
const tsconfig = await app.getTsConfig();
|
|
313
|
+
const tsconfigPaths = tsconfig.compilerOptions.paths ?? {};
|
|
314
|
+
const wildcardEntries = Object.entries(tsconfigPaths).filter(([k]) => k.endsWith("/*")).map(([k, v]) => ({ prefix: k.slice(0, -1), replacements: v })).sort((a, b) => b.prefix.length - a.prefix.length);
|
|
315
|
+
return async (pkgName) => {
|
|
316
|
+
const exact = tsconfigPaths[pkgName];
|
|
317
|
+
if (exact && exact.length > 0) {
|
|
318
|
+
const raw = exact[0];
|
|
319
|
+
if (!raw)
|
|
320
|
+
return null;
|
|
321
|
+
const entryFile = path3.resolve(app.workspace.workspaceRoot, raw);
|
|
322
|
+
if (!await Bun.file(entryFile).exists())
|
|
323
|
+
return null;
|
|
324
|
+
const parsed = path3.parse(entryFile);
|
|
325
|
+
const lastSlash = pkgName.lastIndexOf("/");
|
|
326
|
+
if (parsed.name !== "index" && lastSlash > 0 && pkgName.slice(lastSlash + 1) === parsed.name)
|
|
327
|
+
return { pkgName: pkgName.slice(0, lastSlash), entryFile, pkgDir: parsed.dir };
|
|
328
|
+
return { pkgName, entryFile, pkgDir: path3.dirname(entryFile) };
|
|
329
|
+
}
|
|
330
|
+
for (const { prefix, replacements } of wildcardEntries) {
|
|
331
|
+
if (!pkgName.startsWith(prefix))
|
|
332
|
+
continue;
|
|
333
|
+
const suffix = pkgName.slice(prefix.length);
|
|
334
|
+
for (const repl of replacements) {
|
|
335
|
+
if (!repl)
|
|
336
|
+
continue;
|
|
337
|
+
const replPath = repl.endsWith("/*") ? repl.slice(0, -1) : repl;
|
|
338
|
+
const candidate = path3.resolve(app.workspace.workspaceRoot, replPath + suffix);
|
|
339
|
+
for (const ext of CANDIDATE_EXTS2) {
|
|
340
|
+
const file = `${candidate}${ext}`;
|
|
341
|
+
if (await Bun.file(file).exists()) {
|
|
342
|
+
const lastSlash = pkgName.lastIndexOf("/");
|
|
343
|
+
const parentSpec = lastSlash > 0 ? pkgName.slice(0, lastSlash) : pkgName;
|
|
344
|
+
return { pkgName: parentSpec, entryFile: file, pkgDir: path3.dirname(file) };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
for (const ext of CANDIDATE_EXTS2) {
|
|
348
|
+
const file = path3.join(candidate, `index${ext}`);
|
|
349
|
+
if (await Bun.file(file).exists())
|
|
350
|
+
return { pkgName, entryFile: file, pkgDir: candidate };
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
const exported = await resolveNodePackageExport(app.workspace.workspaceRoot, pkgName);
|
|
356
|
+
if (exported)
|
|
357
|
+
return exported;
|
|
358
|
+
const pkgJsonPath = path3.join(app.workspace.workspaceRoot, "node_modules", pkgName, "package.json");
|
|
359
|
+
if (!await Bun.file(pkgJsonPath).exists())
|
|
360
|
+
return null;
|
|
361
|
+
try {
|
|
362
|
+
const pkgJson = JSON.parse(await Bun.file(pkgJsonPath).text());
|
|
363
|
+
const rel = pkgJson.module ?? pkgJson.main ?? "index.js";
|
|
364
|
+
const entryFile = path3.resolve(path3.dirname(pkgJsonPath), rel);
|
|
365
|
+
if (!await Bun.file(entryFile).exists())
|
|
366
|
+
return null;
|
|
367
|
+
return { pkgName, entryFile, pkgDir: path3.dirname(pkgJsonPath) };
|
|
368
|
+
} catch {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
var CANDIDATE_EXTS2 = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
374
|
+
var NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
|
|
375
|
+
var AKANJS_NODE_MODULE_RE = /[\\/]node_modules[\\/]akanjs[\\/]/;
|
|
376
|
+
var defaultSkipPath = (absPath) => NODE_MODULES_RE.test(absPath) && !AKANJS_NODE_MODULE_RE.test(absPath);
|
|
377
|
+
var resolveNodePackageExport = async (workspaceRoot, specifier) => {
|
|
378
|
+
const packageName = getPackageName(specifier);
|
|
379
|
+
if (!packageName)
|
|
380
|
+
return null;
|
|
381
|
+
const pkgJsonPath = path3.join(workspaceRoot, "node_modules", packageName, "package.json");
|
|
382
|
+
if (!await Bun.file(pkgJsonPath).exists())
|
|
383
|
+
return null;
|
|
384
|
+
try {
|
|
385
|
+
const pkgDir = path3.dirname(pkgJsonPath);
|
|
386
|
+
const pkgJson = JSON.parse(await Bun.file(pkgJsonPath).text());
|
|
387
|
+
const subpath = specifier === packageName ? "." : `.${specifier.slice(packageName.length)}`;
|
|
388
|
+
const exported = resolvePackageExport(pkgJson.exports, subpath);
|
|
389
|
+
const rel = exported ?? (subpath === "." ? pkgJson.module ?? pkgJson.main ?? "index.js" : null);
|
|
390
|
+
if (!rel?.startsWith("."))
|
|
391
|
+
return null;
|
|
392
|
+
const entryFile = await resolveFileCandidate(path3.resolve(pkgDir, rel));
|
|
393
|
+
if (!entryFile)
|
|
394
|
+
return null;
|
|
395
|
+
return { pkgName: specifier, entryFile, pkgDir: path3.dirname(entryFile), preserveFilePath: true };
|
|
396
|
+
} catch {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var resolveExportValue = (value) => {
|
|
401
|
+
if (!value)
|
|
402
|
+
return null;
|
|
403
|
+
if (typeof value === "string")
|
|
404
|
+
return value;
|
|
405
|
+
if (Array.isArray(value)) {
|
|
406
|
+
for (const item of value) {
|
|
407
|
+
const resolved = resolveExportValue(item);
|
|
408
|
+
if (resolved)
|
|
409
|
+
return resolved;
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
for (const condition of ["source", "import", "default", "types"]) {
|
|
414
|
+
const resolved = resolveExportValue(value[condition]);
|
|
415
|
+
if (resolved)
|
|
416
|
+
return resolved;
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
};
|
|
420
|
+
var resolvePackageExport = (exportsMap, subpath) => {
|
|
421
|
+
if (!exportsMap)
|
|
422
|
+
return null;
|
|
423
|
+
const exact = resolveExportValue(exportsMap[subpath]);
|
|
424
|
+
if (exact)
|
|
425
|
+
return exact;
|
|
426
|
+
for (const [key, value] of Object.entries(exportsMap)) {
|
|
427
|
+
const starIdx = key.indexOf("*");
|
|
428
|
+
if (starIdx === -1)
|
|
429
|
+
continue;
|
|
430
|
+
const prefix = key.slice(0, starIdx);
|
|
431
|
+
const suffix = key.slice(starIdx + 1);
|
|
432
|
+
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix))
|
|
433
|
+
continue;
|
|
434
|
+
const wildcard = subpath.slice(prefix.length, subpath.length - suffix.length);
|
|
435
|
+
const resolved = resolveExportValue(value);
|
|
436
|
+
if (resolved)
|
|
437
|
+
return resolved.replace("*", wildcard);
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
};
|
|
441
|
+
var getPackageName = (specifier) => {
|
|
442
|
+
const parts = specifier.split("/");
|
|
443
|
+
if (!parts[0])
|
|
444
|
+
return null;
|
|
445
|
+
if (specifier.startsWith("@"))
|
|
446
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
|
|
447
|
+
return parts[0];
|
|
448
|
+
};
|
|
449
|
+
var resolveFileCandidate = async (candidate) => {
|
|
450
|
+
if (await Bun.file(candidate).exists())
|
|
451
|
+
return candidate;
|
|
452
|
+
if (path3.extname(candidate))
|
|
453
|
+
return null;
|
|
454
|
+
for (const ext of CANDIDATE_EXTS2) {
|
|
455
|
+
const file = `${candidate}${ext}`;
|
|
456
|
+
if (await Bun.file(file).exists())
|
|
457
|
+
return file;
|
|
458
|
+
}
|
|
459
|
+
for (const ext of CANDIDATE_EXTS2) {
|
|
460
|
+
const file = path3.join(candidate, `index${ext}`);
|
|
461
|
+
if (await Bun.file(file).exists())
|
|
462
|
+
return file;
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
};
|
|
466
|
+
var MACRO_ATTR_RE = /with\s*\{\s*type\s*:\s*["']macro["']\s*\}/;
|
|
467
|
+
var rewriteBarrelImports = async (source, barrels, analyzer) => {
|
|
468
|
+
if (!barrels.some((barrel) => source.includes(barrel)))
|
|
469
|
+
return null;
|
|
470
|
+
const statements = findImportStatements(source);
|
|
471
|
+
if (statements.length === 0)
|
|
472
|
+
return null;
|
|
473
|
+
let changed = false;
|
|
474
|
+
let out = source;
|
|
475
|
+
for (let i = statements.length - 1;i >= 0; i--) {
|
|
476
|
+
const stmt = statements[i];
|
|
477
|
+
if (!stmt)
|
|
478
|
+
continue;
|
|
479
|
+
if (!barrels.includes(stmt.specifier))
|
|
480
|
+
continue;
|
|
481
|
+
const map = await analyzer.analyze(stmt.specifier);
|
|
482
|
+
if (!map || map.size === 0)
|
|
483
|
+
continue;
|
|
484
|
+
const replacement = rewriteSingleStatement(stmt, map);
|
|
485
|
+
if (replacement === null)
|
|
486
|
+
continue;
|
|
487
|
+
out = out.slice(0, stmt.start) + replacement + out.slice(stmt.end);
|
|
488
|
+
changed = true;
|
|
489
|
+
}
|
|
490
|
+
return changed ? out : null;
|
|
491
|
+
};
|
|
492
|
+
var findImportStatements = (source) => {
|
|
493
|
+
const statements = [];
|
|
494
|
+
const sourceFile = ts.createSourceFile("barrel-imports.tsx", source, ts.ScriptTarget.Latest, false, ts.ScriptKind.TSX);
|
|
495
|
+
for (const statement of sourceFile.statements) {
|
|
496
|
+
if (!ts.isImportDeclaration(statement))
|
|
497
|
+
continue;
|
|
498
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier))
|
|
499
|
+
continue;
|
|
500
|
+
const importClause = statement.importClause;
|
|
501
|
+
if (!importClause)
|
|
502
|
+
continue;
|
|
503
|
+
statements.push({
|
|
504
|
+
start: statement.getStart(sourceFile),
|
|
505
|
+
end: statement.end,
|
|
506
|
+
clause: source.slice(importClause.getStart(sourceFile), importClause.end).trim(),
|
|
507
|
+
specifier: statement.moduleSpecifier.text
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return statements;
|
|
511
|
+
};
|
|
512
|
+
var parseImportClause = (clause) => {
|
|
513
|
+
let rest = clause.trim();
|
|
514
|
+
let typeOnly = false;
|
|
515
|
+
if (rest.startsWith("type ")) {
|
|
516
|
+
typeOnly = true;
|
|
517
|
+
rest = rest.slice(5).trim();
|
|
518
|
+
}
|
|
519
|
+
const parsed = { typeOnly };
|
|
520
|
+
const commaMatch = /^(\w+)\s*,\s*(.+)$/.exec(rest);
|
|
521
|
+
if (commaMatch) {
|
|
522
|
+
parsed.defaultImport = commaMatch[1];
|
|
523
|
+
rest = commaMatch[2] ?? "";
|
|
524
|
+
} else if (/^\w+$/.test(rest)) {
|
|
525
|
+
parsed.defaultImport = rest;
|
|
526
|
+
return parsed;
|
|
527
|
+
}
|
|
528
|
+
if (rest.startsWith("*")) {
|
|
529
|
+
const ns = /^\*\s+as\s+(\w+)$/.exec(rest);
|
|
530
|
+
if (!ns)
|
|
531
|
+
return null;
|
|
532
|
+
parsed.namespaceImport = ns[1];
|
|
533
|
+
return parsed;
|
|
534
|
+
}
|
|
535
|
+
if (rest.startsWith("{")) {
|
|
536
|
+
const close = rest.indexOf("}");
|
|
537
|
+
if (close === -1)
|
|
538
|
+
return null;
|
|
539
|
+
parsed.named = parseNamedList(rest.slice(1, close));
|
|
540
|
+
}
|
|
541
|
+
return parsed;
|
|
542
|
+
};
|
|
543
|
+
var rewriteSingleStatement = (stmt, map) => {
|
|
544
|
+
const clause = parseImportClause(stmt.clause);
|
|
545
|
+
if (!clause)
|
|
546
|
+
return null;
|
|
547
|
+
if (clause.namespaceImport)
|
|
548
|
+
return null;
|
|
549
|
+
if (clause.typeOnly && !clause.defaultImport)
|
|
550
|
+
return null;
|
|
551
|
+
if (!clause.named?.length)
|
|
552
|
+
return null;
|
|
553
|
+
const remaining = [];
|
|
554
|
+
const rewrites = new Map;
|
|
555
|
+
for (const item of clause.named) {
|
|
556
|
+
if (item.isType) {
|
|
557
|
+
remaining.push(item);
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const target = map.get(item.imported);
|
|
561
|
+
if (!target) {
|
|
562
|
+
remaining.push(item);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const list = rewrites.get(target.subpath) ?? [];
|
|
566
|
+
list.push({ imported: target.originalName, local: item.local, isType: false });
|
|
567
|
+
rewrites.set(target.subpath, list);
|
|
568
|
+
}
|
|
569
|
+
if (rewrites.size === 0)
|
|
570
|
+
return null;
|
|
571
|
+
const lines = [];
|
|
572
|
+
if (shouldPreserveBarrelSideEffects(stmt.specifier))
|
|
573
|
+
lines.push(`import "${stmt.specifier}";`);
|
|
574
|
+
if (clause.defaultImport || remaining.length > 0) {
|
|
575
|
+
const parts = [];
|
|
576
|
+
if (clause.defaultImport)
|
|
577
|
+
parts.push(clause.defaultImport);
|
|
578
|
+
if (remaining.length > 0)
|
|
579
|
+
parts.push(`{ ${remaining.map(serializeNamedItem).join(", ")} }`);
|
|
580
|
+
lines.push(`import ${parts.join(", ")} from "${stmt.specifier}";`);
|
|
581
|
+
}
|
|
582
|
+
for (const [subpath, items] of rewrites) {
|
|
583
|
+
lines.push(`import { ${items.map(serializeNamedItem).join(", ")} } from "${subpath}";`);
|
|
584
|
+
}
|
|
585
|
+
return lines.join(`
|
|
586
|
+
`);
|
|
587
|
+
};
|
|
588
|
+
var shouldPreserveBarrelSideEffects = (specifier) => /^@(apps|libs)\/[^/]+\/client$/.test(specifier);
|
|
589
|
+
var serializeNamedItem = (item) => {
|
|
590
|
+
const prefix = item.isType ? "type " : "";
|
|
591
|
+
if (item.imported === item.local)
|
|
592
|
+
return `${prefix}${item.imported}`;
|
|
593
|
+
return `${prefix}${item.imported} as ${item.local}`;
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
// pkgs/@akanjs/devkit/frontendBuild/clientEntryDiscovery.ts
|
|
597
|
+
var USE_CLIENT_RE = /^\s*(?:\/\*[\s\S]*?\*\/\s*|\/\/[^\n]*\n\s*)*["']use client["']/;
|
|
598
|
+
var NODE_MODULES_RE2 = /[\\/]node_modules[\\/]/;
|
|
599
|
+
var AKANJS_NODE_MODULE_RE2 = /[\\/]node_modules[\\/]akanjs[\\/]/;
|
|
600
|
+
var NON_SOURCE_EXT_RE = /\.(css|scss|sass|less|json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav)$/i;
|
|
601
|
+
var shouldSkipNodeModule = (absPath) => NODE_MODULES_RE2.test(absPath) && !AKANJS_NODE_MODULE_RE2.test(absPath);
|
|
602
|
+
|
|
603
|
+
class GraphClientEntryDiscovery {
|
|
604
|
+
#akanConfig;
|
|
605
|
+
#resolvePackage;
|
|
606
|
+
#analyzer;
|
|
607
|
+
#tsTranspiler = new Bun.Transpiler({ loader: "tsx" });
|
|
608
|
+
#fileExistsCache = new Map;
|
|
609
|
+
#factsCache = new Map;
|
|
610
|
+
#resolvedFileCache = new Map;
|
|
611
|
+
#resolvedSpecifierCache = new Map;
|
|
612
|
+
#reachableEntriesCache = new Map;
|
|
613
|
+
#readFiles = new Set;
|
|
614
|
+
#epoch = 0;
|
|
615
|
+
constructor(akanConfig, resolvePackage) {
|
|
616
|
+
this.#akanConfig = akanConfig;
|
|
617
|
+
this.#resolvePackage = resolvePackage;
|
|
618
|
+
this.#analyzer = new BarrelAnalyzer({ resolvePackage });
|
|
619
|
+
}
|
|
620
|
+
static async create(app) {
|
|
621
|
+
return new GraphClientEntryDiscovery(await app.getConfig(), await createTsconfigPackageResolver(app));
|
|
622
|
+
}
|
|
623
|
+
async discover(seeds) {
|
|
624
|
+
const entries = new Set;
|
|
625
|
+
for (const seed of seeds) {
|
|
626
|
+
const walk = { visiting: new Set, partial: new Set, memo: new Map };
|
|
627
|
+
for (const entry of await this.#discoverFromFile(seed, walk))
|
|
628
|
+
entries.add(entry);
|
|
629
|
+
}
|
|
630
|
+
return Array.from(entries).sort();
|
|
631
|
+
}
|
|
632
|
+
invalidate(files) {
|
|
633
|
+
if (files.length === 0)
|
|
634
|
+
return;
|
|
635
|
+
let moved = false;
|
|
636
|
+
for (const file of files) {
|
|
637
|
+
const absPath = path4.resolve(file);
|
|
638
|
+
const exists = fs.existsSync(absPath);
|
|
639
|
+
if (exists !== this.#readFiles.has(absPath))
|
|
640
|
+
moved = true;
|
|
641
|
+
if (!exists)
|
|
642
|
+
this.#readFiles.delete(absPath);
|
|
643
|
+
this.#factsCache.delete(absPath);
|
|
644
|
+
this.#fileExistsCache.delete(absPath);
|
|
645
|
+
}
|
|
646
|
+
this.#epoch += 1;
|
|
647
|
+
this.#reachableEntriesCache.clear();
|
|
648
|
+
if (!moved)
|
|
649
|
+
return;
|
|
650
|
+
this.#fileExistsCache.clear();
|
|
651
|
+
this.#resolvedFileCache.clear();
|
|
652
|
+
this.#resolvedSpecifierCache.clear();
|
|
653
|
+
}
|
|
654
|
+
#lookup(cache, key, run, isFound) {
|
|
655
|
+
const cached = cache.get(key);
|
|
656
|
+
if (cached && (cached.found || cached.epoch === this.#epoch))
|
|
657
|
+
return cached.promise;
|
|
658
|
+
const entry = { epoch: this.#epoch, promise: run() };
|
|
659
|
+
entry.promise.then((value) => {
|
|
660
|
+
entry.found = isFound(value);
|
|
661
|
+
}, () => {
|
|
662
|
+
entry.found = false;
|
|
663
|
+
});
|
|
664
|
+
cache.set(key, entry);
|
|
665
|
+
return entry.promise;
|
|
666
|
+
}
|
|
667
|
+
async#fileExists(p) {
|
|
668
|
+
const absPath = path4.resolve(p);
|
|
669
|
+
return await this.#lookup(this.#fileExistsCache, absPath, () => Bun.file(absPath).exists(), (exists) => exists);
|
|
670
|
+
}
|
|
671
|
+
#facts(file) {
|
|
672
|
+
const absPath = path4.resolve(file);
|
|
673
|
+
let cached = this.#factsCache.get(absPath);
|
|
674
|
+
if (!cached) {
|
|
675
|
+
cached = (async () => {
|
|
676
|
+
const content = await Bun.file(absPath).text().catch(() => null);
|
|
677
|
+
if (content === null)
|
|
678
|
+
return null;
|
|
679
|
+
this.#readFiles.add(absPath);
|
|
680
|
+
if (USE_CLIENT_RE.test(content))
|
|
681
|
+
return { isClientEntry: true, imports: [] };
|
|
682
|
+
return { isClientEntry: false, imports: this.#scanImports(await this.#rewrite(content)) };
|
|
683
|
+
})();
|
|
684
|
+
this.#factsCache.set(absPath, cached);
|
|
685
|
+
}
|
|
686
|
+
return cached;
|
|
687
|
+
}
|
|
688
|
+
async#rewrite(content) {
|
|
689
|
+
if (this.#akanConfig.barrelImports.length === 0)
|
|
690
|
+
return content;
|
|
691
|
+
try {
|
|
692
|
+
return await rewriteBarrelImports(content, this.#akanConfig.barrelImports, this.#analyzer) ?? content;
|
|
693
|
+
} catch {
|
|
694
|
+
return content;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
#scanImports(source) {
|
|
698
|
+
try {
|
|
699
|
+
return this.#tsTranspiler.scanImports(source);
|
|
700
|
+
} catch {
|
|
701
|
+
return [];
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async#resolveFileCandidate(absPathNoExt) {
|
|
705
|
+
const cacheKey = path4.resolve(absPathNoExt);
|
|
706
|
+
return await this.#lookup(this.#resolvedFileCache, cacheKey, async () => {
|
|
707
|
+
if (await this.#fileExists(cacheKey))
|
|
708
|
+
return cacheKey;
|
|
709
|
+
for (const ext of SOURCE_EXTS) {
|
|
710
|
+
const f = `${cacheKey}${ext}`;
|
|
711
|
+
if (await this.#fileExists(f))
|
|
712
|
+
return f;
|
|
713
|
+
}
|
|
714
|
+
for (const ext of SOURCE_EXTS) {
|
|
715
|
+
const f = path4.join(cacheKey, `index${ext}`);
|
|
716
|
+
if (await this.#fileExists(f))
|
|
717
|
+
return f;
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
}, (file) => file !== null);
|
|
721
|
+
}
|
|
722
|
+
async#resolveSpecifier(spec, importerDir) {
|
|
723
|
+
const cacheKey = `${importerDir}\x00${spec}`;
|
|
724
|
+
return await this.#lookup(this.#resolvedSpecifierCache, cacheKey, async () => {
|
|
725
|
+
if (spec.startsWith(".") || spec.startsWith("/")) {
|
|
726
|
+
const abs = spec.startsWith("/") ? spec : path4.resolve(importerDir, spec);
|
|
727
|
+
return await this.#resolveFileCandidate(abs);
|
|
728
|
+
}
|
|
729
|
+
const pkg = await this.#resolvePackage(spec);
|
|
730
|
+
return pkg ? pkg.entryFile : null;
|
|
731
|
+
}, (file) => file !== null);
|
|
732
|
+
}
|
|
733
|
+
async#discoverFromFile(file, walk) {
|
|
734
|
+
const epoch = this.#epoch;
|
|
735
|
+
const absPath = path4.resolve(file);
|
|
736
|
+
const cached = this.#reachableEntriesCache.get(absPath);
|
|
737
|
+
if (cached)
|
|
738
|
+
return new Set(cached);
|
|
739
|
+
const memo = walk.memo.get(absPath);
|
|
740
|
+
if (memo) {
|
|
741
|
+
if (walk.partial.has(absPath))
|
|
742
|
+
for (const onPath of walk.visiting)
|
|
743
|
+
walk.partial.add(onPath);
|
|
744
|
+
return new Set(memo);
|
|
745
|
+
}
|
|
746
|
+
if (walk.visiting.has(absPath)) {
|
|
747
|
+
let below = false;
|
|
748
|
+
for (const onPath of walk.visiting) {
|
|
749
|
+
if (below)
|
|
750
|
+
walk.partial.add(onPath);
|
|
751
|
+
else
|
|
752
|
+
below = onPath === absPath;
|
|
753
|
+
}
|
|
754
|
+
return new Set;
|
|
755
|
+
}
|
|
756
|
+
if (shouldSkipNodeModule(absPath))
|
|
757
|
+
return new Set;
|
|
758
|
+
walk.visiting.add(absPath);
|
|
759
|
+
const entries = new Set;
|
|
760
|
+
const facts = await this.#facts(absPath);
|
|
761
|
+
if (!facts)
|
|
762
|
+
return this.#finishDiscovery(absPath, walk, entries, epoch);
|
|
763
|
+
if (facts.isClientEntry) {
|
|
764
|
+
entries.add(absPath);
|
|
765
|
+
return this.#finishDiscovery(absPath, walk, entries, epoch);
|
|
766
|
+
}
|
|
767
|
+
const importerDir = path4.dirname(absPath);
|
|
768
|
+
for (const imp of facts.imports) {
|
|
769
|
+
const spec = imp.path;
|
|
770
|
+
if (!spec)
|
|
771
|
+
continue;
|
|
772
|
+
if (NON_SOURCE_EXT_RE.test(spec))
|
|
773
|
+
continue;
|
|
774
|
+
const resolved = await this.#resolveSpecifier(spec, importerDir);
|
|
775
|
+
if (!resolved)
|
|
776
|
+
continue;
|
|
777
|
+
if (shouldSkipNodeModule(resolved))
|
|
778
|
+
continue;
|
|
779
|
+
for (const entry of await this.#discoverFromFile(resolved, walk))
|
|
780
|
+
entries.add(entry);
|
|
781
|
+
}
|
|
782
|
+
return this.#finishDiscovery(absPath, walk, entries, epoch);
|
|
783
|
+
}
|
|
784
|
+
#finishDiscovery(absPath, walk, entries, epoch) {
|
|
785
|
+
walk.visiting.delete(absPath);
|
|
786
|
+
walk.memo.set(absPath, entries);
|
|
787
|
+
if (epoch === this.#epoch && !walk.partial.has(absPath))
|
|
788
|
+
this.#reachableEntriesCache.set(absPath, entries);
|
|
789
|
+
return new Set(entries);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// pkgs/@akanjs/devkit/frontendBuild/routeClientBuilder.ts
|
|
794
|
+
import { mkdir } from "fs/promises";
|
|
795
|
+
import path8 from "path";
|
|
796
|
+
import { SSR_DEV_CHUNK, SSR_DEV_ID_PREFIX } from "akanjs/server/hmr/csrDevManifest";
|
|
797
|
+
|
|
798
|
+
// pkgs/@akanjs/devkit/transforms/rscUseClientTransform.ts
|
|
799
|
+
import path5 from "path";
|
|
800
|
+
import ts2 from "typescript";
|
|
801
|
+
var USE_CLIENT_RE2 = /^\s*(?:\/\*[\s\S]*?\*\/\s*|\/\/[^\n]*\n\s*)*["']use client["']/;
|
|
802
|
+
var IMPLICIT_ROOT_LAYOUT_RE = /[/\\]\.akan[/\\]generated[/\\](?:implicit-root-layout|root-layouts[/\\].*__root_layout)\.(tsx|ts|jsx|js)$/;
|
|
803
|
+
var STAR_REEXPORT_HINT_RE = /\bexport\s*\*\s*from\b/;
|
|
804
|
+
var hasUseClientDirective = (source) => USE_CLIENT_RE2.test(source);
|
|
805
|
+
function toClientReferencePath(absPath, workspaceRoot) {
|
|
806
|
+
return path5.relative(path5.resolve(workspaceRoot), path5.resolve(absPath)).split(path5.sep).join("/");
|
|
807
|
+
}
|
|
808
|
+
var starReexportSpecifiers = (source, filePath) => ts2.createSourceFile(filePath, source, ts2.ScriptTarget.Latest).statements.filter((statement) => ts2.isExportDeclaration(statement) && !statement.exportClause && !statement.isTypeOnly && !!statement.moduleSpecifier && ts2.isStringLiteral(statement.moduleSpecifier)).map((statement) => statement.moduleSpecifier.text);
|
|
809
|
+
var scanUseClientExports = (source, filePath, workspaceRoot) => {
|
|
810
|
+
const stars = USE_CLIENT_RE2.test(source) && STAR_REEXPORT_HINT_RE.test(source) ? starReexportSpecifiers(source, filePath) : [];
|
|
811
|
+
if (stars.length) {
|
|
812
|
+
const file = workspaceRoot ? toClientReferencePath(filePath, workspaceRoot) : filePath;
|
|
813
|
+
const listed = stars.slice(0, 3).map((specifier) => `\`export * from ${JSON.stringify(specifier)}\``);
|
|
814
|
+
const written = stars.length > 3 ? `${listed.join(", ")} (and ${stars.length - 3} more)` : listed.join(", ");
|
|
815
|
+
throw new Error(`${file} is a "use client" module, so it cannot ${written}: the server sees only the names a client module ` + `declares, and a star re-export declares none. Re-export them by name \u2014 \`export { \u2026 } from ${JSON.stringify(stars[0])}\`.`);
|
|
816
|
+
}
|
|
817
|
+
return new Bun.Transpiler({ loader: loaderFor(filePath) }).scan(source).exports;
|
|
818
|
+
};
|
|
819
|
+
function transformUseClient(source, args) {
|
|
820
|
+
if (!USE_CLIENT_RE2.test(source))
|
|
821
|
+
return null;
|
|
822
|
+
if (IMPLICIT_ROOT_LAYOUT_RE.test(args.path))
|
|
823
|
+
return null;
|
|
824
|
+
const exports = scanUseClientExports(source, args.path, args.workspaceRoot);
|
|
825
|
+
if (exports.length === 0)
|
|
826
|
+
return null;
|
|
827
|
+
args.onClientModule?.(args.path, exports);
|
|
828
|
+
const referencePath = args.workspaceRoot ? toClientReferencePath(args.path, args.workspaceRoot) : args.path;
|
|
829
|
+
const filePathLit = JSON.stringify(referencePath);
|
|
830
|
+
const lines = [`import { registerClientReference } from "react-server-dom-webpack/server.node";`];
|
|
831
|
+
for (const name of exports) {
|
|
832
|
+
const nameLit = JSON.stringify(name);
|
|
833
|
+
const errMsg = JSON.stringify(`Attempted to call '${name}' from '${referencePath}' on the server, but it is a client-only export.`);
|
|
834
|
+
const proxy = `() => { throw new Error(${errMsg}); }`;
|
|
835
|
+
const binding = name === "default" ? `export default registerClientReference(${proxy}, ${filePathLit}, ${nameLit});` : `export const ${name} = registerClientReference(${proxy}, ${filePathLit}, ${nameLit});`;
|
|
836
|
+
lines.push(binding);
|
|
837
|
+
}
|
|
838
|
+
return lines.join(`
|
|
839
|
+
`);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// pkgs/@akanjs/devkit/frontendBuild/clientEntriesBundler.ts
|
|
843
|
+
import fs2 from "fs";
|
|
844
|
+
import path6 from "path";
|
|
845
|
+
|
|
846
|
+
// pkgs/@akanjs/devkit/frontendBuild/bundleDefine.ts
|
|
847
|
+
var bundleDefine = (app, command, renderEnv) => {
|
|
848
|
+
const nodeEnv = command === "build" ? "production" : "development";
|
|
849
|
+
return {
|
|
850
|
+
"process.env.NODE_ENV": JSON.stringify(nodeEnv),
|
|
851
|
+
"process.env.AKAN_PUBLIC_RENDER_ENV": JSON.stringify(renderEnv),
|
|
852
|
+
...Object.fromEntries(Object.entries(app.getPublicEnv()).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]))
|
|
853
|
+
};
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
// pkgs/@akanjs/devkit/frontendBuild/clientBuildTypes.ts
|
|
857
|
+
var CLIENT_BUNDLE_NAMING = {
|
|
858
|
+
entry: "[name]-[hash].[ext]",
|
|
859
|
+
chunk: "chunks/[hash].[ext]",
|
|
860
|
+
asset: "assets/[hash].[ext]"
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
// pkgs/@akanjs/devkit/frontendBuild/clientEntriesBundler.ts
|
|
864
|
+
class ClientEntriesBundler {
|
|
865
|
+
#app;
|
|
866
|
+
#entries;
|
|
867
|
+
#plugins;
|
|
868
|
+
#external;
|
|
869
|
+
#externalSubpaths;
|
|
870
|
+
#externalAliases;
|
|
871
|
+
#command;
|
|
872
|
+
#target;
|
|
873
|
+
#outputSubdir;
|
|
874
|
+
#reactFastRefresh;
|
|
875
|
+
#outdir;
|
|
876
|
+
#servePrefix;
|
|
877
|
+
#manifest = {};
|
|
878
|
+
#ssrManifest = { moduleLoading: null, moduleMap: {} };
|
|
879
|
+
#entryUrlsByAbsPath = new Map;
|
|
880
|
+
#entryOutputAbsByAbsPath = new Map;
|
|
881
|
+
#entryDepsByAbsPath = new Map;
|
|
882
|
+
#clientReferenceIdByAbsPath = new Map;
|
|
883
|
+
#opaqueEntries = null;
|
|
884
|
+
#metafileOutputsByAbs = new Map;
|
|
885
|
+
constructor(options) {
|
|
886
|
+
this.#app = options.app;
|
|
887
|
+
this.#entries = options.entries;
|
|
888
|
+
this.#plugins = options.plugins ?? [];
|
|
889
|
+
this.#external = options.external ?? [];
|
|
890
|
+
this.#externalSubpaths = options.externalSubpaths ?? [];
|
|
891
|
+
this.#externalAliases = options.externalAliases ?? {};
|
|
892
|
+
this.#command = options.command ?? "start";
|
|
893
|
+
this.#target = options.target ?? "browser";
|
|
894
|
+
this.#outputSubdir = options.outputSubdir ?? "client";
|
|
895
|
+
this.#reactFastRefresh = options.reactFastRefresh ?? false;
|
|
896
|
+
const artifactDir = `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
|
|
897
|
+
this.#outdir = `${artifactDir}/${this.#outputSubdir}`;
|
|
898
|
+
this.#servePrefix = `/_akan/${this.#outputSubdir}`;
|
|
899
|
+
}
|
|
900
|
+
async bundle() {
|
|
901
|
+
const akanConfig = await this.#app.getConfig();
|
|
902
|
+
this.#opaqueEntries = await this.#createOpaqueEntryAliases();
|
|
903
|
+
const result = await Bun.build({
|
|
904
|
+
entrypoints: this.#opaqueEntries.entries,
|
|
905
|
+
outdir: this.#outdir,
|
|
906
|
+
splitting: true,
|
|
907
|
+
target: this.#target,
|
|
908
|
+
format: "esm",
|
|
909
|
+
naming: CLIENT_BUNDLE_NAMING,
|
|
910
|
+
metafile: true,
|
|
911
|
+
define: bundleDefine(this.#app, this.#command, "ssr"),
|
|
912
|
+
minify: this.#command === "build",
|
|
913
|
+
optimizeImports: akanConfig.optimizeImports,
|
|
914
|
+
reactFastRefresh: this.#command === "start" && this.#outputSubdir === "client" && this.#reactFastRefresh,
|
|
915
|
+
plugins: this.#external.length > 0 || this.#externalSubpaths.length > 0 || Object.keys(this.#externalAliases).length > 0 ? [...this.#plugins, this.#createExternalSpecifiersPlugin()] : this.#plugins
|
|
916
|
+
});
|
|
917
|
+
if (!result.success)
|
|
918
|
+
throw new AggregateError(result.logs, "[ClientEntriesBundler] Bun.build failed");
|
|
919
|
+
await this.#rewriteExternalImportSpecifiers(result.outputs);
|
|
920
|
+
const metafile = result.metafile;
|
|
921
|
+
if (!metafile)
|
|
922
|
+
throw new Error("[ClientEntriesBundler] metafile is missing");
|
|
923
|
+
this.#metafileOutputsByAbs = new Map(Object.entries(metafile.outputs).map(([k, v]) => [this.#absFromOutdir(k), v]));
|
|
924
|
+
for (const artifact of result.outputs.filter((a) => a.kind === "entry-point"))
|
|
925
|
+
this.#addEntryArtifact(artifact.path);
|
|
926
|
+
return {
|
|
927
|
+
manifest: this.#manifest,
|
|
928
|
+
ssrManifest: this.#ssrManifest,
|
|
929
|
+
entryUrlsByAbsPath: this.#entryUrlsByAbsPath,
|
|
930
|
+
entryOutputAbsByAbsPath: this.#entryOutputAbsByAbsPath,
|
|
931
|
+
entryDepsByAbsPath: this.#entryDepsByAbsPath,
|
|
932
|
+
clientReferenceIdByAbsPath: this.#clientReferenceIdByAbsPath
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
async#createOpaqueEntryAliases() {
|
|
936
|
+
const aliasDir = path6.join(this.#app.cwdPath, ".akan", "generated", "client-entry-alias", this.#outputSubdir);
|
|
937
|
+
fs2.mkdirSync(aliasDir, { recursive: true });
|
|
938
|
+
const originalByAlias = new Map;
|
|
939
|
+
const aliasedEntries = await Promise.all(this.#entries.map(async (entry) => {
|
|
940
|
+
const absEntry = path6.resolve(entry);
|
|
941
|
+
const hash = Bun.hash(`${this.#app.name}
|
|
942
|
+
${this.#outputSubdir}
|
|
943
|
+
${absEntry}`).toString(36);
|
|
944
|
+
const aliasPath = path6.join(aliasDir, `${hash}.tsx`);
|
|
945
|
+
await Bun.write(aliasPath, this.#createOpaqueEntryAliasSource(absEntry, await this.#scanEntryExportNames(absEntry)));
|
|
946
|
+
originalByAlias.set(path6.resolve(aliasPath), absEntry);
|
|
947
|
+
return aliasPath;
|
|
948
|
+
}));
|
|
949
|
+
return { entries: aliasedEntries, originalByAlias, aliasDir };
|
|
950
|
+
}
|
|
951
|
+
#createOpaqueEntryAliasSource(absEntry, exportNames) {
|
|
952
|
+
const entryLit = JSON.stringify(path6.resolve(absEntry));
|
|
953
|
+
if (exportNames.length === 0)
|
|
954
|
+
return `export * from ${entryLit};
|
|
955
|
+
`;
|
|
956
|
+
const namedExports = exportNames.filter((name) => name !== "default");
|
|
957
|
+
const lines = namedExports.length > 0 ? [`export { ${namedExports.join(", ")} } from ${entryLit};`] : [];
|
|
958
|
+
if (exportNames.includes("default"))
|
|
959
|
+
lines.push(`export { default } from ${entryLit};`);
|
|
960
|
+
return `${lines.join(`
|
|
961
|
+
`)}
|
|
962
|
+
`;
|
|
963
|
+
}
|
|
964
|
+
async#scanEntryExportNames(absEntry) {
|
|
965
|
+
return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
|
|
966
|
+
}
|
|
967
|
+
#createExternalSpecifiersPlugin() {
|
|
968
|
+
const set = new Set(this.#external);
|
|
969
|
+
const subpathSet = new Set(this.#externalSubpaths);
|
|
970
|
+
const aliases = this.#externalAliases;
|
|
971
|
+
const specifiers = [...new Set([...this.#external, ...this.#externalSubpaths, ...Object.keys(aliases)])];
|
|
972
|
+
const escaped = specifiers.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
973
|
+
const filter = new RegExp(`^(${escaped.join("|")})(?:/.*)?$`);
|
|
974
|
+
return {
|
|
975
|
+
name: "akan-externalize-specifiers",
|
|
976
|
+
setup(build) {
|
|
977
|
+
build.onResolve({ filter }, (args) => {
|
|
978
|
+
const alias = aliases[args.path];
|
|
979
|
+
if (alias)
|
|
980
|
+
return { path: alias, external: true };
|
|
981
|
+
if (!ClientEntriesBundler.#matchesExternalSpecifier(args.path, set, subpathSet))
|
|
982
|
+
return;
|
|
983
|
+
return { path: args.path, external: true };
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
static #matchesExternalSpecifier(specifier, exactExternals, subpathExternals) {
|
|
989
|
+
if (exactExternals.has(specifier) || subpathExternals.has(specifier))
|
|
990
|
+
return true;
|
|
991
|
+
for (const external of subpathExternals)
|
|
992
|
+
if (specifier.startsWith(`${external}/`))
|
|
993
|
+
return true;
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
async#rewriteExternalImportSpecifiers(outputs) {
|
|
997
|
+
if (Object.keys(this.#externalAliases).length === 0)
|
|
998
|
+
return;
|
|
999
|
+
await Promise.all(outputs.filter((output) => output.path.endsWith(".js")).map(async (output) => {
|
|
1000
|
+
const source = await Bun.file(output.path).text();
|
|
1001
|
+
const rewritten = ClientEntriesBundler.rewriteExternalImportSpecifiers(source, this.#externalAliases);
|
|
1002
|
+
if (rewritten !== source)
|
|
1003
|
+
await Bun.write(output.path, rewritten);
|
|
1004
|
+
}));
|
|
1005
|
+
}
|
|
1006
|
+
static rewriteExternalImportSpecifiers(source, aliases) {
|
|
1007
|
+
let rewritten = source;
|
|
1008
|
+
for (const [specifier, alias] of Object.entries(aliases)) {
|
|
1009
|
+
if (!alias)
|
|
1010
|
+
continue;
|
|
1011
|
+
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1012
|
+
rewritten = rewritten.replace(new RegExp(`(\\bfrom\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
|
|
1013
|
+
return `${prefix}${alias}${suffix}`;
|
|
1014
|
+
}).replace(new RegExp(`(\\bimport\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
|
|
1015
|
+
return `${prefix}${alias}${suffix}`;
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
return rewritten;
|
|
1019
|
+
}
|
|
1020
|
+
#toServeUrl(absOutPath) {
|
|
1021
|
+
const rel = path6.relative(this.#outdir, absOutPath).split(path6.sep).join("/");
|
|
1022
|
+
return `${this.#servePrefix}/${rel}`;
|
|
1023
|
+
}
|
|
1024
|
+
#absFromOutdir(p) {
|
|
1025
|
+
return path6.isAbsolute(p) ? p : path6.resolve(this.#outdir, p);
|
|
1026
|
+
}
|
|
1027
|
+
#absFromEntryPoint(p) {
|
|
1028
|
+
if (path6.isAbsolute(p))
|
|
1029
|
+
return path6.resolve(p);
|
|
1030
|
+
const candidates = [path6.resolve(process.cwd(), p), path6.resolve(this.#app.cwdPath, p)];
|
|
1031
|
+
return candidates.find((candidate) => fs2.existsSync(candidate)) ?? candidates[0];
|
|
1032
|
+
}
|
|
1033
|
+
#collectChunkUrls(absOutPath, visited = new Set) {
|
|
1034
|
+
if (visited.has(absOutPath))
|
|
1035
|
+
return [];
|
|
1036
|
+
visited.add(absOutPath);
|
|
1037
|
+
const info = this.#metafileOutputsByAbs.get(absOutPath);
|
|
1038
|
+
if (!info)
|
|
1039
|
+
return [this.#toServeUrl(absOutPath)];
|
|
1040
|
+
const urls = [this.#toServeUrl(absOutPath)];
|
|
1041
|
+
for (const imp of info.imports) {
|
|
1042
|
+
if (imp.kind === "dynamic-import")
|
|
1043
|
+
continue;
|
|
1044
|
+
const absImp = this.#absFromOutdir(imp.path);
|
|
1045
|
+
if (!absImp.endsWith(".js"))
|
|
1046
|
+
continue;
|
|
1047
|
+
urls.push(...this.#collectChunkUrls(absImp, visited));
|
|
1048
|
+
}
|
|
1049
|
+
return urls;
|
|
1050
|
+
}
|
|
1051
|
+
#collectClientDeps(absOutPath, visited = new Set) {
|
|
1052
|
+
if (visited.has(absOutPath))
|
|
1053
|
+
return new Set;
|
|
1054
|
+
visited.add(absOutPath);
|
|
1055
|
+
const deps = new Set;
|
|
1056
|
+
const info = this.#metafileOutputsByAbs.get(absOutPath);
|
|
1057
|
+
if (!info)
|
|
1058
|
+
return deps;
|
|
1059
|
+
if (!this.#opaqueEntries)
|
|
1060
|
+
throw new Error("[ClientEntriesBundler] opaque entries are missing");
|
|
1061
|
+
for (const input of Object.keys(info.inputs ?? {})) {
|
|
1062
|
+
const absInput = this.#absFromEntryPoint(input);
|
|
1063
|
+
if (!absInput.includes("node_modules") && !absInput.startsWith(this.#opaqueEntries.aliasDir))
|
|
1064
|
+
deps.add(absInput);
|
|
1065
|
+
}
|
|
1066
|
+
for (const imp of info.imports) {
|
|
1067
|
+
if (imp.kind === "dynamic-import")
|
|
1068
|
+
continue;
|
|
1069
|
+
const absImp = this.#absFromOutdir(imp.path);
|
|
1070
|
+
if (!absImp.endsWith(".js"))
|
|
1071
|
+
continue;
|
|
1072
|
+
for (const dep of this.#collectClientDeps(absImp, visited))
|
|
1073
|
+
deps.add(dep);
|
|
1074
|
+
}
|
|
1075
|
+
return deps;
|
|
1076
|
+
}
|
|
1077
|
+
#addEntryArtifact(absOut) {
|
|
1078
|
+
if (!this.#opaqueEntries)
|
|
1079
|
+
throw new Error("[ClientEntriesBundler] opaque entries are missing");
|
|
1080
|
+
const info = this.#metafileOutputsByAbs.get(absOut);
|
|
1081
|
+
const buildEntryPointAbs = info?.entryPoint ? this.#absFromEntryPoint(info.entryPoint) : null;
|
|
1082
|
+
const entryPointAbs = buildEntryPointAbs ? this.#opaqueEntries.originalByAlias.get(buildEntryPointAbs) ?? buildEntryPointAbs : null;
|
|
1083
|
+
if (!entryPointAbs)
|
|
1084
|
+
return;
|
|
1085
|
+
const chunkUrls = this.#collectChunkUrls(absOut);
|
|
1086
|
+
const entryUrl = chunkUrls[0] ?? this.#toServeUrl(absOut);
|
|
1087
|
+
this.#entryUrlsByAbsPath.set(entryPointAbs, entryUrl);
|
|
1088
|
+
this.#entryOutputAbsByAbsPath.set(entryPointAbs, absOut);
|
|
1089
|
+
this.#entryDepsByAbsPath.set(entryPointAbs, [...this.#collectClientDeps(absOut)].sort());
|
|
1090
|
+
const clientReferenceId = toClientReferencePath(entryPointAbs, this.#app.workspace.workspaceRoot);
|
|
1091
|
+
this.#clientReferenceIdByAbsPath.set(entryPointAbs, clientReferenceId);
|
|
1092
|
+
const flatChunks = [];
|
|
1093
|
+
for (const url of chunkUrls)
|
|
1094
|
+
flatChunks.push(url, url);
|
|
1095
|
+
const exportNames = info?.exports && info.exports.length > 0 ? info.exports : ["default"];
|
|
1096
|
+
const ssrEntriesByName = {};
|
|
1097
|
+
for (const name of exportNames) {
|
|
1098
|
+
const key = `${clientReferenceId}#${name}`;
|
|
1099
|
+
this.#manifest[key] = { id: entryUrl, chunks: flatChunks, name, async: true };
|
|
1100
|
+
ssrEntriesByName[name] = { id: absOut, chunks: [absOut, absOut], name, async: true };
|
|
1101
|
+
}
|
|
1102
|
+
this.#ssrManifest.moduleMap[entryUrl] = ssrEntriesByName;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevPaths.ts
|
|
1107
|
+
import fs3 from "fs";
|
|
1108
|
+
import path7 from "path";
|
|
1109
|
+
|
|
1110
|
+
class CsrDevPaths {
|
|
1111
|
+
static modulePrefix = "akan-module:";
|
|
1112
|
+
static stubPrefix = "stub:";
|
|
1113
|
+
static emptyStubPrefix = "stub:empty:";
|
|
1114
|
+
static vendorPrefix = "vendor:";
|
|
1115
|
+
root;
|
|
1116
|
+
constructor(workspaceRoot) {
|
|
1117
|
+
this.root = CsrDevPaths.realpath(workspaceRoot);
|
|
1118
|
+
}
|
|
1119
|
+
idOf(file) {
|
|
1120
|
+
const real = CsrDevPaths.realpath(file);
|
|
1121
|
+
const relative = path7.relative(this.root, real);
|
|
1122
|
+
const id = relative.startsWith("..") || path7.isAbsolute(relative) ? real : relative;
|
|
1123
|
+
return id.split(path7.sep).join("/");
|
|
1124
|
+
}
|
|
1125
|
+
fileOf(id) {
|
|
1126
|
+
return path7.isAbsolute(id) ? id : path7.join(this.root, id);
|
|
1127
|
+
}
|
|
1128
|
+
static isStub(target) {
|
|
1129
|
+
return target.startsWith(CsrDevPaths.stubPrefix);
|
|
1130
|
+
}
|
|
1131
|
+
static isVendorFile(file) {
|
|
1132
|
+
return file.includes(`${path7.sep}node_modules${path7.sep}`);
|
|
1133
|
+
}
|
|
1134
|
+
static isScript(file) {
|
|
1135
|
+
return /\.[cm]?[jt]sx?$/.test(file);
|
|
1136
|
+
}
|
|
1137
|
+
static mtimeOf(file) {
|
|
1138
|
+
return fs3.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? -1;
|
|
1139
|
+
}
|
|
1140
|
+
static async hashOf(file) {
|
|
1141
|
+
const source = Bun.file(file);
|
|
1142
|
+
return await source.exists() ? Bun.hash(await source.arrayBuffer()).toString(36) : "";
|
|
1143
|
+
}
|
|
1144
|
+
static realpath(file) {
|
|
1145
|
+
return fs3.existsSync(file) ? fs3.realpathSync.native(file) : path7.resolve(file);
|
|
1146
|
+
}
|
|
1147
|
+
static isNamed(file) {
|
|
1148
|
+
try {
|
|
1149
|
+
return fs3.realpathSync.native(file) === file;
|
|
1150
|
+
} catch {
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
static #extensions = {
|
|
1155
|
+
import: [".tsx", ".jsx", ".mts", ".ts", ".mjs", ".js", ".cts", ".cjs"],
|
|
1156
|
+
require: [".tsx", ".ts", ".jsx", ".cts", ".cjs", ".js", ".mjs", ".mts"]
|
|
1157
|
+
};
|
|
1158
|
+
static resolveOnDisk(base, kind = "import") {
|
|
1159
|
+
return CsrDevPaths.#asFile(base, kind) ?? CsrDevPaths.#asFolder(base, kind);
|
|
1160
|
+
}
|
|
1161
|
+
static resolveRelative(dir, specifier, kind = "import") {
|
|
1162
|
+
const base = path7.resolve(dir, specifier);
|
|
1163
|
+
const folder = /(^|[\\/])\.\.?$|[\\/]$/.test(specifier);
|
|
1164
|
+
return folder ? CsrDevPaths.#asFolder(base, kind) : CsrDevPaths.resolveOnDisk(base, kind);
|
|
1165
|
+
}
|
|
1166
|
+
static #asFile(base, kind) {
|
|
1167
|
+
const isFile = (candidate) => fs3.statSync(candidate, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
1168
|
+
return [base, ...CsrDevPaths.#extensions[kind].map((extension) => `${base}${extension}`)].find(isFile) ?? null;
|
|
1169
|
+
}
|
|
1170
|
+
static #asFolder(dir, kind) {
|
|
1171
|
+
const entry = CsrDevPaths.#packageEntryOf(dir, kind);
|
|
1172
|
+
const named = entry ? CsrDevPaths.#asFile(path7.join(dir, entry), kind) ?? CsrDevPaths.#indexOf(path7.join(dir, entry), kind) : null;
|
|
1173
|
+
return named ?? CsrDevPaths.#indexOf(dir, kind);
|
|
1174
|
+
}
|
|
1175
|
+
static #indexOf(dir, kind) {
|
|
1176
|
+
return CsrDevPaths.#asFile(path7.join(dir, "index"), kind);
|
|
1177
|
+
}
|
|
1178
|
+
static #packageEntryOf(dir, kind) {
|
|
1179
|
+
try {
|
|
1180
|
+
const pkg = JSON.parse(fs3.readFileSync(path7.join(dir, "package.json"), "utf8"));
|
|
1181
|
+
const fields = kind === "require" ? [pkg.browser, pkg.main] : [pkg.browser, pkg.module, pkg.main];
|
|
1182
|
+
const entry = fields.find((field) => typeof field === "string");
|
|
1183
|
+
return typeof entry === "string" ? entry : null;
|
|
1184
|
+
} catch {
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
static installedSince(specifier, from, since) {
|
|
1189
|
+
const [first = "", second = ""] = specifier.split("/");
|
|
1190
|
+
const name = first.startsWith("@") ? `${first}/${second}` : first;
|
|
1191
|
+
if (!name || name.startsWith("."))
|
|
1192
|
+
return false;
|
|
1193
|
+
for (let dir = path7.resolve(from);; dir = path7.dirname(dir)) {
|
|
1194
|
+
const pkgDir = path7.join(dir, "node_modules", name);
|
|
1195
|
+
if (fs3.existsSync(path7.join(pkgDir, "package.json")))
|
|
1196
|
+
return (fs3.statSync(pkgDir, { throwIfNoEntry: false })?.ctimeMs ?? 0) > since;
|
|
1197
|
+
if (path7.dirname(dir) === dir)
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
static tryResolve(specifier, from) {
|
|
1202
|
+
try {
|
|
1203
|
+
return Bun.resolveSync(specifier, from);
|
|
1204
|
+
} catch {
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
static lineCount(text) {
|
|
1209
|
+
let count = 0;
|
|
1210
|
+
for (let index = text.indexOf(`
|
|
1211
|
+
`);index !== -1; index = text.indexOf(`
|
|
1212
|
+
`, index + 1))
|
|
1213
|
+
count += 1;
|
|
1214
|
+
return count;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// pkgs/@akanjs/devkit/frontendBuild/vendorSpecifiers.ts
|
|
1219
|
+
var VENDOR_SPECIFIERS = [
|
|
1220
|
+
"react",
|
|
1221
|
+
"react-dom",
|
|
1222
|
+
"react-dom/client",
|
|
1223
|
+
"react/jsx-runtime",
|
|
1224
|
+
"react/jsx-dev-runtime",
|
|
1225
|
+
"react-refresh/runtime",
|
|
1226
|
+
"scheduler",
|
|
1227
|
+
"react-server-dom-webpack/client.browser",
|
|
1228
|
+
"akanjs/store",
|
|
1229
|
+
"akanjs/base",
|
|
1230
|
+
"akanjs/common",
|
|
1231
|
+
"akanjs/constant",
|
|
1232
|
+
"akanjs/fetch"
|
|
1233
|
+
];
|
|
1234
|
+
|
|
1235
|
+
// pkgs/@akanjs/devkit/frontendBuild/routeClientBuilder.ts
|
|
1236
|
+
var SSR_CLIENT_ALIAS_EXTERNALS = [
|
|
1237
|
+
"react",
|
|
1238
|
+
"react-dom",
|
|
1239
|
+
"react-dom/client",
|
|
1240
|
+
"react/jsx-runtime",
|
|
1241
|
+
"react/jsx-dev-runtime"
|
|
1242
|
+
];
|
|
1243
|
+
var SSR_CLIENT_EXTERNALS = [...SSR_CLIENT_ALIAS_EXTERNALS, "akanjs/fetch"];
|
|
1244
|
+
|
|
1245
|
+
class RouteClientBuilder {
|
|
1246
|
+
#app;
|
|
1247
|
+
#seeds;
|
|
1248
|
+
#graphSeeds;
|
|
1249
|
+
#knownEntries;
|
|
1250
|
+
#command;
|
|
1251
|
+
#discovery;
|
|
1252
|
+
#entries;
|
|
1253
|
+
#browser;
|
|
1254
|
+
constructor(options) {
|
|
1255
|
+
this.#app = options.app;
|
|
1256
|
+
this.#seeds = options.seeds;
|
|
1257
|
+
this.#graphSeeds = options.graphSeeds;
|
|
1258
|
+
this.#knownEntries = options.knownEntries ?? new Set;
|
|
1259
|
+
this.#command = options.command ?? "start";
|
|
1260
|
+
this.#discovery = options.discovery;
|
|
1261
|
+
this.#entries = options.entries;
|
|
1262
|
+
this.#browser = options.browser ?? (this.#command === "start" ? "registry" : "chunks");
|
|
1263
|
+
}
|
|
1264
|
+
async build() {
|
|
1265
|
+
const discovered = this.#entries ?? await (await this.#getDiscovery()).discover(this.#seeds);
|
|
1266
|
+
if (discovered.every((e) => this.#knownEntries.has(e)))
|
|
1267
|
+
return this.#emptyResult(discovered);
|
|
1268
|
+
const entries = this.#graphSeeds ? [...new Set([...discovered, ...await (await this.#getDiscovery()).discover(this.#graphSeeds)])].sort() : discovered.filter((e) => !this.#knownEntries.has(e));
|
|
1269
|
+
const bootstrapEntries = await this.#createBootstrapEntries(entries);
|
|
1270
|
+
const registry = this.#browser === "registry" ? new CsrDevPaths(this.#app.workspace.workspaceRoot) : null;
|
|
1271
|
+
const browserBundle = registry ? null : await this.#buildBrowserBundle(bootstrapEntries);
|
|
1272
|
+
const ssrBundle = await this.#buildSsrBundle(bootstrapEntries);
|
|
1273
|
+
const referenceBundle = browserBundle ?? ssrBundle;
|
|
1274
|
+
const acceptedEntries = new Set(entries);
|
|
1275
|
+
const routeEntries = new Set(discovered);
|
|
1276
|
+
const manifestDelta = {};
|
|
1277
|
+
const ssrModuleMap = {};
|
|
1278
|
+
const clientDeps = new Set;
|
|
1279
|
+
const clientDepsByEntry = {};
|
|
1280
|
+
for (const [key, bundleRow] of Object.entries(referenceBundle.manifest)) {
|
|
1281
|
+
const manifestEntry = RouteClientBuilder.resolveOriginalManifestEntry(key, bootstrapEntries.originalByBuildEntry, referenceBundle.clientReferenceIdByAbsPath, this.#app.workspace.workspaceRoot);
|
|
1282
|
+
if (!manifestEntry)
|
|
1283
|
+
continue;
|
|
1284
|
+
if (!acceptedEntries.has(manifestEntry.originalEntry))
|
|
1285
|
+
continue;
|
|
1286
|
+
const row = registry ? RouteClientBuilder.#registryRow(registry, manifestEntry.originalEntry, bundleRow) : bundleRow;
|
|
1287
|
+
manifestDelta[manifestEntry.key] = row;
|
|
1288
|
+
const ssrOutput = ssrBundle.entryOutputAbsByAbsPath.get(manifestEntry.buildEntry);
|
|
1289
|
+
if (!ssrOutput)
|
|
1290
|
+
continue;
|
|
1291
|
+
ssrModuleMap[row.id] ??= {};
|
|
1292
|
+
ssrModuleMap[row.id][row.name] = { id: ssrOutput, chunks: [ssrOutput, ssrOutput], name: row.name, async: true };
|
|
1293
|
+
}
|
|
1294
|
+
for (const entry of bootstrapEntries.buildEntries) {
|
|
1295
|
+
const buildEntry = path8.resolve(entry);
|
|
1296
|
+
const originalEntry = path8.resolve(bootstrapEntries.originalByBuildEntry.get(buildEntry) ?? buildEntry);
|
|
1297
|
+
if (!acceptedEntries.has(originalEntry))
|
|
1298
|
+
continue;
|
|
1299
|
+
const deps = new Set([originalEntry]);
|
|
1300
|
+
for (const dep of referenceBundle.entryDepsByAbsPath.get(buildEntry) ?? [])
|
|
1301
|
+
deps.add(path8.resolve(dep));
|
|
1302
|
+
const sortedDeps = [...deps].sort();
|
|
1303
|
+
clientDepsByEntry[originalEntry] = sortedDeps;
|
|
1304
|
+
if (routeEntries.has(originalEntry))
|
|
1305
|
+
for (const dep of sortedDeps)
|
|
1306
|
+
clientDeps.add(dep);
|
|
1307
|
+
}
|
|
1308
|
+
return {
|
|
1309
|
+
manifestDelta,
|
|
1310
|
+
ssrManifestDelta: { moduleLoading: null, moduleMap: ssrModuleMap },
|
|
1311
|
+
newEntries: entries.filter((e) => !this.#knownEntries.has(e)),
|
|
1312
|
+
discoveredEntries: discovered,
|
|
1313
|
+
clientDeps: [...clientDeps].sort(),
|
|
1314
|
+
clientDepsByEntry,
|
|
1315
|
+
...registry ? { registryEntries: [...acceptedEntries].sort() } : {}
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
static #registryRow(paths, entry, row) {
|
|
1319
|
+
return {
|
|
1320
|
+
id: `${SSR_DEV_ID_PREFIX}${paths.idOf(entry)}`,
|
|
1321
|
+
chunks: [SSR_DEV_CHUNK, SSR_DEV_CHUNK],
|
|
1322
|
+
name: row.name,
|
|
1323
|
+
async: true
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
async#getDiscovery() {
|
|
1327
|
+
this.#discovery ??= await GraphClientEntryDiscovery.create(this.#app);
|
|
1328
|
+
return this.#discovery;
|
|
1329
|
+
}
|
|
1330
|
+
#emptyResult(discoveredEntries = []) {
|
|
1331
|
+
return {
|
|
1332
|
+
manifestDelta: {},
|
|
1333
|
+
ssrManifestDelta: { moduleLoading: null, moduleMap: {} },
|
|
1334
|
+
newEntries: [],
|
|
1335
|
+
discoveredEntries,
|
|
1336
|
+
clientDeps: [],
|
|
1337
|
+
clientDepsByEntry: {}
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
async#buildBrowserBundle(bootstrapEntries) {
|
|
1341
|
+
const reactFastRefresh = process.env.AKAN_REACT_FAST_REFRESH !== "0";
|
|
1342
|
+
return new ClientEntriesBundler({
|
|
1343
|
+
app: this.#app,
|
|
1344
|
+
entries: bootstrapEntries.buildEntries,
|
|
1345
|
+
plugins: [
|
|
1346
|
+
await createBarrelImportsPlugin(this.#app, {
|
|
1347
|
+
pipeAfter: reactFastRefresh ? RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh : undefined
|
|
1348
|
+
})
|
|
1349
|
+
],
|
|
1350
|
+
external: VENDOR_SPECIFIERS,
|
|
1351
|
+
command: this.#command,
|
|
1352
|
+
reactFastRefresh
|
|
1353
|
+
}).bundle();
|
|
1354
|
+
}
|
|
1355
|
+
async#buildSsrBundle(bootstrapEntries) {
|
|
1356
|
+
return new ClientEntriesBundler({
|
|
1357
|
+
app: this.#app,
|
|
1358
|
+
entries: bootstrapEntries.buildEntries,
|
|
1359
|
+
plugins: [await createBarrelImportsPlugin(this.#app)],
|
|
1360
|
+
...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
|
|
1361
|
+
outputSubdir: "client-ssr",
|
|
1362
|
+
command: this.#command
|
|
1363
|
+
}).bundle();
|
|
1364
|
+
}
|
|
1365
|
+
async#createBootstrapEntries(entries) {
|
|
1366
|
+
if (!await Bun.file(path8.join(this.#app.cwdPath, "lib", "st.ts")).exists()) {
|
|
1367
|
+
return { buildEntries: entries, originalByBuildEntry: new Map };
|
|
1368
|
+
}
|
|
1369
|
+
const outdir = path8.join(this.#app.cwdPath, ".akan", "generated", "client-entry-bootstrap");
|
|
1370
|
+
await mkdir(outdir, { recursive: true });
|
|
1371
|
+
const originalByBuildEntry = new Map;
|
|
1372
|
+
const buildEntries = await Promise.all(entries.map(async (entry) => {
|
|
1373
|
+
const absEntry = path8.resolve(entry);
|
|
1374
|
+
const hash = Bun.hash(`${this.#app.name}
|
|
1375
|
+
${absEntry}`).toString(36);
|
|
1376
|
+
const base = path8.basename(absEntry).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
1377
|
+
const wrapperEntry = path8.join(outdir, `${base}-${hash}.tsx`);
|
|
1378
|
+
const exportNames = await this.#scanExportNames(absEntry);
|
|
1379
|
+
await Bun.write(wrapperEntry, RouteClientBuilder.createStoreBootstrapEntrySource({
|
|
1380
|
+
appName: this.#app.name,
|
|
1381
|
+
originalEntry: absEntry,
|
|
1382
|
+
exportNames
|
|
1383
|
+
}));
|
|
1384
|
+
originalByBuildEntry.set(path8.resolve(wrapperEntry), absEntry);
|
|
1385
|
+
return wrapperEntry;
|
|
1386
|
+
}));
|
|
1387
|
+
return { buildEntries, originalByBuildEntry };
|
|
1388
|
+
}
|
|
1389
|
+
async#scanExportNames(absEntry) {
|
|
1390
|
+
return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
|
|
1391
|
+
}
|
|
1392
|
+
static normalizeNamedDefaultFunctionForFastRefresh(source, { path: filePath } = {}) {
|
|
1393
|
+
const declared = RouteClientBuilder.#declaredNamedDefaultFunctions(source, filePath);
|
|
1394
|
+
if (declared.length === 0)
|
|
1395
|
+
return null;
|
|
1396
|
+
let next = "";
|
|
1397
|
+
let cursor = 0;
|
|
1398
|
+
for (const { index, text, lineStart, indent, asyncKeyword, name } of declared) {
|
|
1399
|
+
next += `${source.slice(cursor, index)}${lineStart}${indent}${asyncKeyword}function ${name}`;
|
|
1400
|
+
cursor = index + text.length;
|
|
1401
|
+
}
|
|
1402
|
+
next += source.slice(cursor);
|
|
1403
|
+
return `${next}
|
|
1404
|
+
${declared.map(({ name }) => `export default ${name};`).join(`
|
|
1405
|
+
`)}
|
|
1406
|
+
`;
|
|
1407
|
+
}
|
|
1408
|
+
static #declaredNamedDefaultFunctions(source, filePath) {
|
|
1409
|
+
const candidates = [
|
|
1410
|
+
...source.matchAll(/(^|\n)(\s*)export\s+default\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)(?=\s*(?:<|\())/g)
|
|
1411
|
+
].map((match) => ({
|
|
1412
|
+
index: match.index,
|
|
1413
|
+
text: match[0],
|
|
1414
|
+
lineStart: match[1] ?? "",
|
|
1415
|
+
indent: match[2] ?? "",
|
|
1416
|
+
asyncKeyword: match[3] ?? "",
|
|
1417
|
+
name: match[4] ?? ""
|
|
1418
|
+
}));
|
|
1419
|
+
if (candidates.length === 0)
|
|
1420
|
+
return [];
|
|
1421
|
+
const probeName = (idx) => `__akanNamedDefault${idx}`;
|
|
1422
|
+
let probe = "";
|
|
1423
|
+
let cursor = 0;
|
|
1424
|
+
candidates.forEach(({ index, text, lineStart, indent, asyncKeyword }, idx) => {
|
|
1425
|
+
probe += `${source.slice(cursor, index)}${lineStart}${indent}export ${asyncKeyword}function ${probeName(idx)}`;
|
|
1426
|
+
cursor = index + text.length;
|
|
1427
|
+
});
|
|
1428
|
+
probe += source.slice(cursor);
|
|
1429
|
+
const exported = RouteClientBuilder.#scanExports(probe, filePath);
|
|
1430
|
+
return candidates.filter((_, idx) => exported.has(probeName(idx)));
|
|
1431
|
+
}
|
|
1432
|
+
static #scanExports(source, filePath) {
|
|
1433
|
+
try {
|
|
1434
|
+
return new Set(new Bun.Transpiler({ loader: filePath ? loaderFor(filePath) : "tsx" }).scan(source).exports);
|
|
1435
|
+
} catch {
|
|
1436
|
+
return new Set;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
static resolveSsrClientRuntimeAliases() {
|
|
1440
|
+
const serverEntry = RouteClientBuilder.resolveAkanServerEntry();
|
|
1441
|
+
return { [Bun.resolveSync("akanjs/fetch", serverEntry)]: "akanjs/fetch" };
|
|
1442
|
+
}
|
|
1443
|
+
static resolveSsrClientBundleOptions(command) {
|
|
1444
|
+
if (command === "start") {
|
|
1445
|
+
return {
|
|
1446
|
+
target: "bun",
|
|
1447
|
+
external: SSR_CLIENT_EXTERNALS,
|
|
1448
|
+
externalSubpaths: ["akanjs/fetch"],
|
|
1449
|
+
externalAliases: RouteClientBuilder.resolveSsrClientRuntimeAliases()
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
return { target: "bun", external: SSR_CLIENT_ALIAS_EXTERNALS };
|
|
1453
|
+
}
|
|
1454
|
+
static resolveAkanServerEntry() {
|
|
1455
|
+
try {
|
|
1456
|
+
return Bun.resolveSync("akanjs/server", import.meta.dir);
|
|
1457
|
+
} catch {
|
|
1458
|
+
return path8.resolve(import.meta.dir, "../../../server/index.ts");
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
static createStoreBootstrapEntrySource(args) {
|
|
1462
|
+
const originalEntry = JSON.stringify(path8.resolve(args.originalEntry));
|
|
1463
|
+
const namedExports = args.exportNames.filter((name) => name !== "default");
|
|
1464
|
+
const lines = [
|
|
1465
|
+
`import ${JSON.stringify(`@apps/${args.appName}/client`)};`,
|
|
1466
|
+
...namedExports.length > 0 ? [`export { ${namedExports.join(", ")} } from ${originalEntry};`] : []
|
|
1467
|
+
];
|
|
1468
|
+
if (args.exportNames.includes("default"))
|
|
1469
|
+
lines.push(`export { default } from ${originalEntry};`);
|
|
1470
|
+
return `${lines.join(`
|
|
1471
|
+
`)}
|
|
1472
|
+
`;
|
|
1473
|
+
}
|
|
1474
|
+
static resolveOriginalManifestEntry(manifestKey, originalByBuildEntry, clientReferenceIdByBuildEntry = new Map, workspaceRoot = process.cwd()) {
|
|
1475
|
+
const hashIdx = manifestKey.lastIndexOf("#");
|
|
1476
|
+
if (hashIdx < 0)
|
|
1477
|
+
return null;
|
|
1478
|
+
const buildReferenceId = manifestKey.slice(0, hashIdx);
|
|
1479
|
+
const name = manifestKey.slice(hashIdx + 1);
|
|
1480
|
+
const buildEntry = [...clientReferenceIdByBuildEntry.entries()].find(([, referenceId]) => referenceId === buildReferenceId)?.[0] ?? buildReferenceId;
|
|
1481
|
+
const originalEntry = originalByBuildEntry.get(buildEntry) ?? buildEntry;
|
|
1482
|
+
const originalReferenceId = toClientReferencePath(originalEntry, workspaceRoot);
|
|
1483
|
+
return { buildEntry, originalEntry, name, key: `${originalReferenceId}#${name}` };
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// pkgs/@akanjs/devkit/frontendBuild/autoImportSync.ts
|
|
1488
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
1489
|
+
import path9 from "path";
|
|
1490
|
+
import ts3 from "typescript";
|
|
1491
|
+
var TEST_FILE_RE = /\.(test|spec)\.(ts|tsx)$/;
|
|
1492
|
+
var AKAN_BASE = {
|
|
1493
|
+
names: ["Int", "Float", "ID", "Any", "Upload", "enumOf", "dayjs"],
|
|
1494
|
+
specifier: "akanjs/base",
|
|
1495
|
+
kind: "named"
|
|
1496
|
+
};
|
|
1497
|
+
var AKAN_BASE_TYPES = { names: ["Dayjs"], specifier: "akanjs/base", kind: "named", typeOnly: true };
|
|
1498
|
+
var CNST_NS = { names: ["cnst"], specifier: "../cnst", kind: "namespace" };
|
|
1499
|
+
var DB_NS = { names: ["db"], specifier: "../db", kind: "namespace" };
|
|
1500
|
+
var SRV_NS = { names: ["srv"], specifier: "../srv", kind: "namespace" };
|
|
1501
|
+
var ERR_DICT = { names: ["Err"], specifier: "../dict", kind: "named" };
|
|
1502
|
+
var RULES = {
|
|
1503
|
+
constant: [AKAN_BASE, AKAN_BASE_TYPES, { names: ["via"], specifier: "akanjs/constant", kind: "named" }],
|
|
1504
|
+
document: [
|
|
1505
|
+
AKAN_BASE,
|
|
1506
|
+
AKAN_BASE_TYPES,
|
|
1507
|
+
CNST_NS,
|
|
1508
|
+
DB_NS,
|
|
1509
|
+
ERR_DICT,
|
|
1510
|
+
{
|
|
1511
|
+
names: ["by", "from", "into", "SchemaOf", "DataInputOf", "DocumentUpdateHelper", "documentQueryHelper", "Mdl"],
|
|
1512
|
+
specifier: "akanjs/document",
|
|
1513
|
+
kind: "named"
|
|
1514
|
+
}
|
|
1515
|
+
],
|
|
1516
|
+
service: [
|
|
1517
|
+
AKAN_BASE,
|
|
1518
|
+
AKAN_BASE_TYPES,
|
|
1519
|
+
DB_NS,
|
|
1520
|
+
SRV_NS,
|
|
1521
|
+
CNST_NS,
|
|
1522
|
+
ERR_DICT,
|
|
1523
|
+
{ names: ["serve"], specifier: "akanjs/service", kind: "named" },
|
|
1524
|
+
{
|
|
1525
|
+
names: ["DataInputOf", "ListQueryOption", "DatabaseRegistry", "getFilterInfoByKey"],
|
|
1526
|
+
specifier: "akanjs/document",
|
|
1527
|
+
kind: "named"
|
|
1528
|
+
}
|
|
1529
|
+
],
|
|
1530
|
+
signal: [
|
|
1531
|
+
AKAN_BASE,
|
|
1532
|
+
AKAN_BASE_TYPES,
|
|
1533
|
+
SRV_NS,
|
|
1534
|
+
CNST_NS,
|
|
1535
|
+
ERR_DICT,
|
|
1536
|
+
{
|
|
1537
|
+
names: ["endpoint", "internal", "slice", "Public", "Req", "Ws", "None", "Res"],
|
|
1538
|
+
specifier: "akanjs/signal",
|
|
1539
|
+
kind: "named"
|
|
1540
|
+
}
|
|
1541
|
+
],
|
|
1542
|
+
dictionary: [
|
|
1543
|
+
{
|
|
1544
|
+
names: ["modelDictionary", "scalarDictionary", "serviceDictionary"],
|
|
1545
|
+
specifier: "akanjs/dictionary",
|
|
1546
|
+
kind: "named"
|
|
1547
|
+
}
|
|
1548
|
+
],
|
|
1549
|
+
srvkit: [
|
|
1550
|
+
AKAN_BASE,
|
|
1551
|
+
AKAN_BASE_TYPES,
|
|
1552
|
+
{ names: ["Logger", "HttpClient", "sleep"], specifier: "akanjs/common", kind: "named" },
|
|
1553
|
+
{ names: ["adapt"], specifier: "akanjs/service", kind: "named" },
|
|
1554
|
+
{ names: ["SignalContext", "Guard", "InternalArg", "Middleware"], specifier: "akanjs/signal", kind: "named" }
|
|
1555
|
+
],
|
|
1556
|
+
common: [AKAN_BASE, AKAN_BASE_TYPES],
|
|
1557
|
+
store: [
|
|
1558
|
+
CNST_NS,
|
|
1559
|
+
{ names: ["store"], specifier: "akanjs/store", kind: "named" },
|
|
1560
|
+
{ names: ["fetch", "usePage", "sig"], specifier: "../useClient", kind: "named" },
|
|
1561
|
+
{ names: ["st"], specifier: "../st", kind: "named" },
|
|
1562
|
+
{ names: ["RootStore"], specifier: "../st", kind: "named", typeOnly: true }
|
|
1563
|
+
],
|
|
1564
|
+
client: [
|
|
1565
|
+
{
|
|
1566
|
+
names: ["cnst", "fetch", "st", "usePage"],
|
|
1567
|
+
specifier: (ctx) => `@${ctx.scope}/${ctx.project}/client`,
|
|
1568
|
+
kind: "named"
|
|
1569
|
+
}
|
|
1570
|
+
]
|
|
1571
|
+
};
|
|
1572
|
+
var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
1573
|
+
var DOMAIN_ROLE_KINDS = {
|
|
1574
|
+
constant: ["constant"],
|
|
1575
|
+
dictionary: ["constant", "document", "signal"],
|
|
1576
|
+
common: ["constant"]
|
|
1577
|
+
};
|
|
1578
|
+
var LIB_BARRELS = {
|
|
1579
|
+
Err: { barrel: "dict", kind: "named" },
|
|
1580
|
+
db: { barrel: "db", kind: "namespace" },
|
|
1581
|
+
cnst: { barrel: "cnst", kind: "namespace" },
|
|
1582
|
+
srv: { barrel: "srv", kind: "namespace" }
|
|
1583
|
+
};
|
|
1584
|
+
var GLOBAL_NAMES = new Set(["fetch"]);
|
|
1585
|
+
var DOMAIN_DENYLIST = new Set([
|
|
1586
|
+
"Map",
|
|
1587
|
+
"Set",
|
|
1588
|
+
"WeakMap",
|
|
1589
|
+
"WeakSet",
|
|
1590
|
+
"Date",
|
|
1591
|
+
"Promise",
|
|
1592
|
+
"Array",
|
|
1593
|
+
"Object",
|
|
1594
|
+
"Error",
|
|
1595
|
+
"TypeError",
|
|
1596
|
+
"RangeError",
|
|
1597
|
+
"RegExp",
|
|
1598
|
+
"Symbol",
|
|
1599
|
+
"Proxy",
|
|
1600
|
+
"Reflect",
|
|
1601
|
+
"JSON",
|
|
1602
|
+
"Math",
|
|
1603
|
+
"Number",
|
|
1604
|
+
"BigInt",
|
|
1605
|
+
"Function",
|
|
1606
|
+
"Boolean",
|
|
1607
|
+
"String",
|
|
1608
|
+
"Record",
|
|
1609
|
+
"Partial",
|
|
1610
|
+
"Required",
|
|
1611
|
+
"Readonly",
|
|
1612
|
+
"Pick",
|
|
1613
|
+
"Omit",
|
|
1614
|
+
"Exclude",
|
|
1615
|
+
"Extract",
|
|
1616
|
+
"NonNullable",
|
|
1617
|
+
"ReturnType",
|
|
1618
|
+
"Parameters",
|
|
1619
|
+
"Awaited",
|
|
1620
|
+
"InstanceType"
|
|
1621
|
+
]);
|
|
1622
|
+
|
|
1623
|
+
class AutoImportSync {
|
|
1624
|
+
#workspaceRoot;
|
|
1625
|
+
#domainCache = new Map;
|
|
1626
|
+
constructor({ workspaceRoot }) {
|
|
1627
|
+
this.#workspaceRoot = path9.resolve(workspaceRoot);
|
|
1628
|
+
}
|
|
1629
|
+
async syncForBatch(files) {
|
|
1630
|
+
const changedFiles = [];
|
|
1631
|
+
const errors = [];
|
|
1632
|
+
const seen = new Set;
|
|
1633
|
+
for (const file of files) {
|
|
1634
|
+
const pkgRoot = this.#domainPkgRootOf(file);
|
|
1635
|
+
if (pkgRoot)
|
|
1636
|
+
this.#domainCache.delete(pkgRoot);
|
|
1637
|
+
}
|
|
1638
|
+
for (const file of files) {
|
|
1639
|
+
const abs = path9.resolve(file);
|
|
1640
|
+
if (seen.has(abs))
|
|
1641
|
+
continue;
|
|
1642
|
+
seen.add(abs);
|
|
1643
|
+
const ctx = this.#contextFor(abs);
|
|
1644
|
+
if (!ctx)
|
|
1645
|
+
continue;
|
|
1646
|
+
try {
|
|
1647
|
+
const changed = await this.#syncFile(abs, ctx);
|
|
1648
|
+
if (changed)
|
|
1649
|
+
changedFiles.push(abs);
|
|
1650
|
+
} catch (err) {
|
|
1651
|
+
errors.push(`[auto-import] sync failed for ${file}: ${formatError(err)}`);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return { changedFiles, errors };
|
|
1655
|
+
}
|
|
1656
|
+
#contextFor(abs) {
|
|
1657
|
+
const rel = path9.relative(this.#workspaceRoot, abs);
|
|
1658
|
+
if (rel.startsWith("..") || path9.isAbsolute(rel))
|
|
1659
|
+
return null;
|
|
1660
|
+
const base = path9.basename(abs);
|
|
1661
|
+
if (TEST_FILE_RE.test(base) || base === "index.ts" || base === "index.tsx" || base.endsWith(".d.ts"))
|
|
1662
|
+
return null;
|
|
1663
|
+
const parts = rel.split(path9.sep).filter(Boolean);
|
|
1664
|
+
const [scope, project, facet] = parts;
|
|
1665
|
+
if (scope !== "apps" && scope !== "libs" || !project || !facet)
|
|
1666
|
+
return null;
|
|
1667
|
+
if (facet === "lib" && parts[3] === "__lib")
|
|
1668
|
+
return null;
|
|
1669
|
+
const role = roleFor(facet, base);
|
|
1670
|
+
if (!role)
|
|
1671
|
+
return null;
|
|
1672
|
+
return { role, scope, project };
|
|
1673
|
+
}
|
|
1674
|
+
async#syncFile(abs, ctx) {
|
|
1675
|
+
const stats = await stat(abs).catch(() => null);
|
|
1676
|
+
if (!stats?.isFile())
|
|
1677
|
+
return false;
|
|
1678
|
+
const source = await readFile(abs, "utf8");
|
|
1679
|
+
const resolveExtra = await this.#extraResolverFor(abs, ctx);
|
|
1680
|
+
const next = transformSource(source, abs, ctx, resolveExtra);
|
|
1681
|
+
if (next === null || next === source)
|
|
1682
|
+
return false;
|
|
1683
|
+
await FileSys2.writeTextAtomic(abs, next);
|
|
1684
|
+
return true;
|
|
1685
|
+
}
|
|
1686
|
+
async#extraResolverFor(abs, ctx) {
|
|
1687
|
+
const resolvers = [];
|
|
1688
|
+
if (ctx.role === "srvkit" || ctx.role === "common")
|
|
1689
|
+
resolvers.push(await this.#libBarrelResolver(abs, ctx));
|
|
1690
|
+
const kinds = DOMAIN_ROLE_KINDS[ctx.role];
|
|
1691
|
+
if (kinds)
|
|
1692
|
+
resolvers.push(await this.#domainResolver(abs, ctx, kinds));
|
|
1693
|
+
if (resolvers.length === 0)
|
|
1694
|
+
return;
|
|
1695
|
+
return (symbol) => {
|
|
1696
|
+
for (const resolve of resolvers) {
|
|
1697
|
+
const target = resolve(symbol);
|
|
1698
|
+
if (target)
|
|
1699
|
+
return target;
|
|
1700
|
+
}
|
|
1701
|
+
return null;
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
async#domainResolver(abs, ctx, kinds) {
|
|
1705
|
+
const index = await this.#domainIndex(path9.join(this.#workspaceRoot, ctx.scope, ctx.project));
|
|
1706
|
+
const fileDir = path9.dirname(abs);
|
|
1707
|
+
return (symbol) => {
|
|
1708
|
+
if (!PASCAL_CASE_RE.test(symbol) || DOMAIN_DENYLIST.has(symbol))
|
|
1709
|
+
return null;
|
|
1710
|
+
const entries = (index.get(symbol) ?? []).filter((entry) => kinds.includes(entry.kind));
|
|
1711
|
+
if (entries.length !== 1)
|
|
1712
|
+
return null;
|
|
1713
|
+
return { specifier: relativeSpecifier(fileDir, entries[0].file), kind: "named" };
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
async#libBarrelResolver(abs, ctx) {
|
|
1717
|
+
const fileDir = path9.dirname(abs);
|
|
1718
|
+
const libDir = path9.join(this.#workspaceRoot, ctx.scope, ctx.project, "lib");
|
|
1719
|
+
const available = new Map;
|
|
1720
|
+
for (const [symbol, { barrel, kind }] of Object.entries(LIB_BARRELS)) {
|
|
1721
|
+
if (await fileExists(path9.join(libDir, `${barrel}.ts`)))
|
|
1722
|
+
available.set(symbol, { specifier: relativeSpecifier(fileDir, path9.join(libDir, barrel)), kind });
|
|
1723
|
+
}
|
|
1724
|
+
return (symbol) => available.get(symbol) ?? null;
|
|
1725
|
+
}
|
|
1726
|
+
async#domainIndex(pkgRoot) {
|
|
1727
|
+
const cached = this.#domainCache.get(pkgRoot);
|
|
1728
|
+
if (cached)
|
|
1729
|
+
return cached;
|
|
1730
|
+
const index = await buildDomainIndex(path9.join(pkgRoot, "lib"));
|
|
1731
|
+
this.#domainCache.set(pkgRoot, index);
|
|
1732
|
+
return index;
|
|
1733
|
+
}
|
|
1734
|
+
#domainPkgRootOf(file) {
|
|
1735
|
+
const rel = path9.relative(this.#workspaceRoot, path9.resolve(file));
|
|
1736
|
+
if (rel.startsWith("..") || path9.isAbsolute(rel))
|
|
1737
|
+
return null;
|
|
1738
|
+
const [scope, project, facet] = rel.split(path9.sep).filter(Boolean);
|
|
1739
|
+
if (scope !== "apps" && scope !== "libs" || !project || facet !== "lib")
|
|
1740
|
+
return null;
|
|
1741
|
+
if (!domainKindOf(path9.basename(file)))
|
|
1742
|
+
return null;
|
|
1743
|
+
return path9.join(this.#workspaceRoot, scope, project);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
var roleFor = (facet, base) => {
|
|
1747
|
+
if (facet === "lib") {
|
|
1748
|
+
if (base.endsWith(".constant.ts"))
|
|
1749
|
+
return "constant";
|
|
1750
|
+
if (base.endsWith(".document.ts"))
|
|
1751
|
+
return "document";
|
|
1752
|
+
if (base.endsWith(".service.ts"))
|
|
1753
|
+
return "service";
|
|
1754
|
+
if (base.endsWith(".signal.ts"))
|
|
1755
|
+
return "signal";
|
|
1756
|
+
if (base.endsWith(".dictionary.ts"))
|
|
1757
|
+
return "dictionary";
|
|
1758
|
+
if (base.endsWith(".store.ts"))
|
|
1759
|
+
return "store";
|
|
1760
|
+
if (base.endsWith(".tsx"))
|
|
1761
|
+
return "client";
|
|
1762
|
+
return null;
|
|
1763
|
+
}
|
|
1764
|
+
if (facet === "ui" || facet === "page")
|
|
1765
|
+
return base.endsWith(".tsx") ? "client" : null;
|
|
1766
|
+
if (facet === "webkit")
|
|
1767
|
+
return base.endsWith(".ts") || base.endsWith(".tsx") ? "client" : null;
|
|
1768
|
+
if (facet === "srvkit")
|
|
1769
|
+
return base.endsWith(".ts") ? "srvkit" : null;
|
|
1770
|
+
if (facet === "common")
|
|
1771
|
+
return base.endsWith(".ts") || base.endsWith(".tsx") ? "common" : null;
|
|
1772
|
+
return null;
|
|
1773
|
+
};
|
|
1774
|
+
var targetFor = (symbol, ctx) => {
|
|
1775
|
+
for (const rule of RULES[ctx.role]) {
|
|
1776
|
+
if (!rule.names.includes(symbol))
|
|
1777
|
+
continue;
|
|
1778
|
+
const specifier = typeof rule.specifier === "function" ? rule.specifier(ctx) : rule.specifier;
|
|
1779
|
+
return { specifier, kind: rule.kind, typeOnly: rule.typeOnly };
|
|
1780
|
+
}
|
|
1781
|
+
return null;
|
|
1782
|
+
};
|
|
1783
|
+
var transformSource = (source, fileName, ctx, resolveExtra) => {
|
|
1784
|
+
const sf = ts3.createSourceFile(fileName, source, ts3.ScriptTarget.Latest, true, scriptKindFor(fileName));
|
|
1785
|
+
const bound = collectBoundNames(sf);
|
|
1786
|
+
const used = collectUsedReferences(sf);
|
|
1787
|
+
const bareGlobals = collectBareGlobalReferences(sf);
|
|
1788
|
+
const namedBySpecifier = new Map;
|
|
1789
|
+
const namespaceImports = [];
|
|
1790
|
+
for (const name of used) {
|
|
1791
|
+
if (bound.has(name) || bareGlobals.has(name))
|
|
1792
|
+
continue;
|
|
1793
|
+
const target = targetFor(name, ctx) ?? resolveExtra?.(name) ?? null;
|
|
1794
|
+
if (!target)
|
|
1795
|
+
continue;
|
|
1796
|
+
if (target.kind === "namespace")
|
|
1797
|
+
namespaceImports.push({ name, specifier: target.specifier });
|
|
1798
|
+
else {
|
|
1799
|
+
const names = namedBySpecifier.get(target.specifier) ?? new Map;
|
|
1800
|
+
names.set(name, target.typeOnly ?? false);
|
|
1801
|
+
namedBySpecifier.set(target.specifier, names);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
if (namedBySpecifier.size === 0 && namespaceImports.length === 0)
|
|
1805
|
+
return null;
|
|
1806
|
+
const importDecls = sf.statements.filter(ts3.isImportDeclaration);
|
|
1807
|
+
const anchor = importDecls.at(-1) ?? null;
|
|
1808
|
+
const namedImportsBySpecifier = collectExistingNamedImports(importDecls);
|
|
1809
|
+
const edits = [];
|
|
1810
|
+
const newStatements = [];
|
|
1811
|
+
for (const [specifier, names] of namedBySpecifier) {
|
|
1812
|
+
const existing = namedImportsBySpecifier.get(specifier);
|
|
1813
|
+
if (existing) {
|
|
1814
|
+
const merged = new Map(existing.names);
|
|
1815
|
+
for (const [name, isType] of names)
|
|
1816
|
+
if (!merged.has(name))
|
|
1817
|
+
merged.set(name, isType);
|
|
1818
|
+
edits.push({
|
|
1819
|
+
start: existing.decl.getStart(sf),
|
|
1820
|
+
end: existing.decl.getEnd(),
|
|
1821
|
+
text: formatNamedImport(specifier, merged)
|
|
1822
|
+
});
|
|
1823
|
+
} else
|
|
1824
|
+
newStatements.push(formatNamedImport(specifier, names));
|
|
1825
|
+
}
|
|
1826
|
+
for (const ns of namespaceImports)
|
|
1827
|
+
newStatements.push(`import * as ${ns.name} from "${ns.specifier}";`);
|
|
1828
|
+
if (newStatements.length > 0) {
|
|
1829
|
+
const anchorEdit = anchor ? edits.find((edit) => edit.start === anchor.getStart(sf)) : undefined;
|
|
1830
|
+
if (anchorEdit)
|
|
1831
|
+
anchorEdit.text = `${anchorEdit.text}
|
|
1832
|
+
${newStatements.join(`
|
|
1833
|
+
`)}`;
|
|
1834
|
+
else if (anchor)
|
|
1835
|
+
edits.push({ start: anchor.getEnd(), end: anchor.getEnd(), text: `
|
|
1836
|
+
${newStatements.join(`
|
|
1837
|
+
`)}` });
|
|
1838
|
+
else {
|
|
1839
|
+
const prologueEnd = directivePrologueEnd(sf);
|
|
1840
|
+
if (prologueEnd >= 0)
|
|
1841
|
+
edits.push({ start: prologueEnd, end: prologueEnd, text: `
|
|
1842
|
+
${newStatements.join(`
|
|
1843
|
+
`)}` });
|
|
1844
|
+
else
|
|
1845
|
+
edits.push({ start: 0, end: 0, text: `${newStatements.join(`
|
|
1846
|
+
`)}
|
|
1847
|
+
|
|
1848
|
+
` });
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
if (edits.length === 0)
|
|
1852
|
+
return null;
|
|
1853
|
+
edits.sort((a, b) => b.start - a.start);
|
|
1854
|
+
let out = source;
|
|
1855
|
+
for (const edit of edits)
|
|
1856
|
+
out = out.slice(0, edit.start) + edit.text + out.slice(edit.end);
|
|
1857
|
+
return out === source ? null : out;
|
|
1858
|
+
};
|
|
1859
|
+
var collectBoundNames = (sf) => {
|
|
1860
|
+
const names = new Set;
|
|
1861
|
+
const visit = (node) => {
|
|
1862
|
+
if (ts3.isImportClause(node)) {
|
|
1863
|
+
if (node.name)
|
|
1864
|
+
names.add(node.name.text);
|
|
1865
|
+
const bindings = node.namedBindings;
|
|
1866
|
+
if (bindings && ts3.isNamespaceImport(bindings))
|
|
1867
|
+
names.add(bindings.name.text);
|
|
1868
|
+
if (bindings && ts3.isNamedImports(bindings))
|
|
1869
|
+
for (const el of bindings.elements)
|
|
1870
|
+
names.add(el.name.text);
|
|
1871
|
+
}
|
|
1872
|
+
if ((ts3.isVariableDeclaration(node) || ts3.isFunctionDeclaration(node) || ts3.isClassDeclaration(node) || ts3.isParameter(node) || ts3.isBindingElement(node) || ts3.isEnumDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isInterfaceDeclaration(node)) && node.name && ts3.isIdentifier(node.name))
|
|
1873
|
+
names.add(node.name.text);
|
|
1874
|
+
ts3.forEachChild(node, visit);
|
|
1875
|
+
};
|
|
1876
|
+
ts3.forEachChild(sf, visit);
|
|
1877
|
+
return names;
|
|
1878
|
+
};
|
|
1879
|
+
var collectUsedReferences = (sf) => {
|
|
1880
|
+
const used = new Set;
|
|
1881
|
+
const visit = (node) => {
|
|
1882
|
+
if (ts3.isIdentifier(node) && isReferencePosition(node))
|
|
1883
|
+
used.add(node.text);
|
|
1884
|
+
ts3.forEachChild(node, visit);
|
|
1885
|
+
};
|
|
1886
|
+
ts3.forEachChild(sf, visit);
|
|
1887
|
+
return used;
|
|
1888
|
+
};
|
|
1889
|
+
var collectBareGlobalReferences = (sf) => {
|
|
1890
|
+
const bare = new Set;
|
|
1891
|
+
const visit = (node) => {
|
|
1892
|
+
if (ts3.isIdentifier(node) && GLOBAL_NAMES.has(node.text) && isReferencePosition(node)) {
|
|
1893
|
+
const parent = node.parent;
|
|
1894
|
+
if (!(parent && ts3.isPropertyAccessExpression(parent) && parent.expression === node))
|
|
1895
|
+
bare.add(node.text);
|
|
1896
|
+
}
|
|
1897
|
+
ts3.forEachChild(node, visit);
|
|
1898
|
+
};
|
|
1899
|
+
ts3.forEachChild(sf, visit);
|
|
1900
|
+
return bare;
|
|
1901
|
+
};
|
|
1902
|
+
var isReferencePosition = (node) => {
|
|
1903
|
+
const parent = node.parent;
|
|
1904
|
+
if (!parent)
|
|
1905
|
+
return true;
|
|
1906
|
+
if (ts3.isPropertyAccessExpression(parent) && parent.name === node)
|
|
1907
|
+
return false;
|
|
1908
|
+
if (ts3.isQualifiedName(parent) && parent.right === node)
|
|
1909
|
+
return false;
|
|
1910
|
+
if (ts3.isPropertyAssignment(parent) && parent.name === node)
|
|
1911
|
+
return false;
|
|
1912
|
+
if (ts3.isPropertySignature(parent) && parent.name === node)
|
|
1913
|
+
return false;
|
|
1914
|
+
if (ts3.isBindingElement(parent) && parent.propertyName === node)
|
|
1915
|
+
return false;
|
|
1916
|
+
if (ts3.isImportSpecifier(parent) || ts3.isExportSpecifier(parent))
|
|
1917
|
+
return false;
|
|
1918
|
+
return true;
|
|
1919
|
+
};
|
|
1920
|
+
var collectExistingNamedImports = (importDecls) => {
|
|
1921
|
+
const map = new Map;
|
|
1922
|
+
for (const decl of importDecls) {
|
|
1923
|
+
const clause = decl.importClause;
|
|
1924
|
+
const bindings = clause?.namedBindings;
|
|
1925
|
+
if (!clause || !bindings || !ts3.isNamedImports(bindings))
|
|
1926
|
+
continue;
|
|
1927
|
+
if (!ts3.isStringLiteral(decl.moduleSpecifier))
|
|
1928
|
+
continue;
|
|
1929
|
+
const specifier = decl.moduleSpecifier.text;
|
|
1930
|
+
if (map.has(specifier))
|
|
1931
|
+
continue;
|
|
1932
|
+
const names = new Map;
|
|
1933
|
+
for (const el of bindings.elements)
|
|
1934
|
+
names.set(el.name.text, clause.isTypeOnly || el.isTypeOnly);
|
|
1935
|
+
map.set(specifier, { decl, names });
|
|
1936
|
+
}
|
|
1937
|
+
return map;
|
|
1938
|
+
};
|
|
1939
|
+
var formatNamedImport = (specifier, names) => {
|
|
1940
|
+
const entries = [...names.entries()].sort((a, b) => compareNames(a[0], b[0]));
|
|
1941
|
+
if (entries.every(([, isType]) => isType))
|
|
1942
|
+
return `import type { ${entries.map(([name]) => name).join(", ")} } from "${specifier}";`;
|
|
1943
|
+
const parts = entries.map(([name, isType]) => isType ? `type ${name}` : name);
|
|
1944
|
+
return `import { ${parts.join(", ")} } from "${specifier}";`;
|
|
1945
|
+
};
|
|
1946
|
+
var directivePrologueEnd = (sf) => {
|
|
1947
|
+
let end = -1;
|
|
1948
|
+
for (const stmt of sf.statements) {
|
|
1949
|
+
if (ts3.isExpressionStatement(stmt) && ts3.isStringLiteral(stmt.expression))
|
|
1950
|
+
end = stmt.getEnd();
|
|
1951
|
+
else
|
|
1952
|
+
break;
|
|
1953
|
+
}
|
|
1954
|
+
return end;
|
|
1955
|
+
};
|
|
1956
|
+
var scriptKindFor = (fileName) => fileName.endsWith(".tsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS;
|
|
1957
|
+
var compareNames = (a, b) => {
|
|
1958
|
+
const [la, lb] = [a.toLowerCase(), b.toLowerCase()];
|
|
1959
|
+
if (la !== lb)
|
|
1960
|
+
return la < lb ? -1 : 1;
|
|
1961
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1962
|
+
};
|
|
1963
|
+
var formatError = (err) => err instanceof Error ? err.message : String(err);
|
|
1964
|
+
var fileExists = async (file) => stat(file).then((s) => s.isFile()).catch(() => false);
|
|
1965
|
+
var domainKindOf = (base) => {
|
|
1966
|
+
if (base.endsWith(".constant.ts"))
|
|
1967
|
+
return "constant";
|
|
1968
|
+
if (base.endsWith(".document.ts"))
|
|
1969
|
+
return "document";
|
|
1970
|
+
if (base.endsWith(".signal.ts"))
|
|
1971
|
+
return "signal";
|
|
1972
|
+
return null;
|
|
1973
|
+
};
|
|
1974
|
+
var buildDomainIndex = async (libDir) => {
|
|
1975
|
+
const index = new Map;
|
|
1976
|
+
for (const file of await collectDomainFiles(libDir)) {
|
|
1977
|
+
const kind = domainKindOf(path9.basename(file));
|
|
1978
|
+
if (!kind)
|
|
1979
|
+
continue;
|
|
1980
|
+
const src = await readFile(file, "utf8").catch(() => null);
|
|
1981
|
+
if (src === null)
|
|
1982
|
+
continue;
|
|
1983
|
+
for (const name of exportedPascalNames(src, file)) {
|
|
1984
|
+
const entries = index.get(name) ?? [];
|
|
1985
|
+
entries.push({ file, kind });
|
|
1986
|
+
index.set(name, entries);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
return index;
|
|
1990
|
+
};
|
|
1991
|
+
var collectDomainFiles = async (dir) => {
|
|
1992
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1993
|
+
const files = [];
|
|
1994
|
+
for (const entry of entries) {
|
|
1995
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
1996
|
+
continue;
|
|
1997
|
+
const full = path9.join(dir, entry.name);
|
|
1998
|
+
if (entry.isDirectory())
|
|
1999
|
+
files.push(...await collectDomainFiles(full));
|
|
2000
|
+
else if (domainKindOf(entry.name) && !TEST_FILE_RE.test(entry.name))
|
|
2001
|
+
files.push(full);
|
|
2002
|
+
}
|
|
2003
|
+
return files;
|
|
2004
|
+
};
|
|
2005
|
+
var exportedPascalNames = (source, fileName) => {
|
|
2006
|
+
const sf = ts3.createSourceFile(fileName, source, ts3.ScriptTarget.Latest, true);
|
|
2007
|
+
const isExported = (node) => ts3.canHaveModifiers(node) && (ts3.getModifiers(node) ?? []).some((m) => m.kind === ts3.SyntaxKind.ExportKeyword);
|
|
2008
|
+
const names = [];
|
|
2009
|
+
for (const stmt of sf.statements) {
|
|
2010
|
+
if ((ts3.isClassDeclaration(stmt) || ts3.isFunctionDeclaration(stmt) || ts3.isEnumDeclaration(stmt)) && stmt.name) {
|
|
2011
|
+
if (isExported(stmt))
|
|
2012
|
+
names.push(stmt.name.text);
|
|
2013
|
+
} else if (ts3.isVariableStatement(stmt) && isExported(stmt)) {
|
|
2014
|
+
for (const decl of stmt.declarationList.declarations)
|
|
2015
|
+
if (ts3.isIdentifier(decl.name))
|
|
2016
|
+
names.push(decl.name.text);
|
|
2017
|
+
} else if (ts3.isExportDeclaration(stmt) && stmt.exportClause && ts3.isNamedExports(stmt.exportClause)) {
|
|
2018
|
+
for (const el of stmt.exportClause.elements)
|
|
2019
|
+
names.push(el.name.text);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return names.filter((name) => PASCAL_CASE_RE.test(name));
|
|
2023
|
+
};
|
|
2024
|
+
var relativeSpecifier = (fromDir, toFile) => {
|
|
2025
|
+
const rel = path9.relative(fromDir, toFile).replace(/\.tsx?$/, "").split(path9.sep).join("/");
|
|
2026
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
2027
|
+
};
|
|
2028
|
+
|
|
2029
|
+
// pkgs/@akanjs/devkit/frontendBuild/serverGraphFile.ts
|
|
2030
|
+
import fs4 from "fs";
|
|
2031
|
+
import path10 from "path";
|
|
2032
|
+
class ServerGraphFile {
|
|
2033
|
+
static fileName = "server-graph.json";
|
|
2034
|
+
static #cache = null;
|
|
2035
|
+
static async read(artifactDir) {
|
|
2036
|
+
const file = path10.join(artifactDir, ServerGraphFile.fileName);
|
|
2037
|
+
const stat = fs4.statSync(file, { throwIfNoEntry: false });
|
|
2038
|
+
if (!stat)
|
|
2039
|
+
return null;
|
|
2040
|
+
const stamp = `${stat.ino}:${stat.size}:${stat.mtimeMs}`;
|
|
2041
|
+
const cached = ServerGraphFile.#cache;
|
|
2042
|
+
if (cached?.file === file && cached.stamp === stamp)
|
|
2043
|
+
return cached.graph;
|
|
2044
|
+
const graph = await Bun.file(file).json().catch(() => null);
|
|
2045
|
+
if (graph)
|
|
2046
|
+
ServerGraphFile.#cache = { file, stamp, graph };
|
|
2047
|
+
return graph;
|
|
2048
|
+
}
|
|
2049
|
+
static async write(artifactDir, graph) {
|
|
2050
|
+
const file = path10.join(artifactDir, ServerGraphFile.fileName);
|
|
2051
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
2052
|
+
await Bun.write(temp, JSON.stringify(graph));
|
|
2053
|
+
await FileSys2.replace(temp, file);
|
|
2054
|
+
}
|
|
2055
|
+
static async carry(artifactDir, files) {
|
|
2056
|
+
const graph = await ServerGraphFile.read(artifactDir);
|
|
2057
|
+
if (!graph || files.length === 0)
|
|
2058
|
+
return;
|
|
2059
|
+
const carried = [...new Set([...graph.carried ?? [], ...files.map((file) => CsrDevPaths.realpath(file))])];
|
|
2060
|
+
await ServerGraphFile.write(artifactDir, { ...graph, carried });
|
|
2061
|
+
}
|
|
2062
|
+
static async touches(graph, files, exportsNow, next) {
|
|
2063
|
+
if (!graph)
|
|
2064
|
+
return true;
|
|
2065
|
+
const inputs = new Set(graph.inputs);
|
|
2066
|
+
const joined = new Set((next?.inputs ?? []).filter((input) => !inputs.has(input)));
|
|
2067
|
+
for (const rawFile of files) {
|
|
2068
|
+
const file = CsrDevPaths.realpath(rawFile);
|
|
2069
|
+
if (joined.has(file))
|
|
2070
|
+
return true;
|
|
2071
|
+
if (!inputs.has(file))
|
|
2072
|
+
continue;
|
|
2073
|
+
const before = graph.clientExports[file];
|
|
2074
|
+
if (!before)
|
|
2075
|
+
return true;
|
|
2076
|
+
const now = await exportsNow(file);
|
|
2077
|
+
if (!now || ServerGraphFile.#names(now) !== ServerGraphFile.#names(before))
|
|
2078
|
+
return true;
|
|
2079
|
+
}
|
|
2080
|
+
return false;
|
|
2081
|
+
}
|
|
2082
|
+
static async clientExportsOf(file) {
|
|
2083
|
+
const source = await Bun.file(file).text().catch(() => null);
|
|
2084
|
+
if (source === null || !hasUseClientDirective(source))
|
|
2085
|
+
return null;
|
|
2086
|
+
try {
|
|
2087
|
+
return scanUseClientExports(source, file);
|
|
2088
|
+
} catch {
|
|
2089
|
+
return null;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
static #names(exports) {
|
|
2093
|
+
return [...exports].sort().join(",");
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
2098
|
+
import fs5 from "fs";
|
|
2099
|
+
import path13 from "path";
|
|
2100
|
+
|
|
2101
|
+
// pkgs/@akanjs/devkit/transforms/externalizeFrameworkPlugin.ts
|
|
2102
|
+
import path11 from "path";
|
|
2103
|
+
var DEFAULT_INCLUDE = ["akanjs/", "@apps/", "@libs/"];
|
|
2104
|
+
var DEFAULT_EXCLUDE_EXACT = new Set(["akanjs/webkit", "@akanjs/cli", "@akanjs/devkit"]);
|
|
2105
|
+
var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
|
|
2106
|
+
var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
|
|
2107
|
+
"@libsql/client",
|
|
2108
|
+
"bullmq",
|
|
2109
|
+
"ioredis",
|
|
2110
|
+
"postgres",
|
|
2111
|
+
"protobufjs"
|
|
2112
|
+
]);
|
|
2113
|
+
var RUNTIME_EXTERNAL_EXACT = new Set([
|
|
2114
|
+
"react",
|
|
2115
|
+
"react-dom",
|
|
2116
|
+
"react/jsx-runtime",
|
|
2117
|
+
"react/jsx-dev-runtime",
|
|
2118
|
+
"react-server-dom-webpack",
|
|
2119
|
+
"react-server-dom-webpack/server.node",
|
|
2120
|
+
"react-server-dom-webpack/client.node",
|
|
2121
|
+
"react-server-dom-webpack/client.browser"
|
|
2122
|
+
]);
|
|
2123
|
+
var RUNTIME_EXTERNAL_PREFIX = ["react-dom/", "react-server-dom-webpack/"];
|
|
2124
|
+
var CANDIDATE_EXTS3 = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
|
|
2125
|
+
async function createExternalizeFrameworkPlugin(options) {
|
|
2126
|
+
const tsconfig = await options.app.getTsConfig();
|
|
2127
|
+
const includePrefixes = options.include ?? DEFAULT_INCLUDE;
|
|
2128
|
+
const extraExact = new Set(options.extra ?? []);
|
|
2129
|
+
const workspaceRoot = options.app.workspace.workspaceRoot;
|
|
2130
|
+
const tsconfigPaths = tsconfig.compilerOptions.paths ?? {};
|
|
2131
|
+
const rootEntries = Object.entries(tsconfigPaths).filter(([k]) => !k.endsWith("/*")).map(([k, v]) => ({ pkg: k, entryFile: v[0] ?? null })).filter((e) => e.entryFile !== null);
|
|
2132
|
+
const wildcardEntries = Object.entries(tsconfigPaths).filter(([k]) => k.endsWith("/*")).map(([k, v]) => ({ prefix: k.slice(0, -1), replacements: v })).sort((a, b) => b.prefix.length - a.prefix.length);
|
|
2133
|
+
async function resolveWorkspaceSubpath(spec) {
|
|
2134
|
+
if (!workspaceRoot)
|
|
2135
|
+
return null;
|
|
2136
|
+
for (const { prefix, replacements } of wildcardEntries) {
|
|
2137
|
+
if (!spec.startsWith(prefix))
|
|
2138
|
+
continue;
|
|
2139
|
+
const suffix = spec.slice(prefix.length);
|
|
2140
|
+
for (const repl of replacements) {
|
|
2141
|
+
const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
|
|
2142
|
+
if (!replPath)
|
|
2143
|
+
continue;
|
|
2144
|
+
const candidate = path11.resolve(workspaceRoot, replPath + suffix);
|
|
2145
|
+
const hit = await firstExisting(candidate);
|
|
2146
|
+
if (hit)
|
|
2147
|
+
return hit;
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
for (const { pkg, entryFile } of rootEntries) {
|
|
2151
|
+
if (!spec.startsWith(`${pkg}/`))
|
|
2152
|
+
continue;
|
|
2153
|
+
const suffix = spec.slice(pkg.length + 1);
|
|
2154
|
+
const pkgDir = path11.dirname(path11.resolve(workspaceRoot, entryFile));
|
|
2155
|
+
const candidate = path11.join(pkgDir, suffix);
|
|
2156
|
+
const hit = await firstExisting(candidate);
|
|
2157
|
+
if (hit)
|
|
2158
|
+
return hit;
|
|
2159
|
+
}
|
|
2160
|
+
return null;
|
|
2161
|
+
}
|
|
2162
|
+
return {
|
|
2163
|
+
name: "akan-externalize-framework",
|
|
2164
|
+
setup(build) {
|
|
2165
|
+
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
2166
|
+
const spec = args.path;
|
|
2167
|
+
if (spec === "." || spec === ".." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/"))
|
|
2168
|
+
return;
|
|
2169
|
+
if (extraExact.has(spec) || DEFAULT_EXCLUDE_EXACT.has(spec))
|
|
2170
|
+
return { path: spec, external: true };
|
|
2171
|
+
for (const prefix of DEFAULT_EXCLUDE_PREFIX) {
|
|
2172
|
+
if (spec.startsWith(prefix))
|
|
2173
|
+
return { path: spec, external: true };
|
|
2174
|
+
}
|
|
2175
|
+
if (OPTIONAL_BACKEND_EXTERNAL_EXACT.has(spec))
|
|
2176
|
+
return { path: spec, external: true };
|
|
2177
|
+
if (RUNTIME_EXTERNAL_EXACT.has(spec))
|
|
2178
|
+
return { path: spec, external: true };
|
|
2179
|
+
for (const prefix of RUNTIME_EXTERNAL_PREFIX) {
|
|
2180
|
+
if (spec.startsWith(prefix))
|
|
2181
|
+
return { path: spec, external: true };
|
|
2182
|
+
}
|
|
2183
|
+
if (!includePrefixes.some((prefix) => spec.startsWith(prefix)))
|
|
2184
|
+
return;
|
|
2185
|
+
const resolved = await resolveWorkspaceSubpath(spec);
|
|
2186
|
+
return resolved ? { path: resolved } : undefined;
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
async function firstExisting(basePath) {
|
|
2192
|
+
if (await Bun.file(basePath).exists())
|
|
2193
|
+
return basePath;
|
|
2194
|
+
for (const ext of CANDIDATE_EXTS3) {
|
|
2195
|
+
const candidate = `${basePath}${ext}`;
|
|
2196
|
+
if (await Bun.file(candidate).exists())
|
|
2197
|
+
return candidate;
|
|
2198
|
+
}
|
|
2199
|
+
for (const ext of CANDIDATE_EXTS3) {
|
|
2200
|
+
const candidate = path11.join(basePath, `index${ext}`);
|
|
2201
|
+
if (await Bun.file(candidate).exists())
|
|
2202
|
+
return candidate;
|
|
2203
|
+
}
|
|
2204
|
+
return null;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// pkgs/@akanjs/devkit/transforms/useClientBundlePlugin.ts
|
|
2208
|
+
function createUseClientBundlePlugin(options = {}) {
|
|
2209
|
+
return {
|
|
2210
|
+
name: "akan-use-client-bundle",
|
|
2211
|
+
setup(build) {
|
|
2212
|
+
build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
|
|
2213
|
+
if (args.path.includes("/node_modules/"))
|
|
2214
|
+
return;
|
|
2215
|
+
let source;
|
|
2216
|
+
try {
|
|
2217
|
+
source = await Bun.file(args.path).text();
|
|
2218
|
+
} catch {
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
const stubbed = transformUseClient(source, {
|
|
2222
|
+
path: args.path,
|
|
2223
|
+
workspaceRoot: options.workspaceRoot,
|
|
2224
|
+
onClientModule: options.onClientModule
|
|
2225
|
+
});
|
|
2226
|
+
if (stubbed === null)
|
|
2227
|
+
return;
|
|
2228
|
+
return { contents: stubbed, loader: loaderFor(args.path) };
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
|
|
2235
|
+
import path12 from "path";
|
|
2236
|
+
class PagesEntrySourceGenerator {
|
|
2237
|
+
#pageEntries;
|
|
2238
|
+
#asyncDefaults = new Map;
|
|
2239
|
+
#detect;
|
|
2240
|
+
constructor(pageEntries, { isAsyncDefault } = {}) {
|
|
2241
|
+
this.#pageEntries = pageEntries;
|
|
2242
|
+
this.#detect = isAsyncDefault ?? ((moduleAbsPath) => AsyncDefaultExportDetector.detect(moduleAbsPath));
|
|
2243
|
+
}
|
|
2244
|
+
static generate(pageEntries) {
|
|
2245
|
+
return new PagesEntrySourceGenerator(pageEntries).generate();
|
|
2246
|
+
}
|
|
2247
|
+
generate() {
|
|
2248
|
+
const lines = this.#pageEntries.map(({ key, moduleAbsPath }) => {
|
|
2249
|
+
const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
2250
|
+
return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(specifier)}),`;
|
|
2251
|
+
});
|
|
2252
|
+
return `export const pages = {
|
|
2253
|
+
${lines.join(`
|
|
2254
|
+
`)}
|
|
2255
|
+
};
|
|
2256
|
+
`;
|
|
2257
|
+
}
|
|
2258
|
+
static async generateStatic(pageEntries, { fromDir } = {}) {
|
|
2259
|
+
return await new PagesEntrySourceGenerator(pageEntries).generateStatic({ fromDir });
|
|
2260
|
+
}
|
|
2261
|
+
async generateStatic({ fromDir } = {}) {
|
|
2262
|
+
const imports = this.#pageEntries.map(({ moduleAbsPath }, index) => {
|
|
2263
|
+
const specifier = fromDir ? PagesEntrySourceGenerator.#toRelativeSpecifier(fromDir, moduleAbsPath) : PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
2264
|
+
return `import * as page${index} from ${JSON.stringify(specifier)};`;
|
|
2265
|
+
});
|
|
2266
|
+
const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }, index) => {
|
|
2267
|
+
const isAsyncDefault = await this.#isAsyncDefault(moduleAbsPath);
|
|
2268
|
+
return ` ${JSON.stringify(key)}: { loader: async () => page${index}, isAsyncDefault: ${isAsyncDefault} },`;
|
|
2269
|
+
}));
|
|
2270
|
+
return `${imports.join(`
|
|
2271
|
+
`)}
|
|
2272
|
+
export const pages = {
|
|
2273
|
+
${entries.join(`
|
|
2274
|
+
`)}
|
|
2275
|
+
};
|
|
2276
|
+
`;
|
|
2277
|
+
}
|
|
2278
|
+
async generateHotReplace({
|
|
2279
|
+
fromDir,
|
|
2280
|
+
ownerId,
|
|
2281
|
+
moduleIds
|
|
2282
|
+
}) {
|
|
2283
|
+
const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }) => {
|
|
2284
|
+
const specifier = PagesEntrySourceGenerator.#toRelativeSpecifier(fromDir, moduleAbsPath);
|
|
2285
|
+
const isAsyncDefault = await this.#isAsyncDefault(moduleAbsPath);
|
|
2286
|
+
return ` ${JSON.stringify(key)}: { loader: async () => require(${JSON.stringify(specifier)}), isAsyncDefault: ${isAsyncDefault} },`;
|
|
2287
|
+
}));
|
|
2288
|
+
return `__akan.accept(${JSON.stringify(ownerId)}, ${JSON.stringify(moduleIds)}, async () => {
|
|
2289
|
+
const replaced = await akanWebkit.replacePages({
|
|
2290
|
+
${entries.join(`
|
|
2291
|
+
`)}
|
|
2292
|
+
});
|
|
2293
|
+
if (!replaced) throw new Error("the route table no longer matches the pages it was built from");
|
|
2294
|
+
});
|
|
2295
|
+
`;
|
|
2296
|
+
}
|
|
2297
|
+
#isAsyncDefault(moduleAbsPath) {
|
|
2298
|
+
const known = this.#asyncDefaults.get(moduleAbsPath);
|
|
2299
|
+
if (known)
|
|
2300
|
+
return known;
|
|
2301
|
+
const detected = this.#detect(moduleAbsPath);
|
|
2302
|
+
this.#asyncDefaults.set(moduleAbsPath, detected);
|
|
2303
|
+
return detected;
|
|
2304
|
+
}
|
|
2305
|
+
static #toImportSpecifier(moduleAbsPath) {
|
|
2306
|
+
return path12.resolve(moduleAbsPath).split(path12.sep).join("/");
|
|
2307
|
+
}
|
|
2308
|
+
static #toRelativeSpecifier(fromDir, moduleAbsPath) {
|
|
2309
|
+
const relative = path12.relative(fromDir, path12.resolve(moduleAbsPath)).split(path12.sep).join("/");
|
|
2310
|
+
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
2315
|
+
var VIRTUAL_PAGES_ENTRY = "akan-pages-entry";
|
|
2316
|
+
|
|
2317
|
+
class PagesBundleBuilder {
|
|
2318
|
+
#app;
|
|
2319
|
+
#command;
|
|
2320
|
+
#pageEntries;
|
|
2321
|
+
#started = Date.now();
|
|
2322
|
+
constructor(app, command = "start", pageEntries) {
|
|
2323
|
+
this.#app = app;
|
|
2324
|
+
this.#command = command;
|
|
2325
|
+
this.#pageEntries = pageEntries;
|
|
2326
|
+
}
|
|
2327
|
+
async build() {
|
|
2328
|
+
const akanConfig = await this.#app.getConfig();
|
|
2329
|
+
const resolvedEntries = this.#pageEntries ?? await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
|
|
2330
|
+
const entrySource = PagesEntrySourceGenerator.generate(resolvedEntries);
|
|
2331
|
+
const workspaceRoot = this.#app.workspace.workspaceRoot;
|
|
2332
|
+
const dev = this.#command === "start";
|
|
2333
|
+
const clientExports = {};
|
|
2334
|
+
const onClientModule = dev ? (file, exports) => {
|
|
2335
|
+
clientExports[CsrDevPaths.realpath(file)] = exports;
|
|
2336
|
+
} : undefined;
|
|
2337
|
+
const result = await Bun.build({
|
|
2338
|
+
entrypoints: [VIRTUAL_PAGES_ENTRY],
|
|
2339
|
+
outdir: `${this.#artifactDir}/server`,
|
|
2340
|
+
target: "bun",
|
|
2341
|
+
format: "esm",
|
|
2342
|
+
splitting: this.#splitting,
|
|
2343
|
+
minify: this.#command === "build",
|
|
2344
|
+
naming: {
|
|
2345
|
+
entry: "pages-[hash].[ext]",
|
|
2346
|
+
chunk: "chunks/[name]-[hash].[ext]",
|
|
2347
|
+
asset: "assets/[name]-[hash].[ext]"
|
|
2348
|
+
},
|
|
2349
|
+
define: bundleDefine(this.#app, this.#command, "ssr"),
|
|
2350
|
+
metafile: dev,
|
|
2351
|
+
plugins: [
|
|
2352
|
+
PagesBundleBuilder.createPagesEntryPlugin(entrySource),
|
|
2353
|
+
PagesBundleBuilder.createCssStubPlugin(),
|
|
2354
|
+
PagesBundleBuilder.createServerUseClientFetchPlugin(),
|
|
2355
|
+
await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig.externalLibs }),
|
|
2356
|
+
akanConfig.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
|
|
2357
|
+
pipeAfter: (source, args) => transformUseClient(source, {
|
|
2358
|
+
path: args.path,
|
|
2359
|
+
workspaceRoot,
|
|
2360
|
+
onClientModule
|
|
2361
|
+
})
|
|
2362
|
+
}) : createUseClientBundlePlugin({ workspaceRoot, onClientModule })
|
|
2363
|
+
]
|
|
2364
|
+
});
|
|
2365
|
+
if (!result.success)
|
|
2366
|
+
throw new AggregateError(result.logs, "[PagesBundleBuilder] Bun.build failed");
|
|
2367
|
+
const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
|
|
2368
|
+
if (!entryArtifact)
|
|
2369
|
+
throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
|
|
2370
|
+
const bundlePath = path13.resolve(entryArtifact.path);
|
|
2371
|
+
const buildId = Date.now();
|
|
2372
|
+
if (dev && result.metafile)
|
|
2373
|
+
await ServerGraphFile.write(this.#artifactDir, this.#serverGraph(result.metafile, clientExports));
|
|
2374
|
+
const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
|
|
2375
|
+
const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
|
|
2376
|
+
this.#app.verbose(`[PagesBundleBuilder] ${path13.basename(bundlePath)} emitted in ${Date.now() - this.#started}ms splitting=${this.#splitting} entry=${entryArtifact.size} bytes outputs=${result.outputs.length} chunks=${chunkCount} total=${outputBytes} bytes`);
|
|
2377
|
+
return {
|
|
2378
|
+
bundlePath,
|
|
2379
|
+
buildId,
|
|
2380
|
+
splitting: this.#splitting,
|
|
2381
|
+
entryBytes: entryArtifact.size,
|
|
2382
|
+
outputBytes,
|
|
2383
|
+
outputCount: result.outputs.length,
|
|
2384
|
+
chunkCount
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
#serverGraph(metafile, clientExports) {
|
|
2388
|
+
const inputs = Object.keys(metafile.inputs).filter((input) => !input.includes("node_modules")).map((input) => path13.resolve(input)).filter((input) => fs5.existsSync(input)).map((input) => CsrDevPaths.realpath(input));
|
|
2389
|
+
return { inputs, clientExports };
|
|
2390
|
+
}
|
|
2391
|
+
get #artifactDir() {
|
|
2392
|
+
return `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
|
|
2393
|
+
}
|
|
2394
|
+
get #splitting() {
|
|
2395
|
+
return process.env.AKAN_SERVER_PAGES_SPLITTING === "1";
|
|
2396
|
+
}
|
|
2397
|
+
static createPagesEntryPlugin(source) {
|
|
2398
|
+
return {
|
|
2399
|
+
name: "akan-pages-entry",
|
|
2400
|
+
setup(build) {
|
|
2401
|
+
build.onResolve({ filter: /^akan-pages-entry$/ }, () => ({
|
|
2402
|
+
path: VIRTUAL_PAGES_ENTRY,
|
|
2403
|
+
namespace: "akan-virtual"
|
|
2404
|
+
}));
|
|
2405
|
+
build.onLoad({ filter: /^akan-pages-entry$/, namespace: "akan-virtual" }, () => ({
|
|
2406
|
+
contents: source,
|
|
2407
|
+
loader: "tsx"
|
|
2408
|
+
}));
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
static createCssStubPlugin() {
|
|
2413
|
+
return {
|
|
2414
|
+
name: "akan-css-stub",
|
|
2415
|
+
setup(build) {
|
|
2416
|
+
build.onLoad({ filter: /\.css$/ }, () => ({
|
|
2417
|
+
contents: "",
|
|
2418
|
+
loader: "js"
|
|
2419
|
+
}));
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
static createServerUseClientFetchPlugin() {
|
|
2424
|
+
return {
|
|
2425
|
+
name: "akan-server-use-client-fetch",
|
|
2426
|
+
setup(build) {
|
|
2427
|
+
build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
|
|
2428
|
+
const source = await Bun.file(args.path).text();
|
|
2429
|
+
const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source);
|
|
2430
|
+
if (transformed === source)
|
|
2431
|
+
return;
|
|
2432
|
+
return { contents: transformed, loader: loaderFor(args.path) };
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
static transformServerUseClientFetchSource(source) {
|
|
2438
|
+
if (!source.includes(`with { type: "macro" }`) || !source.includes("FetchClient.build"))
|
|
2439
|
+
return source;
|
|
2440
|
+
return source.replace(/import\s+\{\s*getSerializedSignal\s*\}\s+from\s+["']\.\/sig["']\s+with\s+\{\s*type\s*:\s*["']macro["']\s*\};/, `import { fetch as serverFetch } from "./sig";`).replace(/const\s+fetchProto\s*=\s*FetchClient\.build<[^;]+;/, "const fetchProto = FetchClient.build<typeof signal>(cnst, serverFetch.serializedSignal, { Err: pageProto.Err, base: serverFetch });");
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevPatcher.ts
|
|
2445
|
+
import fs7 from "fs";
|
|
2446
|
+
import path15 from "path";
|
|
2447
|
+
import { appGenerationOf, CSR_DEV_MANIFEST_FILE } from "akanjs/server/hmr/csrDevManifest";
|
|
2448
|
+
|
|
2449
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevResolver.ts
|
|
2450
|
+
import fs6 from "fs";
|
|
2451
|
+
import path14 from "path";
|
|
2452
|
+
class CsrDevResolver {
|
|
2453
|
+
static inline = "inline";
|
|
2454
|
+
#paths;
|
|
2455
|
+
#context;
|
|
2456
|
+
#entryFiles;
|
|
2457
|
+
#resolution = new Map;
|
|
2458
|
+
#fallbacks = new Set;
|
|
2459
|
+
#runtimeResolved = new Map;
|
|
2460
|
+
#browserMaps = new Map;
|
|
2461
|
+
#loaded = new Set;
|
|
2462
|
+
#startedAt;
|
|
2463
|
+
#prepassDone = false;
|
|
2464
|
+
constructor({
|
|
2465
|
+
paths,
|
|
2466
|
+
context,
|
|
2467
|
+
entryFiles,
|
|
2468
|
+
resolution = {},
|
|
2469
|
+
runtimeResolved = {},
|
|
2470
|
+
startedAt = Date.now() - process.uptime() * 1000
|
|
2471
|
+
}) {
|
|
2472
|
+
this.#paths = paths;
|
|
2473
|
+
this.#startedAt = startedAt;
|
|
2474
|
+
this.#context = context;
|
|
2475
|
+
this.#entryFiles = entryFiles;
|
|
2476
|
+
for (const [importer, specifiers] of Object.entries(runtimeResolved))
|
|
2477
|
+
this.#runtimeResolved.set(paths.fileOf(importer), new Set(specifiers));
|
|
2478
|
+
for (const [importer, bySpecifier] of Object.entries(resolution))
|
|
2479
|
+
this.#resolution.set(paths.fileOf(importer), new Map(Object.entries(bySpecifier).map(([specifier, target]) => [
|
|
2480
|
+
specifier,
|
|
2481
|
+
CsrDevPaths.isStub(target) ? target : paths.fileOf(target)
|
|
2482
|
+
])));
|
|
2483
|
+
}
|
|
2484
|
+
get prepassDone() {
|
|
2485
|
+
return this.#prepassDone;
|
|
2486
|
+
}
|
|
2487
|
+
get fallbacks() {
|
|
2488
|
+
return this.#fallbacks;
|
|
2489
|
+
}
|
|
2490
|
+
async prepass() {
|
|
2491
|
+
const result = await Bun.build({
|
|
2492
|
+
entrypoints: [...this.#entryFiles, ...this.#context.refreshFile ? [this.#context.refreshFile] : []],
|
|
2493
|
+
target: "browser",
|
|
2494
|
+
metafile: true,
|
|
2495
|
+
external: [...this.#context.externals ?? []],
|
|
2496
|
+
env: "AKAN_PUBLIC_*",
|
|
2497
|
+
define: this.#context.define,
|
|
2498
|
+
optimizeImports: this.#context.optimizeImports,
|
|
2499
|
+
plugins: [PagesBundleBuilder.createCssStubPlugin()]
|
|
2500
|
+
});
|
|
2501
|
+
this.#record(result.metafile.inputs);
|
|
2502
|
+
this.#prepassDone = true;
|
|
2503
|
+
}
|
|
2504
|
+
async cover(files) {
|
|
2505
|
+
if (!this.#prepassDone)
|
|
2506
|
+
return;
|
|
2507
|
+
const pending = files.filter((file) => CsrDevPaths.isScript(file) && !this.#loaded.has(file));
|
|
2508
|
+
if (pending.length === 0)
|
|
2509
|
+
return;
|
|
2510
|
+
for (const file of pending)
|
|
2511
|
+
this.#loaded.add(file);
|
|
2512
|
+
const entries = new Set(pending);
|
|
2513
|
+
const result = await Bun.build({
|
|
2514
|
+
entrypoints: pending,
|
|
2515
|
+
target: "browser",
|
|
2516
|
+
metafile: true,
|
|
2517
|
+
throw: false,
|
|
2518
|
+
external: [...this.#context.externals ?? []],
|
|
2519
|
+
env: "AKAN_PUBLIC_*",
|
|
2520
|
+
define: this.#context.define,
|
|
2521
|
+
plugins: [
|
|
2522
|
+
{
|
|
2523
|
+
name: "akan-csr-cover",
|
|
2524
|
+
setup: (build) => {
|
|
2525
|
+
build.onLoad({ filter: /.*/ }, (args) => entries.has(CsrDevPaths.realpath(args.path)) ? undefined : { contents: "module.exports = {};", loader: "js" });
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
]
|
|
2529
|
+
});
|
|
2530
|
+
if (result.metafile)
|
|
2531
|
+
this.#record(result.metafile.inputs, entries);
|
|
2532
|
+
}
|
|
2533
|
+
#record(inputs, only) {
|
|
2534
|
+
for (const [input, { imports }] of Object.entries(inputs)) {
|
|
2535
|
+
const importer = CsrDevPaths.realpath(path14.resolve(input));
|
|
2536
|
+
if (only && !only.has(importer))
|
|
2537
|
+
continue;
|
|
2538
|
+
if (imports.every((record) => record.original))
|
|
2539
|
+
this.#loaded.add(importer);
|
|
2540
|
+
for (const record of imports) {
|
|
2541
|
+
if (!record.original)
|
|
2542
|
+
continue;
|
|
2543
|
+
if (record.external) {
|
|
2544
|
+
if (record.original.startsWith("node:"))
|
|
2545
|
+
this.#remember(importer, record.original, `${CsrDevPaths.stubPrefix}${record.original}`);
|
|
2546
|
+
continue;
|
|
2547
|
+
}
|
|
2548
|
+
const target = path14.resolve(record.path);
|
|
2549
|
+
if (!fs6.existsSync(target))
|
|
2550
|
+
continue;
|
|
2551
|
+
this.#remember(importer, record.original, CsrDevPaths.realpath(target));
|
|
2552
|
+
this.#runtimeResolved.get(importer)?.delete(record.original);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
resolve(importer, specifier, kind = "import") {
|
|
2557
|
+
if (this.#isInlineSpecifier(specifier))
|
|
2558
|
+
return CsrDevResolver.inline;
|
|
2559
|
+
const disabled = this.#disabledInBrowser(importer, specifier, kind);
|
|
2560
|
+
if (disabled)
|
|
2561
|
+
return disabled;
|
|
2562
|
+
const known = this.#resolution.get(importer)?.get(specifier);
|
|
2563
|
+
const usable = known && this.#stillAnswers(importer, specifier, known, kind) ? known : null;
|
|
2564
|
+
const target = usable ?? this.#resolveUnknown(importer, specifier, kind);
|
|
2565
|
+
if (target === null || CsrDevPaths.isStub(target))
|
|
2566
|
+
return target;
|
|
2567
|
+
if (!CsrDevPaths.isScript(target))
|
|
2568
|
+
return CsrDevResolver.inline;
|
|
2569
|
+
if (!usable)
|
|
2570
|
+
this.#remember(importer, specifier, target);
|
|
2571
|
+
return target;
|
|
2572
|
+
}
|
|
2573
|
+
#stillAnswers(importer, specifier, known, kind) {
|
|
2574
|
+
if (CsrDevPaths.isStub(known))
|
|
2575
|
+
return true;
|
|
2576
|
+
if (!fs6.existsSync(known))
|
|
2577
|
+
return false;
|
|
2578
|
+
if (!specifier.startsWith(".") || CsrDevPaths.isVendorFile(importer))
|
|
2579
|
+
return true;
|
|
2580
|
+
if (this.#hasBrowserMap(path14.dirname(importer)))
|
|
2581
|
+
return true;
|
|
2582
|
+
const onDisk = CsrDevPaths.resolveRelative(path14.dirname(importer), specifier, kind);
|
|
2583
|
+
return onDisk !== null && CsrDevPaths.realpath(onDisk) === known;
|
|
2584
|
+
}
|
|
2585
|
+
isRegistryModule(file) {
|
|
2586
|
+
if (!CsrDevPaths.isScript(file))
|
|
2587
|
+
return false;
|
|
2588
|
+
const packageName = CsrDevResolver.#packageNameOf(file);
|
|
2589
|
+
if (packageName === null)
|
|
2590
|
+
return true;
|
|
2591
|
+
return !this.#context.optimizeImports.some((pattern) => {
|
|
2592
|
+
const base = CsrDevResolver.#patternBase(pattern);
|
|
2593
|
+
return base === packageName || base.startsWith(`${packageName}/`);
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
#disabledInBrowser(importer, specifier, kind) {
|
|
2597
|
+
const map = this.#browserMapOf(path14.dirname(importer));
|
|
2598
|
+
if (!map || map.offFiles.size === 0 && map.offNames.size === 0)
|
|
2599
|
+
return null;
|
|
2600
|
+
if (!specifier.startsWith(".") && !path14.isAbsolute(specifier))
|
|
2601
|
+
return map.offNames.has(specifier) ? `${CsrDevPaths.emptyStubPrefix}${specifier}` : null;
|
|
2602
|
+
const target = CsrDevPaths.resolveRelative(path14.dirname(importer), specifier, kind);
|
|
2603
|
+
return target && map.offFiles.has(CsrDevPaths.realpath(target)) ? `${CsrDevPaths.emptyStubPrefix}${this.#paths.idOf(target)}` : null;
|
|
2604
|
+
}
|
|
2605
|
+
#hasBrowserMap(dir) {
|
|
2606
|
+
return this.#browserMapOf(dir) !== null;
|
|
2607
|
+
}
|
|
2608
|
+
#browserMapOf(dir) {
|
|
2609
|
+
const cached = this.#browserMaps.get(dir);
|
|
2610
|
+
if (cached !== undefined)
|
|
2611
|
+
return cached;
|
|
2612
|
+
const pkgFile = path14.join(dir, "package.json");
|
|
2613
|
+
const parent = path14.dirname(dir);
|
|
2614
|
+
let map = null;
|
|
2615
|
+
if (fs6.existsSync(pkgFile)) {
|
|
2616
|
+
try {
|
|
2617
|
+
const browser = JSON.parse(fs6.readFileSync(pkgFile, "utf8")).browser;
|
|
2618
|
+
if (typeof browser === "object" && browser !== null) {
|
|
2619
|
+
const off = Object.entries(browser).filter(([, target]) => target === false).map(([key]) => key);
|
|
2620
|
+
const files = off.filter((key) => key.startsWith(".")).map((key) => CsrDevPaths.resolveRelative(dir, key)).filter((file) => !!file);
|
|
2621
|
+
map = {
|
|
2622
|
+
offFiles: new Set(files.map((file) => CsrDevPaths.realpath(file))),
|
|
2623
|
+
offNames: new Set(off.filter((key) => !key.startsWith(".")))
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
} catch {}
|
|
2627
|
+
} else if (parent !== dir)
|
|
2628
|
+
map = this.#browserMapOf(parent);
|
|
2629
|
+
this.#browserMaps.set(dir, map);
|
|
2630
|
+
return map;
|
|
2631
|
+
}
|
|
2632
|
+
forget(importer) {
|
|
2633
|
+
this.#resolution.delete(importer);
|
|
2634
|
+
this.#runtimeResolved.delete(importer);
|
|
2635
|
+
}
|
|
2636
|
+
serializeRuntimeResolved() {
|
|
2637
|
+
return Object.fromEntries([...this.#runtimeResolved].filter(([, specifiers]) => specifiers.size > 0).map(([importer, specifiers]) => [this.#paths.idOf(importer), [...specifiers].sort()]));
|
|
2638
|
+
}
|
|
2639
|
+
serialize() {
|
|
2640
|
+
return Object.fromEntries([...this.#resolution].map(([importer, bySpecifier]) => [
|
|
2641
|
+
this.#paths.idOf(importer),
|
|
2642
|
+
Object.fromEntries([...bySpecifier].filter(([, target]) => CsrDevPaths.isStub(target) || fs6.existsSync(target)).map(([specifier, target]) => [specifier, CsrDevPaths.isStub(target) ? target : this.#paths.idOf(target)]))
|
|
2643
|
+
]));
|
|
2644
|
+
}
|
|
2645
|
+
#resolveUnknown(importer, specifier, kind) {
|
|
2646
|
+
if (specifier.startsWith("node:"))
|
|
2647
|
+
return `${CsrDevPaths.stubPrefix}${specifier}`;
|
|
2648
|
+
const relative = specifier.startsWith(".") || path14.isAbsolute(specifier);
|
|
2649
|
+
const cannotResolve = () => new Error(`[csr-dev] cannot resolve "${specifier}" from ${this.#paths.idOf(importer)}`);
|
|
2650
|
+
if (relative) {
|
|
2651
|
+
const onDisk = CsrDevPaths.resolveRelative(path14.dirname(importer), specifier, kind) ?? CsrDevResolver.#onDisk(CsrDevPaths.tryResolve(specifier, path14.dirname(importer)));
|
|
2652
|
+
if (!onDisk)
|
|
2653
|
+
throw cannotResolve();
|
|
2654
|
+
return CsrDevPaths.realpath(onDisk);
|
|
2655
|
+
}
|
|
2656
|
+
const sibling = this.#resolvedBySibling(importer, specifier);
|
|
2657
|
+
if (sibling)
|
|
2658
|
+
return sibling;
|
|
2659
|
+
const resolved = CsrDevPaths.tryResolve(specifier, path14.dirname(importer));
|
|
2660
|
+
const moved = resolved !== null && path14.isAbsolute(resolved) && !fs6.existsSync(resolved);
|
|
2661
|
+
const installed = resolved === null && CsrDevPaths.installedSince(specifier, path14.dirname(importer), this.#startedAt);
|
|
2662
|
+
if (!this.#prepassDone || moved) {
|
|
2663
|
+
if (resolved === null && !installed && !CsrDevPaths.isVendorFile(importer))
|
|
2664
|
+
throw cannotResolve();
|
|
2665
|
+
return null;
|
|
2666
|
+
}
|
|
2667
|
+
if (!resolved) {
|
|
2668
|
+
if (installed)
|
|
2669
|
+
return null;
|
|
2670
|
+
throw cannotResolve();
|
|
2671
|
+
}
|
|
2672
|
+
if (!path14.isAbsolute(resolved))
|
|
2673
|
+
return `${CsrDevPaths.stubPrefix}${specifier}`;
|
|
2674
|
+
this.#fallbacks.add(specifier);
|
|
2675
|
+
this.#runtimeResolved.set(importer, (this.#runtimeResolved.get(importer) ?? new Set).add(specifier));
|
|
2676
|
+
return CsrDevPaths.realpath(resolved);
|
|
2677
|
+
}
|
|
2678
|
+
static #onDisk(resolved) {
|
|
2679
|
+
return resolved && path14.isAbsolute(resolved) && fs6.existsSync(resolved) ? resolved : null;
|
|
2680
|
+
}
|
|
2681
|
+
#resolvedBySibling(importer, specifier) {
|
|
2682
|
+
const scope = CsrDevResolver.#packageScopeOf(importer);
|
|
2683
|
+
for (const [other, bySpecifier] of this.#resolution) {
|
|
2684
|
+
const target = bySpecifier.get(specifier);
|
|
2685
|
+
if (!target || other === importer || this.#runtimeResolved.get(other)?.has(specifier))
|
|
2686
|
+
continue;
|
|
2687
|
+
if (!CsrDevPaths.isStub(target) && !fs6.existsSync(target))
|
|
2688
|
+
continue;
|
|
2689
|
+
if (CsrDevResolver.#packageScopeOf(other) === scope)
|
|
2690
|
+
return target;
|
|
2691
|
+
}
|
|
2692
|
+
return null;
|
|
2693
|
+
}
|
|
2694
|
+
#remember(importer, specifier, target) {
|
|
2695
|
+
const byImporter = this.#resolution.get(importer) ?? new Map;
|
|
2696
|
+
this.#resolution.set(importer, byImporter.set(specifier, target));
|
|
2697
|
+
}
|
|
2698
|
+
#isInlineSpecifier(specifier) {
|
|
2699
|
+
if (specifier.startsWith(".") || path14.isAbsolute(specifier) || specifier.startsWith("node:"))
|
|
2700
|
+
return false;
|
|
2701
|
+
return this.#context.optimizeImports.some((pattern) => {
|
|
2702
|
+
const base = CsrDevResolver.#patternBase(pattern);
|
|
2703
|
+
return specifier === base || specifier.startsWith(`${base}/`);
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
static #patternBase(pattern) {
|
|
2707
|
+
return pattern.endsWith("/*") ? pattern.slice(0, -2) : pattern;
|
|
2708
|
+
}
|
|
2709
|
+
static #packageNameOf(file) {
|
|
2710
|
+
const marker = `${path14.sep}node_modules${path14.sep}`;
|
|
2711
|
+
const index = file.lastIndexOf(marker);
|
|
2712
|
+
if (index < 0)
|
|
2713
|
+
return null;
|
|
2714
|
+
const [scope, name] = file.slice(index + marker.length).split(path14.sep);
|
|
2715
|
+
return (scope?.startsWith("@") ? `${scope}/${name}` : scope) || null;
|
|
2716
|
+
}
|
|
2717
|
+
static #packageScopeOf(file) {
|
|
2718
|
+
const marker = `${path14.sep}node_modules${path14.sep}`;
|
|
2719
|
+
const index = file.lastIndexOf(marker);
|
|
2720
|
+
if (index < 0)
|
|
2721
|
+
return "";
|
|
2722
|
+
const [scope, name] = file.slice(index + marker.length).split(path14.sep);
|
|
2723
|
+
return file.slice(0, index + marker.length) + (scope?.startsWith("@") ? `${scope}${path14.sep}${name}` : scope);
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevPatcher.ts
|
|
2728
|
+
class CsrDevPatcher {
|
|
2729
|
+
#bundler;
|
|
2730
|
+
#resident;
|
|
2731
|
+
#state = null;
|
|
2732
|
+
#delegated = new Map;
|
|
2733
|
+
constructor(bundler, { resident = false } = {}) {
|
|
2734
|
+
this.#bundler = bundler;
|
|
2735
|
+
this.#resident = resident;
|
|
2736
|
+
}
|
|
2737
|
+
forget() {
|
|
2738
|
+
this.#state = null;
|
|
2739
|
+
}
|
|
2740
|
+
releaseHandedOver() {
|
|
2741
|
+
for (const [id, handedOver] of this.#delegated)
|
|
2742
|
+
if (!handedOver.failed)
|
|
2743
|
+
this.#delegated.delete(id);
|
|
2744
|
+
}
|
|
2745
|
+
async update(changedFiles = [], { announce, roots = [], onlyRoots = false, allowWholeAppBuilds = false } = {}) {
|
|
2746
|
+
const context = await this.#bundler.context();
|
|
2747
|
+
if (!context)
|
|
2748
|
+
return { kind: "unchanged" };
|
|
2749
|
+
const resident = this.#state;
|
|
2750
|
+
const state = resident ?? await this.#load();
|
|
2751
|
+
if (!state) {
|
|
2752
|
+
const manifest = await this.#bundler.writer.readJson(CSR_DEV_MANIFEST_FILE);
|
|
2753
|
+
const reason = !manifest ? "first build" : await this.#bundler.writer.isBuilding() ? "the last whole build was cut short" : "the dev bundle config changed";
|
|
2754
|
+
return this.#handBack(reason, (manifest?.generation ?? 0) + 1, context, { first: !manifest });
|
|
2755
|
+
}
|
|
2756
|
+
const generation = state.manifest.generation + 1;
|
|
2757
|
+
if (state.graph.configKey !== context.configKey)
|
|
2758
|
+
return this.#handBack("the dev bundle config changed", generation, context);
|
|
2759
|
+
const metadataFile = changedFiles.find((file) => this.#bundler.isMetadataFile(file));
|
|
2760
|
+
if (metadataFile)
|
|
2761
|
+
return this.#handBack(`${path15.basename(metadataFile)} changed`, generation, context);
|
|
2762
|
+
if (!resident && state.graph.metadata !== await this.#bundler.metadataFingerprint())
|
|
2763
|
+
return this.#handBack("signal or dictionary metadata changed since the registry was built", generation, context);
|
|
2764
|
+
this.#state = this.#resident ? state : null;
|
|
2765
|
+
const healable = appGenerationOf(state.manifest) < state.manifest.generation && this.#withoutFactory(state, (file) => fs7.existsSync(file)).length === 0;
|
|
2766
|
+
if (healable)
|
|
2767
|
+
state.manifest = await this.#bundler.writer.healApp(state.graph, state.manifest, await this.#code(state));
|
|
2768
|
+
return await this.#patch(context, state, changedFiles, generation, {
|
|
2769
|
+
announce,
|
|
2770
|
+
roots,
|
|
2771
|
+
onlyRoots,
|
|
2772
|
+
allowWholeAppBuilds
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
async#patch(context, state, changedFiles, generation, { announce, roots = [], onlyRoots = false, allowWholeAppBuilds }) {
|
|
2776
|
+
const { paths, writer } = this.#bundler;
|
|
2777
|
+
const { graph } = state;
|
|
2778
|
+
const entries = await this.#bundler.writeEntries(context);
|
|
2779
|
+
const entryIds = this.#bundler.entryIds(entries.files);
|
|
2780
|
+
const present = CsrDevPatcher.#presence(changedFiles);
|
|
2781
|
+
const forgotten = this.#forgetDeletedModules(state, present);
|
|
2782
|
+
const changed = onlyRoots ? new Set : await this.#changedModules(graph, changedFiles, present);
|
|
2783
|
+
for (const id of this.#withoutFactory(state, present))
|
|
2784
|
+
changed.add(id);
|
|
2785
|
+
for (const file of entries.changed)
|
|
2786
|
+
changed.add(paths.idOf(file));
|
|
2787
|
+
const rootFiles = [...Object.values(entries.files), ...roots.filter((file) => fs7.existsSync(file))];
|
|
2788
|
+
for (const file of rootFiles) {
|
|
2789
|
+
const id = paths.idOf(file);
|
|
2790
|
+
if (!graph.modules[id] && !(onlyRoots && await this.#parked(graph, id)))
|
|
2791
|
+
changed.add(id);
|
|
2792
|
+
}
|
|
2793
|
+
const carried = onlyRoots ? [] : await this.#carriedRoots(graph, changed, present);
|
|
2794
|
+
const routesMoved = this.#bundler.reloadsOnEntryChange && (entries.changed.length > 0 || JSON.stringify(entryIds) !== JSON.stringify(graph.entries));
|
|
2795
|
+
if (changed.size === 0) {
|
|
2796
|
+
if (carried.length === 0)
|
|
2797
|
+
return { kind: "unchanged" };
|
|
2798
|
+
return await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
|
|
2799
|
+
}
|
|
2800
|
+
const resolver = new CsrDevResolver({
|
|
2801
|
+
paths,
|
|
2802
|
+
context,
|
|
2803
|
+
entryFiles: rootFiles.map((file) => CsrDevPaths.realpath(file)),
|
|
2804
|
+
resolution: graph.resolution,
|
|
2805
|
+
runtimeResolved: graph.runtimeResolved
|
|
2806
|
+
});
|
|
2807
|
+
const known = new Set(Object.keys(graph.modules).map((id) => paths.fileOf(id)));
|
|
2808
|
+
const compile = async (ids) => await this.#bundler.compiler(context, resolver).compile(ids.map((id) => paths.fileOf(id)), known, { refuseNewVendor: !allowWholeAppBuilds, refusePrepass: !allowWholeAppBuilds });
|
|
2809
|
+
let result;
|
|
2810
|
+
let orphans;
|
|
2811
|
+
try {
|
|
2812
|
+
({ result, orphans } = await this.#compileLeavingOrphans(compile, graph, changed));
|
|
2813
|
+
for (const module of result.modules)
|
|
2814
|
+
known.add(module.file);
|
|
2815
|
+
result = await this.#withTwinImporters(compile, graph, result);
|
|
2816
|
+
} catch (error) {
|
|
2817
|
+
graph.pending = [...new Set([...graph.pending, ...changed, ...carried])];
|
|
2818
|
+
if (!this.#resident)
|
|
2819
|
+
graph.failed = {
|
|
2820
|
+
roots: [...changed, ...carried],
|
|
2821
|
+
files: await CsrDevPatcher.#hashesOf(paths, CsrDevPatcher.#implicatedFiles(error))
|
|
2822
|
+
};
|
|
2823
|
+
await writer.writeJson("graph.json", graph);
|
|
2824
|
+
throw error;
|
|
2825
|
+
}
|
|
2826
|
+
const refusal = result.refusedVendors.length > 0 ? "an npm module joined the graph" : result.unresolved.length > 0 ? `a new import needs the resolution build: ${result.unresolved[0]}` : null;
|
|
2827
|
+
if (refusal) {
|
|
2828
|
+
if (routesMoved)
|
|
2829
|
+
await writer.writeJson("graph.json", { ...graph, entries: {} });
|
|
2830
|
+
if (onlyRoots) {
|
|
2831
|
+
for (const id of changed)
|
|
2832
|
+
if (!graph.modules[id])
|
|
2833
|
+
this.#delegated.set(id, {
|
|
2834
|
+
hash: await CsrDevPaths.hashOf(paths.fileOf(id)),
|
|
2835
|
+
before: JSON.stringify(graph.failed ?? null)
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
return this.#handBack(refusal, generation, context);
|
|
2839
|
+
}
|
|
2840
|
+
const compiled = result.modules;
|
|
2841
|
+
const vendorJoined = compiled.some((module) => module.vendor && !graph.modules[module.id]);
|
|
2842
|
+
const code = await this.#code(state);
|
|
2843
|
+
for (const id of [...orphans, ...this.#caseTwins(graph, compiled)]) {
|
|
2844
|
+
delete graph.modules[id];
|
|
2845
|
+
resolver.forget(paths.fileOf(id));
|
|
2846
|
+
code?.modules.delete(id);
|
|
2847
|
+
forgotten.push(id);
|
|
2848
|
+
}
|
|
2849
|
+
graph.pending = graph.pending.filter((id) => !orphans.includes(id));
|
|
2850
|
+
this.#bundler.merge(graph, resolver, compiled);
|
|
2851
|
+
if (!this.#resident)
|
|
2852
|
+
delete graph.failed;
|
|
2853
|
+
for (const module of compiled)
|
|
2854
|
+
this.#delegated.delete(module.id);
|
|
2855
|
+
graph.entries = entryIds;
|
|
2856
|
+
for (const module of compiled) {
|
|
2857
|
+
if (module.vendor || !code)
|
|
2858
|
+
continue;
|
|
2859
|
+
code.modules.set(module.id, module.factory);
|
|
2860
|
+
if (module.helpers)
|
|
2861
|
+
code.helpers.set(module.helpers.hash, module.helpers.definition);
|
|
2862
|
+
}
|
|
2863
|
+
const changedIds = compiled.filter((module) => !module.vendor).map((module) => module.id);
|
|
2864
|
+
const constantId = changedIds.find((id) => id.endsWith(".constant.ts"));
|
|
2865
|
+
const reason = vendorJoined ? "an npm module joined the graph" : routesMoved ? "the route table changed" : constantId ? `${path15.basename(constantId)} changed` : undefined;
|
|
2866
|
+
if (compiled.length === 0 && !reason) {
|
|
2867
|
+
await writer.writeJson("graph.json", graph);
|
|
2868
|
+
await writer.forgetModules(forgotten);
|
|
2869
|
+
if (carried.length === 0)
|
|
2870
|
+
return { kind: "unchanged" };
|
|
2871
|
+
return await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
|
|
2872
|
+
}
|
|
2873
|
+
const previous = state.manifest;
|
|
2874
|
+
const next = { ...previous, generation, entries: entryIds };
|
|
2875
|
+
const update = (patchUrl) => ({
|
|
2876
|
+
generation,
|
|
2877
|
+
reload: !!reason,
|
|
2878
|
+
reason,
|
|
2879
|
+
patchUrl,
|
|
2880
|
+
changedIds,
|
|
2881
|
+
moduleCount: Object.keys(graph.modules).length,
|
|
2882
|
+
epoch: previous.epoch
|
|
2883
|
+
});
|
|
2884
|
+
let outcome;
|
|
2885
|
+
if (reason) {
|
|
2886
|
+
await writer.forgetPatch(generation);
|
|
2887
|
+
await writer.writeModules(compiled);
|
|
2888
|
+
const vendorFile = vendorJoined ? await writer.writeVendor(graph) : previous.vendorFile;
|
|
2889
|
+
await writer.writeApp(graph, { generation, vendorFile, epoch: previous.epoch }, code);
|
|
2890
|
+
state.manifest = { ...next, vendorFile, appGeneration: generation };
|
|
2891
|
+
await writer.writeState(graph, state.manifest);
|
|
2892
|
+
await writer.prune(vendorFile, generation);
|
|
2893
|
+
const reloaded = update();
|
|
2894
|
+
announce?.(reloaded);
|
|
2895
|
+
outcome = { kind: "update", update: reloaded };
|
|
2896
|
+
} else {
|
|
2897
|
+
const patched = update(await writer.writePatch(generation, compiled.filter((module) => !module.vendor)));
|
|
2898
|
+
await writer.commitPatch(graph, { ...next, appGeneration: appGenerationOf(previous) }, () => announce?.(patched), { modules: compiled, code });
|
|
2899
|
+
state.manifest = { ...next, appGeneration: generation };
|
|
2900
|
+
await writer.prune(previous.vendorFile, generation);
|
|
2901
|
+
outcome = { kind: "update", update: patched };
|
|
2902
|
+
}
|
|
2903
|
+
await writer.forgetModules(forgotten);
|
|
2904
|
+
if (carried.length === 0)
|
|
2905
|
+
return outcome;
|
|
2906
|
+
const retried = await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
|
|
2907
|
+
return retried.kind === "delegate" ? retried : outcome;
|
|
2908
|
+
}
|
|
2909
|
+
async#retryCarried(context, state, carried, { announce, allowWholeAppBuilds }) {
|
|
2910
|
+
const { paths, writer } = this.#bundler;
|
|
2911
|
+
const retried = await this.#patch(context, state, [], state.manifest.generation + 1, {
|
|
2912
|
+
announce,
|
|
2913
|
+
roots: carried.map((id) => paths.fileOf(id)),
|
|
2914
|
+
onlyRoots: true,
|
|
2915
|
+
allowWholeAppBuilds
|
|
2916
|
+
});
|
|
2917
|
+
if (retried.kind === "delegate") {
|
|
2918
|
+
state.graph.pending = [...new Set([...state.graph.pending, ...carried])];
|
|
2919
|
+
await writer.writeJson("graph.json", state.graph);
|
|
2920
|
+
}
|
|
2921
|
+
return retried;
|
|
2922
|
+
}
|
|
2923
|
+
async#compileLeavingOrphans(compile, graph, changed) {
|
|
2924
|
+
const { paths } = this.#bundler;
|
|
2925
|
+
try {
|
|
2926
|
+
return { result: await compile([...changed]), orphans: [] };
|
|
2927
|
+
} catch (error) {
|
|
2928
|
+
const failing = new Set(CsrDevPatcher.#failingFiles(error).map((file) => paths.idOf(file)));
|
|
2929
|
+
const broken = [...changed].filter((id) => failing.has(id) && graph.modules[id]);
|
|
2930
|
+
if (broken.length === 0)
|
|
2931
|
+
throw error;
|
|
2932
|
+
const rest = [...changed].filter((id) => !broken.includes(id));
|
|
2933
|
+
const empty = { modules: [], refusedVendors: [], unresolved: [] };
|
|
2934
|
+
const result = rest.length === 0 ? empty : await compile(rest).catch(() => {
|
|
2935
|
+
throw error;
|
|
2936
|
+
});
|
|
2937
|
+
const modules = { ...graph.modules };
|
|
2938
|
+
for (const module of result.modules)
|
|
2939
|
+
modules[module.id] = {
|
|
2940
|
+
vendor: module.vendor,
|
|
2941
|
+
mtimeMs: module.mtimeMs,
|
|
2942
|
+
hash: module.hash,
|
|
2943
|
+
deps: module.deps.map((dep) => CsrDevPaths.isStub(dep) ? dep : paths.idOf(dep))
|
|
2944
|
+
};
|
|
2945
|
+
const reached = await this.#bundler.reachable({ ...graph, modules });
|
|
2946
|
+
if (broken.some((id) => reached.has(id)))
|
|
2947
|
+
throw error;
|
|
2948
|
+
const orphans = Object.keys(modules).filter((id) => !reached.has(id) && !modules[id]?.vendor);
|
|
2949
|
+
return { result, orphans };
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
#caseTwins(graph, compiled) {
|
|
2953
|
+
const { paths } = this.#bundler;
|
|
2954
|
+
const idsByFold = new Map;
|
|
2955
|
+
for (const id of Object.keys(graph.modules))
|
|
2956
|
+
idsByFold.set(id.toLowerCase(), [...idsByFold.get(id.toLowerCase()) ?? [], id]);
|
|
2957
|
+
return compiled.flatMap((module) => (idsByFold.get(module.id.toLowerCase()) ?? []).filter((id) => id !== module.id && !graph.modules[id]?.vendor && !CsrDevPaths.isNamed(paths.fileOf(id))));
|
|
2958
|
+
}
|
|
2959
|
+
async#withTwinImporters(compile, graph, result) {
|
|
2960
|
+
const twins = new Set(this.#caseTwins(graph, result.modules));
|
|
2961
|
+
if (twins.size === 0)
|
|
2962
|
+
return result;
|
|
2963
|
+
const compiledIds = new Set(result.modules.map((module) => module.id));
|
|
2964
|
+
const importers = Object.entries(graph.modules).filter(([id, module]) => !module.vendor && !compiledIds.has(id) && !twins.has(id)).filter(([, module]) => module.deps.some((dep) => twins.has(dep))).map(([id]) => id);
|
|
2965
|
+
if (importers.length === 0)
|
|
2966
|
+
return result;
|
|
2967
|
+
const more = await compile(importers);
|
|
2968
|
+
return {
|
|
2969
|
+
modules: [...result.modules, ...more.modules],
|
|
2970
|
+
refusedVendors: [...result.refusedVendors, ...more.refusedVendors],
|
|
2971
|
+
unresolved: [...result.unresolved, ...more.unresolved]
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
static #implicatedFiles(error) {
|
|
2975
|
+
const failing = CsrDevPatcher.#failingFiles(error);
|
|
2976
|
+
const messages = error instanceof AggregateError ? error.errors : [];
|
|
2977
|
+
const named = messages.flatMap((message) => {
|
|
2978
|
+
const text = String(message.message ?? "");
|
|
2979
|
+
const bases = [process.cwd(), ...failing.map((file) => path15.dirname(file))];
|
|
2980
|
+
return [...text.matchAll(/"([^"\n]+)"/g)].flatMap(([, quoted = ""]) => {
|
|
2981
|
+
const found = bases.map((base) => path15.resolve(base, quoted)).find((file) => CsrDevPaths.isScript(file) && fs7.existsSync(file));
|
|
2982
|
+
return found ? [CsrDevPaths.realpath(found)] : [];
|
|
2983
|
+
});
|
|
2984
|
+
});
|
|
2985
|
+
return [...new Set([...failing, ...named])];
|
|
2986
|
+
}
|
|
2987
|
+
static #failingFiles(error) {
|
|
2988
|
+
const messages = error instanceof AggregateError ? error.errors : [];
|
|
2989
|
+
return messages.map((message) => message.position?.file).filter((file) => typeof file === "string" && path15.isAbsolute(file)).map((file) => CsrDevPaths.realpath(file));
|
|
2990
|
+
}
|
|
2991
|
+
async#carriedRoots(graph, changed, present) {
|
|
2992
|
+
const { paths } = this.#bundler;
|
|
2993
|
+
const outside = graph.pending.filter((id) => !graph.modules[id] && !changed.has(id) && present(paths.fileOf(id)));
|
|
2994
|
+
if (outside.length === 0)
|
|
2995
|
+
return [];
|
|
2996
|
+
const wanted = await this.#bundler.wantedRoots(outside.map((id) => paths.fileOf(id)), graph);
|
|
2997
|
+
const kept = outside.filter((id) => wanted.has(paths.fileOf(id)));
|
|
2998
|
+
graph.pending = graph.pending.filter((id) => graph.modules[id] || kept.includes(id));
|
|
2999
|
+
const carried = [];
|
|
3000
|
+
for (const id of kept)
|
|
3001
|
+
if (!await this.#parked(graph, id))
|
|
3002
|
+
carried.push(id);
|
|
3003
|
+
return carried;
|
|
3004
|
+
}
|
|
3005
|
+
async#parked(graph, id) {
|
|
3006
|
+
const { paths } = this.#bundler;
|
|
3007
|
+
const handedOver = this.#delegated.get(id);
|
|
3008
|
+
if (!handedOver || handedOver.hash !== await CsrDevPaths.hashOf(paths.fileOf(id)))
|
|
3009
|
+
return false;
|
|
3010
|
+
if (!handedOver.failed) {
|
|
3011
|
+
if (JSON.stringify(graph.failed ?? null) === handedOver.before)
|
|
3012
|
+
return true;
|
|
3013
|
+
if (graph.failed?.roots.includes(id))
|
|
3014
|
+
handedOver.failed = graph.failed.files;
|
|
3015
|
+
}
|
|
3016
|
+
if (!handedOver.failed)
|
|
3017
|
+
return false;
|
|
3018
|
+
for (const [failedId, hash] of Object.entries(handedOver.failed))
|
|
3019
|
+
if (await CsrDevPaths.hashOf(paths.fileOf(failedId)) !== hash)
|
|
3020
|
+
return false;
|
|
3021
|
+
return true;
|
|
3022
|
+
}
|
|
3023
|
+
static async#hashesOf(paths, files) {
|
|
3024
|
+
return Object.fromEntries(await Promise.all(files.map(async (file) => [paths.idOf(file), await CsrDevPaths.hashOf(file)])));
|
|
3025
|
+
}
|
|
3026
|
+
#withoutFactory({ graph, code }, present) {
|
|
3027
|
+
const { paths, writer } = this.#bundler;
|
|
3028
|
+
const hasFactory = (id) => code ? code.modules.has(id) : writer.hasModule(id);
|
|
3029
|
+
return Object.entries(graph.modules).filter(([id, module]) => !module.vendor && !hasFactory(id) && present(paths.fileOf(id))).map(([id]) => id);
|
|
3030
|
+
}
|
|
3031
|
+
static #presence(changedFiles) {
|
|
3032
|
+
const caseMoved = changedFiles.some((file) => fs7.existsSync(file) && !CsrDevPaths.isNamed(file));
|
|
3033
|
+
return caseMoved ? (file) => CsrDevPaths.isNamed(file) : (file) => fs7.existsSync(file);
|
|
3034
|
+
}
|
|
3035
|
+
#handBack(reason, generation, context, { first = false } = {}) {
|
|
3036
|
+
this.#state = null;
|
|
3037
|
+
return { kind: "delegate", reason, generation, context, ...first ? { first } : {} };
|
|
3038
|
+
}
|
|
3039
|
+
async#load() {
|
|
3040
|
+
const manifest = await this.#bundler.writer.readJson(CSR_DEV_MANIFEST_FILE);
|
|
3041
|
+
const graph = await this.#bundler.writer.readJson("graph.json");
|
|
3042
|
+
if (!manifest || !graph || graph.version !== 1 || await this.#bundler.writer.isBuilding())
|
|
3043
|
+
return null;
|
|
3044
|
+
return { graph, manifest, code: null };
|
|
3045
|
+
}
|
|
3046
|
+
async#code(state) {
|
|
3047
|
+
if (!this.#resident)
|
|
3048
|
+
return;
|
|
3049
|
+
state.code ??= await this.#bundler.writer.readCode(state.graph);
|
|
3050
|
+
return state.code;
|
|
3051
|
+
}
|
|
3052
|
+
async#changedModules(graph, changedFiles, present) {
|
|
3053
|
+
const { paths } = this.#bundler;
|
|
3054
|
+
const changed = new Set(graph.pending.filter((id) => graph.modules[id]));
|
|
3055
|
+
for (const file of changedFiles) {
|
|
3056
|
+
const id = paths.idOf(file);
|
|
3057
|
+
if (graph.modules[id])
|
|
3058
|
+
changed.add(id);
|
|
3059
|
+
}
|
|
3060
|
+
for (const id of this.#shadowedImporters(graph, changedFiles))
|
|
3061
|
+
changed.add(id);
|
|
3062
|
+
for (const [id, module] of Object.entries(graph.modules)) {
|
|
3063
|
+
if (module.vendor || changed.has(id))
|
|
3064
|
+
continue;
|
|
3065
|
+
const file = paths.fileOf(id);
|
|
3066
|
+
if (CsrDevPaths.mtimeOf(file) === module.mtimeMs)
|
|
3067
|
+
continue;
|
|
3068
|
+
if (await CsrDevPaths.hashOf(file) !== module.hash)
|
|
3069
|
+
changed.add(id);
|
|
3070
|
+
}
|
|
3071
|
+
for (const [id, module] of Object.entries(graph.modules)) {
|
|
3072
|
+
if (changed.has(id))
|
|
3073
|
+
continue;
|
|
3074
|
+
for (const dep of module.deps) {
|
|
3075
|
+
if (graph.modules[dep] || CsrDevPaths.isStub(dep))
|
|
3076
|
+
continue;
|
|
3077
|
+
const file = paths.fileOf(dep);
|
|
3078
|
+
if (present(file) && CsrDevPaths.isNamed(file))
|
|
3079
|
+
changed.add(dep);
|
|
3080
|
+
else if (!module.vendor)
|
|
3081
|
+
changed.add(id);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
return changed;
|
|
3085
|
+
}
|
|
3086
|
+
#shadowedImporters(graph, changedFiles) {
|
|
3087
|
+
const { paths } = this.#bundler;
|
|
3088
|
+
const bases = new Set(changedFiles.map((file) => CsrDevPaths.realpath(file)).filter((file) => !graph.modules[paths.idOf(file)] && CsrDevPaths.isScript(file) && fs7.existsSync(file)).flatMap((file) => {
|
|
3089
|
+
const stem = file.slice(0, file.length - path15.extname(file).length);
|
|
3090
|
+
return path15.basename(stem) === "index" ? [stem, path15.dirname(stem)] : [stem];
|
|
3091
|
+
}));
|
|
3092
|
+
if (bases.size === 0)
|
|
3093
|
+
return [];
|
|
3094
|
+
return Object.entries(graph.resolution).filter(([importer, bySpecifier]) => {
|
|
3095
|
+
if (!graph.modules[importer] || graph.modules[importer]?.vendor)
|
|
3096
|
+
return false;
|
|
3097
|
+
const dir = path15.dirname(paths.fileOf(importer));
|
|
3098
|
+
return Object.keys(bySpecifier).some((specifier) => specifier.startsWith(".") && bases.has(path15.resolve(dir, specifier)));
|
|
3099
|
+
}).map(([importer]) => importer);
|
|
3100
|
+
}
|
|
3101
|
+
#forgetDeletedModules({ graph, code }, present) {
|
|
3102
|
+
const { paths } = this.#bundler;
|
|
3103
|
+
const deleted = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && !present(paths.fileOf(id)));
|
|
3104
|
+
for (const id of deleted) {
|
|
3105
|
+
delete graph.modules[id];
|
|
3106
|
+
code?.modules.delete(id);
|
|
3107
|
+
}
|
|
3108
|
+
return deleted;
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
|
|
3113
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
3114
|
+
import path21 from "path";
|
|
3115
|
+
import { CSR_DEV_DIRNAME, CSR_DEV_ROUTE_PREFIX } from "akanjs/server/hmr/csrDevManifest";
|
|
3116
|
+
|
|
3117
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrEntryFiles.ts
|
|
3118
|
+
import path16 from "path";
|
|
3119
|
+
|
|
3120
|
+
class CsrEntryFiles {
|
|
3121
|
+
static entryFilename(basePath) {
|
|
3122
|
+
return `${basePath || "index"}.csr.tsx`;
|
|
3123
|
+
}
|
|
3124
|
+
static pageEntriesForBasePath(pageEntries, basePath, basePaths) {
|
|
3125
|
+
return pageEntries.filter((entry) => {
|
|
3126
|
+
const entryBasePath = CsrEntryFiles.basePathOfPageKey(entry.key, basePaths);
|
|
3127
|
+
return entryBasePath === null || entryBasePath === basePath;
|
|
3128
|
+
});
|
|
3129
|
+
}
|
|
3130
|
+
static basePathOfPageKey(pageKey, basePaths) {
|
|
3131
|
+
const normalized = pageKey.split(path16.sep).join("/").replace(/^\.\//, "");
|
|
3132
|
+
const segments = normalized.split("/");
|
|
3133
|
+
const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
|
|
3134
|
+
return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// pkgs/@akanjs/devkit/frontendBuild/devRegistryBundler.ts
|
|
3139
|
+
import fs10 from "fs";
|
|
3140
|
+
import path20 from "path";
|
|
3141
|
+
import { isAkanRuntimeMetadataFile } from "akanjs/server/hmr/runtimeMetadataFile";
|
|
3142
|
+
|
|
3143
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
|
|
3144
|
+
import fs8 from "fs";
|
|
3145
|
+
import { mkdir as mkdir2, readdir as readdir2, rm } from "fs/promises";
|
|
3146
|
+
import path17 from "path";
|
|
3147
|
+
import {
|
|
3148
|
+
appGenerationOf as appGenerationOf2,
|
|
3149
|
+
CSR_DEV_APP_FILE,
|
|
3150
|
+
CSR_DEV_KEPT_PATCHES,
|
|
3151
|
+
CSR_DEV_MANIFEST_FILE as CSR_DEV_MANIFEST_FILE2,
|
|
3152
|
+
CSR_DEV_PATCHING_MARKER,
|
|
3153
|
+
csrDevModuleFile
|
|
3154
|
+
} from "akanjs/server/hmr/csrDevManifest";
|
|
3155
|
+
class CsrDevArtifactWriter {
|
|
3156
|
+
static #keptVendors = 3;
|
|
3157
|
+
static #buildingMarker = ".building";
|
|
3158
|
+
#outDir;
|
|
3159
|
+
#routePrefix;
|
|
3160
|
+
#library;
|
|
3161
|
+
constructor(outDir, { routePrefix, library = false }) {
|
|
3162
|
+
this.#outDir = outDir;
|
|
3163
|
+
this.#routePrefix = routePrefix;
|
|
3164
|
+
this.#library = library;
|
|
3165
|
+
}
|
|
3166
|
+
async markBuilding() {
|
|
3167
|
+
await Bun.write(path17.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker), String(process.pid));
|
|
3168
|
+
}
|
|
3169
|
+
async clearBuilding() {
|
|
3170
|
+
await CsrDevArtifactWriter.#remove(path17.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker));
|
|
3171
|
+
}
|
|
3172
|
+
async isBuilding() {
|
|
3173
|
+
return await Bun.file(path17.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker)).exists();
|
|
3174
|
+
}
|
|
3175
|
+
async forgetPatch(generation) {
|
|
3176
|
+
await Promise.all([`patch-${generation}.js`, `patch-${generation}.js.layout.json`].map((name) => CsrDevArtifactWriter.#remove(path17.join(this.#outDir, name))));
|
|
3177
|
+
}
|
|
3178
|
+
async readJson(name) {
|
|
3179
|
+
const file = Bun.file(path17.join(this.#outDir, name));
|
|
3180
|
+
if (!await file.exists())
|
|
3181
|
+
return null;
|
|
3182
|
+
return await file.json().catch(() => null);
|
|
3183
|
+
}
|
|
3184
|
+
async writeJson(name, value) {
|
|
3185
|
+
await this.#writeAtomic(name, JSON.stringify(value));
|
|
3186
|
+
}
|
|
3187
|
+
async writeModules(compiled) {
|
|
3188
|
+
await mkdir2(path17.join(this.#outDir, "modules"), { recursive: true });
|
|
3189
|
+
const helpers = new Map(compiled.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
|
|
3190
|
+
await Promise.all([
|
|
3191
|
+
...compiled.flatMap((module) => [
|
|
3192
|
+
Bun.write(this.#modulePath(module.id, ".js"), module.factory),
|
|
3193
|
+
...module.sourceMap ? [Bun.write(this.#modulePath(module.id, ".js.map"), module.sourceMap)] : []
|
|
3194
|
+
]),
|
|
3195
|
+
...[...helpers.values()].map((shared) => Bun.write(this.#helpersPath(shared.hash), shared.definition))
|
|
3196
|
+
]);
|
|
3197
|
+
}
|
|
3198
|
+
async readCode(graph) {
|
|
3199
|
+
const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && this.hasModule(id));
|
|
3200
|
+
const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))];
|
|
3201
|
+
const [modules, helpers] = await Promise.all([
|
|
3202
|
+
Promise.all(ids.map(async (id) => [id, await Bun.file(this.#modulePath(id, ".js")).text()])),
|
|
3203
|
+
Promise.all(hashes.map(async (hash) => [hash, await Bun.file(this.#helpersPath(hash)).text()]))
|
|
3204
|
+
]);
|
|
3205
|
+
return { modules: new Map(modules), helpers: new Map(helpers) };
|
|
3206
|
+
}
|
|
3207
|
+
hasModule(id) {
|
|
3208
|
+
return fs8.existsSync(this.#modulePath(id, ".js"));
|
|
3209
|
+
}
|
|
3210
|
+
async forgetModules(ids) {
|
|
3211
|
+
await Promise.all(ids.flatMap((id) => [
|
|
3212
|
+
CsrDevArtifactWriter.#remove(this.#modulePath(id, ".js")),
|
|
3213
|
+
CsrDevArtifactWriter.#remove(this.#modulePath(id, ".js.map"))
|
|
3214
|
+
]));
|
|
3215
|
+
}
|
|
3216
|
+
async writeVendor(graph) {
|
|
3217
|
+
const ids = Object.keys(graph.modules).filter((id) => graph.modules[id]?.vendor).sort();
|
|
3218
|
+
const stubs = new Set(Object.values(graph.modules).flatMap((module) => module.deps.filter(CsrDevPaths.isStub)));
|
|
3219
|
+
const lines = [await this.#helperDefinitions(graph, ids), ...await this.#defineLines(ids)];
|
|
3220
|
+
for (const stub of [...stubs].sort()) {
|
|
3221
|
+
if (stub.startsWith(CsrDevPaths.emptyStubPrefix)) {
|
|
3222
|
+
lines.push(`__akan.define(${JSON.stringify(stub)}, function () {});
|
|
3223
|
+
`);
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3226
|
+
const message = `[akan-csr] ${stub.slice(CsrDevPaths.stubPrefix.length)} is a Node built-in the browser does not have`;
|
|
3227
|
+
lines.push(`__akan.define(${JSON.stringify(stub)}, function () {
|
|
3228
|
+
throw new Error(${JSON.stringify(message)});
|
|
3229
|
+
});
|
|
3230
|
+
`);
|
|
3231
|
+
}
|
|
3232
|
+
const code = lines.join("");
|
|
3233
|
+
const vendorFile = `vendor-${Bun.hash(code).toString(36)}.js`;
|
|
3234
|
+
await this.#writeAtomic(vendorFile, code);
|
|
3235
|
+
return vendorFile;
|
|
3236
|
+
}
|
|
3237
|
+
async writeApp(graph, { generation, vendorFile, epoch }, code) {
|
|
3238
|
+
const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor).sort();
|
|
3239
|
+
const lines = await this.#defineLines(ids, code);
|
|
3240
|
+
const blocks = ids.map((id, index) => [id, lines[index] ?? ""]);
|
|
3241
|
+
const start = this.#library ? `__akan.startLibrary(${JSON.stringify({ generation, refresh: graph.refresh, bootstrap: graph.entries[""], vendorFile, epoch })});
|
|
3242
|
+
` : `__akan.start(${JSON.stringify({ generation, refresh: graph.refresh })});
|
|
3243
|
+
`;
|
|
3244
|
+
await this.#writeWithSourceMap(CSR_DEV_APP_FILE, await this.#helperDefinitions(graph, ids, code), blocks, start);
|
|
3245
|
+
}
|
|
3246
|
+
async writePatch(generation, modules) {
|
|
3247
|
+
const patchFile = `patch-${generation}.js`;
|
|
3248
|
+
const blocks = modules.map((module, index) => [module.id, `${JSON.stringify(module.id)}: ${module.factory}${index < modules.length - 1 ? `,
|
|
3249
|
+
` : `
|
|
3250
|
+
`}`]);
|
|
3251
|
+
const helpers = new Map(modules.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
|
|
3252
|
+
const header = `${[...helpers.values()].map((shared) => shared.definition).join("")}__akan.update(${generation}, {
|
|
3253
|
+
`;
|
|
3254
|
+
await this.#writeWithSourceMap(patchFile, header, blocks, `});
|
|
3255
|
+
`);
|
|
3256
|
+
return `${this.#routePrefix}${patchFile}`;
|
|
3257
|
+
}
|
|
3258
|
+
async writeState(graph, manifest) {
|
|
3259
|
+
await this.writeJson("graph.json", graph);
|
|
3260
|
+
await this.writeJson(CSR_DEV_MANIFEST_FILE2, manifest);
|
|
3261
|
+
}
|
|
3262
|
+
async commitPatch(graph, manifest, announce, { modules, code }) {
|
|
3263
|
+
await this.writeJson(CSR_DEV_MANIFEST_FILE2, manifest);
|
|
3264
|
+
announce();
|
|
3265
|
+
await CsrDevArtifactWriter.#appWriteDelay();
|
|
3266
|
+
await this.writeModules(modules);
|
|
3267
|
+
await this.writeJson("graph.json", graph);
|
|
3268
|
+
await this.writeApp(graph, manifest, code);
|
|
3269
|
+
await this.writeJson(CSR_DEV_MANIFEST_FILE2, { ...manifest, appGeneration: manifest.generation });
|
|
3270
|
+
}
|
|
3271
|
+
async healApp(graph, manifest, code) {
|
|
3272
|
+
if (appGenerationOf2(manifest) >= manifest.generation)
|
|
3273
|
+
return manifest;
|
|
3274
|
+
await this.writeApp(graph, manifest, code);
|
|
3275
|
+
const healed = { ...manifest, appGeneration: manifest.generation };
|
|
3276
|
+
await this.writeJson(CSR_DEV_MANIFEST_FILE2, healed);
|
|
3277
|
+
return healed;
|
|
3278
|
+
}
|
|
3279
|
+
async prune(vendorFile, generation) {
|
|
3280
|
+
const names = await readdir2(this.#outDir);
|
|
3281
|
+
const stale = names.filter((name) => {
|
|
3282
|
+
const patch = /^patch-(\d+)\.js(?:\.layout\.json)?$/.exec(name);
|
|
3283
|
+
return patch ? Number(patch[1]) <= generation - CSR_DEV_KEPT_PATCHES : false;
|
|
3284
|
+
});
|
|
3285
|
+
const vendors = names.filter((name) => /^vendor-[\w-]+\.js$/.test(name) && name !== vendorFile).map((name) => ({ name, mtimeMs: CsrDevPaths.mtimeOf(path17.join(this.#outDir, name)) })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(CsrDevArtifactWriter.#keptVendors - 1).map(({ name }) => name);
|
|
3286
|
+
await Promise.all([...stale, ...vendors].map((name) => CsrDevArtifactWriter.#remove(path17.join(this.#outDir, name))));
|
|
3287
|
+
}
|
|
3288
|
+
async pruneAfterFullBuild(graph, vendorFile, { generation, assets }) {
|
|
3289
|
+
const kept = new Set(Object.keys(graph.modules).flatMap((id) => [this.#modulePath(id, ".js"), this.#modulePath(id, ".js.map")]));
|
|
3290
|
+
for (const module of Object.values(graph.modules))
|
|
3291
|
+
if (module.helpers)
|
|
3292
|
+
kept.add(this.#helpersPath(module.helpers));
|
|
3293
|
+
for (const asset of assets)
|
|
3294
|
+
kept.add(path17.resolve(asset));
|
|
3295
|
+
const listed = async (dir) => (await readdir2(path17.join(this.#outDir, dir)).catch(() => [])).map((name) => path17.join(this.#outDir, dir, name));
|
|
3296
|
+
const stale = [
|
|
3297
|
+
...(await listed("modules")).filter((file) => !kept.has(file)),
|
|
3298
|
+
...(await listed("assets")).filter((file) => !kept.has(file)),
|
|
3299
|
+
...(await listed(".")).filter((file) => {
|
|
3300
|
+
const name = path17.basename(file);
|
|
3301
|
+
return /^patch-\d+\.js(?:\.layout\.json)?$/.test(name) || name.endsWith(".tmp");
|
|
3302
|
+
}),
|
|
3303
|
+
path17.join(this.#outDir, CSR_DEV_PATCHING_MARKER)
|
|
3304
|
+
];
|
|
3305
|
+
await Promise.all(stale.map((file) => CsrDevArtifactWriter.#remove(file)));
|
|
3306
|
+
await this.prune(vendorFile, generation);
|
|
3307
|
+
}
|
|
3308
|
+
async#helperDefinitions(graph, ids, code) {
|
|
3309
|
+
const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))].sort();
|
|
3310
|
+
return (await Promise.all(hashes.map(async (hash) => code?.helpers.get(hash) ?? Bun.file(this.#helpersPath(hash)).text()))).join("");
|
|
3311
|
+
}
|
|
3312
|
+
async#defineLines(ids, code) {
|
|
3313
|
+
return await Promise.all(ids.map(async (id) => {
|
|
3314
|
+
const factory = code?.modules.get(id) ?? await Bun.file(this.#modulePath(id, ".js")).text();
|
|
3315
|
+
return `__akan.define(${JSON.stringify(id)}, ${factory});
|
|
3316
|
+
`;
|
|
3317
|
+
}));
|
|
3318
|
+
}
|
|
3319
|
+
async#writeWithSourceMap(name, header, blocks, footer) {
|
|
3320
|
+
const layout = { lineCount: 0, modules: [] };
|
|
3321
|
+
let line = CsrDevPaths.lineCount(header);
|
|
3322
|
+
for (const [id, text] of blocks) {
|
|
3323
|
+
layout.modules.push([id, line + 1]);
|
|
3324
|
+
line += CsrDevPaths.lineCount(text);
|
|
3325
|
+
}
|
|
3326
|
+
const code = `${header}${blocks.map(([, text]) => text).join("")}${footer}//# sourceMappingURL=${name}.map
|
|
3327
|
+
`;
|
|
3328
|
+
layout.lineCount = CsrDevPaths.lineCount(code);
|
|
3329
|
+
await this.writeJson(`${name}.layout.json`, layout);
|
|
3330
|
+
await this.#writeAtomic(name, code);
|
|
3331
|
+
}
|
|
3332
|
+
async#writeAtomic(name, content) {
|
|
3333
|
+
const target = path17.join(this.#outDir, name);
|
|
3334
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
3335
|
+
await Bun.write(temp, content);
|
|
3336
|
+
await FileSys2.replace(temp, target);
|
|
3337
|
+
}
|
|
3338
|
+
static async#remove(file) {
|
|
3339
|
+
await rm(file, { force: true }).catch(() => {
|
|
3340
|
+
return;
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
static async#appWriteDelay() {
|
|
3344
|
+
const ms = Number(process.env.AKAN_CSR_DEV_APP_WRITE_DELAY_MS);
|
|
3345
|
+
if (Number.isInteger(ms) && ms > 0)
|
|
3346
|
+
await Bun.sleep(ms);
|
|
3347
|
+
}
|
|
3348
|
+
#modulePath(id, extension) {
|
|
3349
|
+
return path17.join(this.#outDir, csrDevModuleFile(id, extension));
|
|
3350
|
+
}
|
|
3351
|
+
#helpersPath(hash) {
|
|
3352
|
+
return path17.join(this.#outDir, "modules", `helpers-${hash}.js`);
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
|
|
3357
|
+
import path18 from "path";
|
|
3358
|
+
|
|
3359
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevFactoryCheck.ts
|
|
3360
|
+
class CsrDevFactoryCheck {
|
|
3361
|
+
static #parsed = new Set;
|
|
3362
|
+
static problemOf(factory, helperDefinition = "") {
|
|
3363
|
+
const key = Bun.hash(`${helperDefinition}\x00${factory}`);
|
|
3364
|
+
if (CsrDevFactoryCheck.#parsed.has(key))
|
|
3365
|
+
return null;
|
|
3366
|
+
const problem = CsrDevFactoryCheck.#syntaxError(`${helperDefinition}return (${factory});`);
|
|
3367
|
+
if (problem === null) {
|
|
3368
|
+
CsrDevFactoryCheck.#parsed.add(key);
|
|
3369
|
+
return null;
|
|
3370
|
+
}
|
|
3371
|
+
if (problem.includes("import.meta")) {
|
|
3372
|
+
const line = factory.split(`
|
|
3373
|
+
`).findIndex((text) => /(?<![\w$.])import\.meta\b/.test(text));
|
|
3374
|
+
return `import.meta is left in it${line > 0 ? ` (compiled line ${line})` : ""}, which a classic script cannot hold: ${problem}`;
|
|
3375
|
+
}
|
|
3376
|
+
if (CsrDevFactoryCheck.#syntaxError(`${helperDefinition}return (async ${factory});`) === null)
|
|
3377
|
+
return `a top-level await is left in it, which a CommonJS factory cannot wait on: ${problem}`;
|
|
3378
|
+
return problem;
|
|
3379
|
+
}
|
|
3380
|
+
static thrower(id, problem) {
|
|
3381
|
+
const message = `[akan-csr] ${id} was left out of the dev registry: ${problem}`;
|
|
3382
|
+
return `function () {
|
|
3383
|
+
throw new Error(${JSON.stringify(message)});
|
|
3384
|
+
}`;
|
|
3385
|
+
}
|
|
3386
|
+
static #syntaxError(source) {
|
|
3387
|
+
try {
|
|
3388
|
+
new Function(source);
|
|
3389
|
+
return null;
|
|
3390
|
+
} catch (error) {
|
|
3391
|
+
return error instanceof Error ? error.message : String(error);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
|
|
3397
|
+
class CsrDevModuleCompiler {
|
|
3398
|
+
static #storeRoot = /(?:^|\/)lib\/st\.ts$/;
|
|
3399
|
+
static #macroImport = /\b(?:with|assert)\s*\{\s*type\s*:\s*["']macro["']/;
|
|
3400
|
+
static #factoryArgument = /(?<![\w$.])(?:require|module|exports|__akanImport|\$RefreshReg\$|\$RefreshSig\$)(?![\w$]|\s*:)/;
|
|
3401
|
+
static #helperPatches = [
|
|
3402
|
+
["__toESM", "(mod, isNodeMode, target)", "toESM"],
|
|
3403
|
+
["__reExport", "(target, mod, secondTarget)", "reExport"]
|
|
3404
|
+
];
|
|
3405
|
+
#app;
|
|
3406
|
+
#paths;
|
|
3407
|
+
#resolver;
|
|
3408
|
+
#context;
|
|
3409
|
+
#outDir;
|
|
3410
|
+
#routePrefix;
|
|
3411
|
+
#assets = new Set;
|
|
3412
|
+
constructor({ app, paths, resolver, context, outDir, routePrefix }) {
|
|
3413
|
+
this.#app = app;
|
|
3414
|
+
this.#paths = paths;
|
|
3415
|
+
this.#resolver = resolver;
|
|
3416
|
+
this.#context = context;
|
|
3417
|
+
this.#outDir = outDir;
|
|
3418
|
+
this.#routePrefix = routePrefix;
|
|
3419
|
+
}
|
|
3420
|
+
get assets() {
|
|
3421
|
+
return [...this.#assets];
|
|
3422
|
+
}
|
|
3423
|
+
async compile(startFiles, known, { refuseNewVendor = false, refusePrepass = false } = {}) {
|
|
3424
|
+
const compiled = new Map;
|
|
3425
|
+
const seen = new Set([...known, ...startFiles]);
|
|
3426
|
+
let frontier = [...new Set(startFiles)];
|
|
3427
|
+
while (frontier.length > 0) {
|
|
3428
|
+
const refusedVendors = refuseNewVendor ? frontier.filter((file) => CsrDevPaths.isVendorFile(file)) : [];
|
|
3429
|
+
if (refusedVendors.length > 0)
|
|
3430
|
+
return { modules: [...compiled.values()], refusedVendors, unresolved: [] };
|
|
3431
|
+
await this.#resolver.cover(frontier);
|
|
3432
|
+
const round = await this.#compileRound(frontier);
|
|
3433
|
+
if (round.misses.length > 0 && !this.#resolver.prepassDone && refusePrepass)
|
|
3434
|
+
return { modules: [...compiled.values()], refusedVendors: [], unresolved: round.misses };
|
|
3435
|
+
if (round.misses.length > 0 && !this.#resolver.prepassDone) {
|
|
3436
|
+
this.#app.verbose(`[csr-dev] ${round.misses.length} unresolved import(s); rerunning the resolution build`);
|
|
3437
|
+
await this.#resolver.prepass();
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
const next = [];
|
|
3441
|
+
for (const module of round.modules) {
|
|
3442
|
+
compiled.set(module.file, module);
|
|
3443
|
+
for (const dep of module.deps) {
|
|
3444
|
+
if (CsrDevPaths.isStub(dep) || seen.has(dep))
|
|
3445
|
+
continue;
|
|
3446
|
+
seen.add(dep);
|
|
3447
|
+
next.push(dep);
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
frontier = next;
|
|
3451
|
+
}
|
|
3452
|
+
const { fallbacks } = this.#resolver;
|
|
3453
|
+
if (fallbacks.size > 0)
|
|
3454
|
+
this.#app.verbose(`[csr-dev] ${fallbacks.size} import(s) outside the browser build's graph resolved with Bun's runtime resolver: ${[...fallbacks].slice(0, 5).join(", ")}${fallbacks.size > 5 ? ", ..." : ""}`);
|
|
3455
|
+
return { modules: [...compiled.values()], refusedVendors: [], unresolved: [] };
|
|
3456
|
+
}
|
|
3457
|
+
async#compileRound(files) {
|
|
3458
|
+
const appFiles = files.filter((file) => !CsrDevPaths.isVendorFile(file));
|
|
3459
|
+
const vendorFiles = files.filter((file) => CsrDevPaths.isVendorFile(file));
|
|
3460
|
+
const rounds = await Promise.all([
|
|
3461
|
+
appFiles.length > 0 ? this.#bunBuild(appFiles, false) : null,
|
|
3462
|
+
vendorFiles.length > 0 ? this.#bunBuild(vendorFiles, true) : null
|
|
3463
|
+
]);
|
|
3464
|
+
const modules = rounds.flatMap((round) => round?.modules ?? []);
|
|
3465
|
+
const misses = rounds.flatMap((round) => round?.misses ?? []);
|
|
3466
|
+
for (const file of rounds.flatMap((round) => round?.tangled ?? [])) {
|
|
3467
|
+
const single = await this.#bunBuild([file], CsrDevPaths.isVendorFile(file));
|
|
3468
|
+
if (single.tangled.length > 0)
|
|
3469
|
+
throw new Error(`[csr-dev] ${this.#paths.idOf(file)} kept inlining another module`);
|
|
3470
|
+
modules.push(...single.modules);
|
|
3471
|
+
misses.push(...single.misses);
|
|
3472
|
+
}
|
|
3473
|
+
return { modules, misses };
|
|
3474
|
+
}
|
|
3475
|
+
async#bunBuild(files, vendor) {
|
|
3476
|
+
const mtimes = new Map(files.map((file) => [CsrDevPaths.realpath(file), CsrDevPaths.mtimeOf(file)]));
|
|
3477
|
+
const hashes = new Map(await Promise.all(files.map(async (file) => [CsrDevPaths.realpath(file), vendor ? "" : await CsrDevPaths.hashOf(file)])));
|
|
3478
|
+
const misses = [];
|
|
3479
|
+
const macroOutputs = vendor ? new Map : await this.#evaluateMacros(files);
|
|
3480
|
+
let result;
|
|
3481
|
+
try {
|
|
3482
|
+
result = await Bun.build({
|
|
3483
|
+
entrypoints: files,
|
|
3484
|
+
root: this.#paths.root,
|
|
3485
|
+
target: "browser",
|
|
3486
|
+
format: "cjs",
|
|
3487
|
+
reactFastRefresh: !vendor,
|
|
3488
|
+
sourcemap: vendor ? "none" : "external",
|
|
3489
|
+
naming: { entry: "[dir]/[name].[ext]", asset: "assets/[name]-[hash].[ext]" },
|
|
3490
|
+
publicPath: this.#routePrefix,
|
|
3491
|
+
metafile: true,
|
|
3492
|
+
env: "AKAN_PUBLIC_*",
|
|
3493
|
+
define: { ...this.#context.define, "import.meta": "__akanMeta" },
|
|
3494
|
+
optimizeImports: this.#context.optimizeImports,
|
|
3495
|
+
plugins: [this.#registryPlugin(vendor, misses, macroOutputs)]
|
|
3496
|
+
});
|
|
3497
|
+
} catch (error) {
|
|
3498
|
+
if (vendor && error instanceof AggregateError)
|
|
3499
|
+
return await this.#withoutFailed(files, error);
|
|
3500
|
+
throw error;
|
|
3501
|
+
}
|
|
3502
|
+
const { outputs } = result.metafile;
|
|
3503
|
+
const sourceMaps = new Map(result.outputs.filter((artifact) => artifact.kind === "sourcemap").map((artifact) => [artifact.path, artifact]));
|
|
3504
|
+
const modules = [];
|
|
3505
|
+
const tangled = [];
|
|
3506
|
+
for (const artifact of result.outputs) {
|
|
3507
|
+
if (artifact.kind === "asset") {
|
|
3508
|
+
const assetPath = path18.join(this.#outDir, artifact.path);
|
|
3509
|
+
await Bun.write(assetPath, artifact);
|
|
3510
|
+
this.#assets.add(assetPath);
|
|
3511
|
+
continue;
|
|
3512
|
+
}
|
|
3513
|
+
if (artifact.kind !== "entry-point")
|
|
3514
|
+
continue;
|
|
3515
|
+
const meta = outputs[artifact.path];
|
|
3516
|
+
if (!meta?.entryPoint)
|
|
3517
|
+
throw new Error(`[csr-dev] no entry point recorded for ${artifact.path}`);
|
|
3518
|
+
const file = CsrDevPaths.realpath(path18.resolve(meta.entryPoint));
|
|
3519
|
+
const inputs = Object.keys(meta.inputs).map((input) => CsrDevPaths.realpath(path18.resolve(input)));
|
|
3520
|
+
if (inputs.some((input) => input !== file && this.#resolver.isRegistryModule(input))) {
|
|
3521
|
+
tangled.push(file);
|
|
3522
|
+
continue;
|
|
3523
|
+
}
|
|
3524
|
+
const code = await artifact.text();
|
|
3525
|
+
const id = this.#paths.idOf(file);
|
|
3526
|
+
const built = CsrDevModuleCompiler.#factory(id, code);
|
|
3527
|
+
const problem = CsrDevFactoryCheck.problemOf(built.factory, built.helpers?.definition);
|
|
3528
|
+
if (problem && !vendor)
|
|
3529
|
+
throw new Error(`[csr-dev] ${id}: its factory does not parse as a classic script: ${problem}`);
|
|
3530
|
+
if (problem) {
|
|
3531
|
+
modules.push(this.#leftOut(file, problem));
|
|
3532
|
+
continue;
|
|
3533
|
+
}
|
|
3534
|
+
const sourceMap = sourceMaps.get(`${artifact.path}.map`);
|
|
3535
|
+
modules.push({
|
|
3536
|
+
id,
|
|
3537
|
+
file,
|
|
3538
|
+
vendor,
|
|
3539
|
+
...built,
|
|
3540
|
+
sourceMap: sourceMap ? this.#rebaseSourceMap(await sourceMap.text()) : undefined,
|
|
3541
|
+
deps: this.#emittedDeps(code),
|
|
3542
|
+
mtimeMs: mtimes.get(file) ?? CsrDevPaths.mtimeOf(file),
|
|
3543
|
+
hash: hashes.get(file) ?? ""
|
|
3544
|
+
});
|
|
3545
|
+
}
|
|
3546
|
+
return { modules, misses, tangled };
|
|
3547
|
+
}
|
|
3548
|
+
async#withoutFailed(files, error) {
|
|
3549
|
+
const reasons = new Map;
|
|
3550
|
+
for (const message of error.errors) {
|
|
3551
|
+
const file = message.position?.file;
|
|
3552
|
+
if (typeof file !== "string" || !path18.isAbsolute(file))
|
|
3553
|
+
continue;
|
|
3554
|
+
const real = CsrDevPaths.realpath(file);
|
|
3555
|
+
const at = message.position?.line ? `:${message.position.line}` : "";
|
|
3556
|
+
reasons.set(real, [...reasons.get(real) ?? [], `${String(message.message)} (${this.#paths.idOf(real)}${at})`]);
|
|
3557
|
+
}
|
|
3558
|
+
const failed = new Set(files.filter((file) => reasons.has(CsrDevPaths.realpath(file))));
|
|
3559
|
+
if (failed.size === 0)
|
|
3560
|
+
throw error;
|
|
3561
|
+
const rest = files.filter((file) => !failed.has(file));
|
|
3562
|
+
const round = rest.length > 0 ? await this.#bunBuild(rest, true) : { modules: [], misses: [], tangled: [] };
|
|
3563
|
+
for (const file of failed) {
|
|
3564
|
+
const text = (reasons.get(CsrDevPaths.realpath(file)) ?? []).join("; ");
|
|
3565
|
+
const awaits = text.includes('"await" can only be used');
|
|
3566
|
+
round.modules.push(this.#leftOut(file, awaits ? `a top-level await, which a CommonJS factory cannot wait on: ${text}` : text));
|
|
3567
|
+
}
|
|
3568
|
+
return round;
|
|
3569
|
+
}
|
|
3570
|
+
#leftOut(file, problem) {
|
|
3571
|
+
const real = CsrDevPaths.realpath(file);
|
|
3572
|
+
const id = this.#paths.idOf(real);
|
|
3573
|
+
this.#app.logger.error(`[csr-dev] ${id} is left out of the dev registry: ${problem}`);
|
|
3574
|
+
return {
|
|
3575
|
+
id,
|
|
3576
|
+
file: real,
|
|
3577
|
+
vendor: true,
|
|
3578
|
+
factory: CsrDevFactoryCheck.thrower(id, problem),
|
|
3579
|
+
helpers: null,
|
|
3580
|
+
deps: [],
|
|
3581
|
+
mtimeMs: CsrDevPaths.mtimeOf(real),
|
|
3582
|
+
hash: ""
|
|
3583
|
+
};
|
|
3584
|
+
}
|
|
3585
|
+
async#evaluateMacros(files) {
|
|
3586
|
+
const sources = await Promise.all(files.map(async (file) => [file, await Bun.file(file).text()]));
|
|
3587
|
+
const importers = sources.filter(([, source]) => CsrDevModuleCompiler.#macroImport.test(source));
|
|
3588
|
+
const outputs = await Promise.all(importers.map(async ([file]) => {
|
|
3589
|
+
const result = await Bun.build({
|
|
3590
|
+
entrypoints: [file],
|
|
3591
|
+
target: "browser",
|
|
3592
|
+
format: "esm",
|
|
3593
|
+
plugins: [
|
|
3594
|
+
{
|
|
3595
|
+
name: "akan-csr-macro-pass",
|
|
3596
|
+
setup: (build) => void build.onResolve({ filter: /.*/ }, (args) => args.importer ? { path: args.path, external: true } : undefined)
|
|
3597
|
+
}
|
|
3598
|
+
]
|
|
3599
|
+
});
|
|
3600
|
+
const [output] = result.outputs;
|
|
3601
|
+
if (!output)
|
|
3602
|
+
throw new Error(`[csr-dev] ${this.#paths.idOf(file)}: its macro pass wrote no output`);
|
|
3603
|
+
const text = await output.text();
|
|
3604
|
+
return [
|
|
3605
|
+
[file, text],
|
|
3606
|
+
[CsrDevPaths.realpath(file), text]
|
|
3607
|
+
];
|
|
3608
|
+
}));
|
|
3609
|
+
return new Map(outputs.flat());
|
|
3610
|
+
}
|
|
3611
|
+
#registryPlugin(vendor, misses, macroOutputs) {
|
|
3612
|
+
return {
|
|
3613
|
+
name: "akan-csr-registry",
|
|
3614
|
+
setup: (build) => {
|
|
3615
|
+
build.onLoad({ filter: /\.css$/ }, () => ({ contents: "", loader: "js" }));
|
|
3616
|
+
if (macroOutputs.size > 0) {
|
|
3617
|
+
const exact = [...macroOutputs.keys()].map((file) => file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
3618
|
+
build.onLoad({ filter: new RegExp(`^(?:${exact.join("|")})$`) }, (args) => {
|
|
3619
|
+
const contents = macroOutputs.get(args.path);
|
|
3620
|
+
return contents === undefined ? undefined : { contents, loader: "js" };
|
|
3621
|
+
});
|
|
3622
|
+
}
|
|
3623
|
+
if (!vendor)
|
|
3624
|
+
build.onLoad({ filter: /\.tsx$/ }, async (args) => {
|
|
3625
|
+
if (CsrDevPaths.isVendorFile(args.path))
|
|
3626
|
+
return;
|
|
3627
|
+
const normalized = RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh(await Bun.file(args.path).text(), { path: args.path });
|
|
3628
|
+
return normalized ? { contents: normalized, loader: "tsx" } : undefined;
|
|
3629
|
+
});
|
|
3630
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
3631
|
+
if (args.kind === "entry-point-build" || !args.importer)
|
|
3632
|
+
return;
|
|
3633
|
+
if (this.#context.externals?.includes(args.path))
|
|
3634
|
+
return { path: `${CsrDevPaths.vendorPrefix}${args.path}`, external: true };
|
|
3635
|
+
const importer = CsrDevPaths.realpath(args.importer);
|
|
3636
|
+
const target = this.#resolver.resolve(importer, args.path, args.kind === "require-call" || args.kind === "require-resolve" ? "require" : "import");
|
|
3637
|
+
if (target === CsrDevResolver.inline)
|
|
3638
|
+
return;
|
|
3639
|
+
if (target === null) {
|
|
3640
|
+
misses.push(`${args.path} from ${this.#paths.idOf(importer)}`);
|
|
3641
|
+
return { path: `akan-miss:${args.path}`, external: true };
|
|
3642
|
+
}
|
|
3643
|
+
const external = CsrDevPaths.isStub(target) ? target : `${CsrDevPaths.modulePrefix}${this.#paths.idOf(target)}`;
|
|
3644
|
+
return { path: external, external: true };
|
|
3645
|
+
});
|
|
3646
|
+
}
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3649
|
+
#rebaseSourceMap(text) {
|
|
3650
|
+
const map = JSON.parse(text);
|
|
3651
|
+
const files = map.sources.map((source) => path18.resolve(source));
|
|
3652
|
+
map.sources = files.map((file) => `/${this.#paths.idOf(file)}`);
|
|
3653
|
+
map.sourcesContent = files.map((file, index) => CsrDevPaths.isVendorFile(file) ? null : map.sourcesContent?.[index] ?? null);
|
|
3654
|
+
return JSON.stringify(map);
|
|
3655
|
+
}
|
|
3656
|
+
#emittedDeps(code) {
|
|
3657
|
+
const deps = new Set;
|
|
3658
|
+
const references = /\b(?:require|import|__akanImport)\("((?:akan-module|stub):[^"]+)"\)/g;
|
|
3659
|
+
for (const [, reference] of code.matchAll(references)) {
|
|
3660
|
+
if (!reference)
|
|
3661
|
+
continue;
|
|
3662
|
+
deps.add(CsrDevPaths.isStub(reference) ? reference : this.#paths.fileOf(reference.slice(CsrDevPaths.modulePrefix.length)));
|
|
3663
|
+
}
|
|
3664
|
+
return [...deps];
|
|
3665
|
+
}
|
|
3666
|
+
static #factory(id, code) {
|
|
3667
|
+
const prefix = CsrDevPaths.modulePrefix;
|
|
3668
|
+
let patched = code.replace(/^#!/, "//").replaceAll(`import("${prefix}`, `__akanImport("${prefix}`).replace(/^\/\/# debugId=.*$/m, "");
|
|
3669
|
+
const marker = patched.search(/^\/\/ /m);
|
|
3670
|
+
const body = marker < 0 ? "" : patched.slice(marker);
|
|
3671
|
+
for (const [name, params, method] of CsrDevModuleCompiler.#helperPatches) {
|
|
3672
|
+
const definition = `var ${name} = ${params} => {`;
|
|
3673
|
+
if (patched.includes(definition))
|
|
3674
|
+
patched = patched.replace(definition, `var ${name} = __akan.${method}, __bun${name} = ${params} => {`);
|
|
3675
|
+
else if (patched.includes(`${name}(`) && !new RegExp(`\\b(?:var|let|const|function)\\s+${name}\\b`).test(body))
|
|
3676
|
+
throw new Error(`[csr-dev] ${id}: Bun's ${name} helper no longer reads "${definition}"; CsrDevModuleCompiler must follow`);
|
|
3677
|
+
}
|
|
3678
|
+
if (CsrDevModuleCompiler.#storeRoot.test(id))
|
|
3679
|
+
patched += `
|
|
3680
|
+
module.hot.accept();`;
|
|
3681
|
+
const shared = CsrDevModuleCompiler.#shareHelpers(patched);
|
|
3682
|
+
const meta = /(?<![\w$.])__akanMeta(?![\w$])/.test(patched) ? ` var __akanMeta = { url: new URL(${JSON.stringify(id.startsWith("/") ? id : `/${id}`)}, self.location.href).href };` : "";
|
|
3683
|
+
return {
|
|
3684
|
+
factory: `function (require, module, exports, $RefreshReg$, $RefreshSig$, __akanImport) {${meta}
|
|
3685
|
+
${shared?.code ?? patched}
|
|
3686
|
+
}`,
|
|
3687
|
+
helpers: shared?.helpers ?? null
|
|
3688
|
+
};
|
|
3689
|
+
}
|
|
3690
|
+
static #shareHelpers(code) {
|
|
3691
|
+
const marker = code.search(/^\/\/ /m);
|
|
3692
|
+
if (marker <= 0)
|
|
3693
|
+
return null;
|
|
3694
|
+
const preamble = code.slice(0, marker);
|
|
3695
|
+
const names = [...preamble.matchAll(/^(?:var|let|const|function)\s+([\w$]+)/gm)].map(([, name]) => name).filter((name) => !!name);
|
|
3696
|
+
if (names.length === 0 || CsrDevModuleCompiler.#factoryArgument.test(preamble))
|
|
3697
|
+
return null;
|
|
3698
|
+
const hash = Bun.hash(preamble).toString(36);
|
|
3699
|
+
const binding = `var { ${names.join(", ")} } = __akan.helpers(${JSON.stringify(hash)});`;
|
|
3700
|
+
return {
|
|
3701
|
+
code: `${binding}${`
|
|
3702
|
+
`.repeat(CsrDevPaths.lineCount(preamble))}${code.slice(marker)}`,
|
|
3703
|
+
helpers: {
|
|
3704
|
+
hash,
|
|
3705
|
+
definition: `__akan.defineHelpers(${JSON.stringify(hash)}, function () {
|
|
3706
|
+
${preamble}return { ${names.join(", ")} };
|
|
3707
|
+
});
|
|
3708
|
+
`
|
|
3709
|
+
}
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
// pkgs/@akanjs/devkit/frontendBuild/registryMetadataFingerprint.ts
|
|
3715
|
+
import fs9 from "fs";
|
|
3716
|
+
import path19 from "path";
|
|
3717
|
+
|
|
3718
|
+
class RegistryMetadataFingerprint {
|
|
3719
|
+
static #sources = new Bun.Glob("**/*.{signal,dictionary}.ts");
|
|
3720
|
+
static #libFiles = ["sig.ts", "dict.ts", "useClient.ts"];
|
|
3721
|
+
static async of(roots, isMetadataFile) {
|
|
3722
|
+
const stamps = [];
|
|
3723
|
+
for (const root of new Set(roots)) {
|
|
3724
|
+
const libDir = path19.join(root, "lib");
|
|
3725
|
+
if (!fs9.existsSync(libDir))
|
|
3726
|
+
continue;
|
|
3727
|
+
const files = RegistryMetadataFingerprint.#libFiles.map((name) => path19.join(libDir, name));
|
|
3728
|
+
for await (const relative of RegistryMetadataFingerprint.#sources.scan({ cwd: libDir, onlyFiles: true }))
|
|
3729
|
+
files.push(path19.join(libDir, relative));
|
|
3730
|
+
for (const file of files) {
|
|
3731
|
+
if (!isMetadataFile(file))
|
|
3732
|
+
continue;
|
|
3733
|
+
const stat = fs9.statSync(file, { throwIfNoEntry: false });
|
|
3734
|
+
if (stat)
|
|
3735
|
+
stamps.push(`${file}:${stat.mtimeMs}:${stat.size}`);
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
return Bun.hash(stamps.sort((a, b) => a.localeCompare(b)).join(`
|
|
3739
|
+
`)).toString(36);
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
// pkgs/@akanjs/devkit/frontendBuild/devRegistryBundler.ts
|
|
3744
|
+
class DevRegistryBundler {
|
|
3745
|
+
outDir;
|
|
3746
|
+
routePrefix;
|
|
3747
|
+
paths;
|
|
3748
|
+
writer;
|
|
3749
|
+
app;
|
|
3750
|
+
entryDir;
|
|
3751
|
+
constructor(app, { dirName, routePrefix, library }) {
|
|
3752
|
+
this.app = app;
|
|
3753
|
+
this.routePrefix = routePrefix;
|
|
3754
|
+
this.paths = new CsrDevPaths(app.workspace.workspaceRoot);
|
|
3755
|
+
this.outDir = path20.join(app.cwdPath, ".akan/artifact", dirName);
|
|
3756
|
+
this.entryDir = path20.join(app.cwdPath, ".akan/generated", dirName);
|
|
3757
|
+
this.writer = new CsrDevArtifactWriter(this.outDir, { routePrefix, library });
|
|
3758
|
+
}
|
|
3759
|
+
async update(changedFiles = [], options = {}) {
|
|
3760
|
+
const result = await new CsrDevPatcher(this).update(changedFiles, { ...options, allowWholeAppBuilds: true });
|
|
3761
|
+
if (result.kind === "unchanged")
|
|
3762
|
+
return null;
|
|
3763
|
+
if (result.kind === "update")
|
|
3764
|
+
return result.update;
|
|
3765
|
+
const built = await this.fullBuild(result.context, result.generation, result.reason);
|
|
3766
|
+
const update = result.first ? { ...built, first: true } : built;
|
|
3767
|
+
options.announce?.(update);
|
|
3768
|
+
return update;
|
|
3769
|
+
}
|
|
3770
|
+
async wantedRoots(files, graph) {
|
|
3771
|
+
const reached = await this.reachable(graph);
|
|
3772
|
+
return new Set(files.filter((file) => reached.has(this.paths.idOf(file))));
|
|
3773
|
+
}
|
|
3774
|
+
async reachable(graph) {
|
|
3775
|
+
const roots = await this.rootFiles(Object.values(graph.entries).map((id) => this.paths.fileOf(id)));
|
|
3776
|
+
const reached = new Set;
|
|
3777
|
+
const stack = [graph.refresh, ...roots.map((file) => this.paths.idOf(file))];
|
|
3778
|
+
for (let id = stack.pop();id !== undefined; id = stack.pop()) {
|
|
3779
|
+
if (reached.has(id))
|
|
3780
|
+
continue;
|
|
3781
|
+
reached.add(id);
|
|
3782
|
+
stack.push(...graph.modules[id]?.deps ?? []);
|
|
3783
|
+
}
|
|
3784
|
+
return reached;
|
|
3785
|
+
}
|
|
3786
|
+
async holds(files) {
|
|
3787
|
+
const graph = await this.writer.readJson("graph.json");
|
|
3788
|
+
return graph !== null && files.every((file) => graph.modules[this.paths.idOf(file)] !== undefined);
|
|
3789
|
+
}
|
|
3790
|
+
isMetadataFile(file) {
|
|
3791
|
+
return isAkanRuntimeMetadataFile(file);
|
|
3792
|
+
}
|
|
3793
|
+
async metadataFingerprint() {
|
|
3794
|
+
const libsContainer = path20.resolve(this.app.workspace.workspaceRoot, "libs");
|
|
3795
|
+
const roots = await new WatchRootResolver(this.app).resolve();
|
|
3796
|
+
const libRoots = roots.includes(libsContainer) ? fs10.readdirSync(libsContainer).map((name) => path20.join(libsContainer, name)) : roots.filter((root) => path20.dirname(root) === libsContainer);
|
|
3797
|
+
return await RegistryMetadataFingerprint.of([path20.resolve(this.app.cwdPath), ...libRoots], (file) => this.isMetadataFile(file));
|
|
3798
|
+
}
|
|
3799
|
+
async fullBuild(context, generation, reason) {
|
|
3800
|
+
const metadata = await this.metadataFingerprint();
|
|
3801
|
+
const entries = await this.writeEntries(context);
|
|
3802
|
+
const roots = await this.rootFiles(Object.values(entries.files));
|
|
3803
|
+
const resolver = new CsrDevResolver({ paths: this.paths, context, entryFiles: roots });
|
|
3804
|
+
await resolver.prepass();
|
|
3805
|
+
const compiler = this.compiler(context, resolver);
|
|
3806
|
+
const { modules: compiled } = await compiler.compile([...roots, ...context.refreshFile ? [context.refreshFile] : []], new Set);
|
|
3807
|
+
const graph = {
|
|
3808
|
+
version: 1,
|
|
3809
|
+
configKey: context.configKey,
|
|
3810
|
+
refresh: context.refreshFile ? this.paths.idOf(context.refreshFile) : `${CsrDevPaths.vendorPrefix}react-refresh/runtime`,
|
|
3811
|
+
entries: this.entryIds(entries.files),
|
|
3812
|
+
modules: {},
|
|
3813
|
+
resolution: {},
|
|
3814
|
+
pending: [],
|
|
3815
|
+
metadata
|
|
3816
|
+
};
|
|
3817
|
+
this.merge(graph, resolver, compiled);
|
|
3818
|
+
await this.writer.markBuilding();
|
|
3819
|
+
await this.writer.writeModules(compiled);
|
|
3820
|
+
const vendorFile = await this.writer.writeVendor(graph);
|
|
3821
|
+
const epoch = Date.now();
|
|
3822
|
+
await this.writer.writeApp(graph, { generation, vendorFile, epoch });
|
|
3823
|
+
await this.writer.writeState(graph, { version: 1, generation, vendorFile, entries: graph.entries, epoch });
|
|
3824
|
+
await this.writer.pruneAfterFullBuild(graph, vendorFile, { generation, assets: compiler.assets });
|
|
3825
|
+
await this.writer.clearBuilding();
|
|
3826
|
+
return { generation, reload: true, reason, changedIds: [], moduleCount: Object.keys(graph.modules).length, epoch };
|
|
3827
|
+
}
|
|
3828
|
+
compiler(context, resolver) {
|
|
3829
|
+
return new CsrDevModuleCompiler({
|
|
3830
|
+
app: this.app,
|
|
3831
|
+
paths: this.paths,
|
|
3832
|
+
resolver,
|
|
3833
|
+
context,
|
|
3834
|
+
outDir: this.outDir,
|
|
3835
|
+
routePrefix: this.routePrefix
|
|
3836
|
+
});
|
|
3837
|
+
}
|
|
3838
|
+
merge(graph, resolver, compiled) {
|
|
3839
|
+
for (const module of compiled)
|
|
3840
|
+
graph.modules[module.id] = {
|
|
3841
|
+
vendor: module.vendor,
|
|
3842
|
+
mtimeMs: module.mtimeMs,
|
|
3843
|
+
hash: module.hash,
|
|
3844
|
+
deps: module.deps.map((dep) => CsrDevPaths.isStub(dep) ? dep : this.paths.idOf(dep)),
|
|
3845
|
+
...module.helpers ? { helpers: module.helpers.hash } : {}
|
|
3846
|
+
};
|
|
3847
|
+
const compiledIds = new Set(compiled.map((module) => module.id));
|
|
3848
|
+
graph.pending = graph.pending.filter((id) => !compiledIds.has(id));
|
|
3849
|
+
graph.resolution = resolver.serialize();
|
|
3850
|
+
graph.runtimeResolved = resolver.serializeRuntimeResolved();
|
|
3851
|
+
}
|
|
3852
|
+
entryIds(files) {
|
|
3853
|
+
return Object.fromEntries(Object.entries(files).map(([key, file]) => [key, this.paths.idOf(file)]));
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
|
|
3858
|
+
class CsrDevBundler extends DevRegistryBundler {
|
|
3859
|
+
static #formatVersion = 4;
|
|
3860
|
+
reloadsOnEntryChange = true;
|
|
3861
|
+
#asyncDefaults = new Map;
|
|
3862
|
+
constructor(app) {
|
|
3863
|
+
super(app, { dirName: CSR_DEV_DIRNAME, routePrefix: CSR_DEV_ROUTE_PREFIX, library: false });
|
|
3864
|
+
}
|
|
3865
|
+
async context() {
|
|
3866
|
+
const akanConfig = await this.app.getConfig();
|
|
3867
|
+
const pageEntries = await resolveSsrPageEntriesForApp(this.app, await this.app.getPageKeys());
|
|
3868
|
+
if (pageEntries.length === 0)
|
|
3869
|
+
return null;
|
|
3870
|
+
const basePaths = [...akanConfig.basePaths];
|
|
3871
|
+
const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
|
|
3872
|
+
const define = bundleDefine(this.app, "start", "csr");
|
|
3873
|
+
const optimizeImports = [...akanConfig.optimizeImports];
|
|
3874
|
+
const configKey = Bun.hash(JSON.stringify([CsrDevBundler.#formatVersion, Bun.version, define, optimizeImports, htmlBasePaths])).toString(36);
|
|
3875
|
+
return {
|
|
3876
|
+
pageEntries,
|
|
3877
|
+
basePaths,
|
|
3878
|
+
htmlBasePaths,
|
|
3879
|
+
define,
|
|
3880
|
+
optimizeImports,
|
|
3881
|
+
configKey,
|
|
3882
|
+
refreshFile: this.#resolveRefreshRuntime()
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
async writeEntries(context) {
|
|
3886
|
+
await mkdir3(this.entryDir, { recursive: true });
|
|
3887
|
+
const files = {};
|
|
3888
|
+
const changed = [];
|
|
3889
|
+
for (const basePath of context.htmlBasePaths) {
|
|
3890
|
+
const file = path21.join(this.entryDir, CsrEntryFiles.entryFilename(basePath));
|
|
3891
|
+
const entryPages = CsrEntryFiles.pageEntriesForBasePath(context.pageEntries, basePath, context.basePaths);
|
|
3892
|
+
const generator = new PagesEntrySourceGenerator(entryPages, {
|
|
3893
|
+
isAsyncDefault: (moduleAbsPath) => this.#isAsyncDefault(moduleAbsPath)
|
|
3894
|
+
});
|
|
3895
|
+
const pages = await generator.generateStatic({ fromDir: this.entryDir });
|
|
3896
|
+
const hot = await generator.generateHotReplace({
|
|
3897
|
+
fromDir: this.entryDir,
|
|
3898
|
+
ownerId: this.paths.idOf(file),
|
|
3899
|
+
moduleIds: entryPages.map(({ moduleAbsPath }) => this.paths.idOf(moduleAbsPath))
|
|
3900
|
+
});
|
|
3901
|
+
const source = `import * as akanWebkit from "akanjs/webkit";
|
|
3902
|
+
${pages}
|
|
3903
|
+
void akanWebkit.bootCsr(pages);
|
|
3904
|
+
${hot}`;
|
|
3905
|
+
const unchanged = await Bun.file(file).exists() && await Bun.file(file).text() === source;
|
|
3906
|
+
if (!unchanged)
|
|
3907
|
+
await Bun.write(file, source);
|
|
3908
|
+
const entryFile = CsrDevPaths.realpath(file);
|
|
3909
|
+
files[basePath] = entryFile;
|
|
3910
|
+
if (!unchanged)
|
|
3911
|
+
changed.push(entryFile);
|
|
3912
|
+
}
|
|
3913
|
+
return { files, changed };
|
|
3914
|
+
}
|
|
3915
|
+
async rootFiles(entryFiles) {
|
|
3916
|
+
return entryFiles;
|
|
3917
|
+
}
|
|
3918
|
+
#isAsyncDefault(moduleAbsPath) {
|
|
3919
|
+
const mtimeMs = CsrDevPaths.mtimeOf(moduleAbsPath);
|
|
3920
|
+
const cached = this.#asyncDefaults.get(moduleAbsPath);
|
|
3921
|
+
if (cached?.mtimeMs === mtimeMs)
|
|
3922
|
+
return cached.detected;
|
|
3923
|
+
const detected = AsyncDefaultExportDetector.detect(moduleAbsPath);
|
|
3924
|
+
this.#asyncDefaults.set(moduleAbsPath, { mtimeMs, detected });
|
|
3925
|
+
return detected;
|
|
3926
|
+
}
|
|
3927
|
+
#resolveRefreshRuntime() {
|
|
3928
|
+
const akanWebkit = CsrDevPaths.tryResolve("akanjs/webkit", this.app.cwdPath);
|
|
3929
|
+
const bases = [this.app.cwdPath, this.paths.root, ...akanWebkit ? [path21.dirname(path21.dirname(akanWebkit))] : []];
|
|
3930
|
+
for (const base of bases) {
|
|
3931
|
+
const resolved = CsrDevPaths.tryResolve("react-refresh/runtime", base);
|
|
3932
|
+
if (resolved)
|
|
3933
|
+
return CsrDevPaths.realpath(resolved);
|
|
3934
|
+
}
|
|
3935
|
+
throw new Error(`[csr-dev] react-refresh/runtime is not resolvable from ${bases.join(", ")}`);
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
// pkgs/@akanjs/devkit/frontendBuild/devChangePlanner.ts
|
|
3940
|
+
import path22 from "path";
|
|
3941
|
+
var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
3942
|
+
var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
3943
|
+
var CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
|
|
3944
|
+
var SHARED_SUFFIXES = [".constant.ts", ".dictionary.ts", ".signal.ts"];
|
|
3945
|
+
var SERVER_SUFFIXES = [".service.ts", ".document.ts"];
|
|
3946
|
+
var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
|
|
3947
|
+
|
|
3948
|
+
class DevChangePlanner {
|
|
3949
|
+
#workspaceRoot;
|
|
3950
|
+
constructor({ workspaceRoot }) {
|
|
3951
|
+
this.#workspaceRoot = path22.resolve(workspaceRoot);
|
|
3952
|
+
}
|
|
3953
|
+
plan({ generation, files, kinds, generatedFiles = [] }) {
|
|
3954
|
+
const fileList = uniqueResolved([...files, ...generatedFiles]);
|
|
3955
|
+
const generatedSet = new Set(generatedFiles.map((file) => path22.resolve(file)));
|
|
3956
|
+
const kindSet = new Set(kinds);
|
|
3957
|
+
const roles = new Set;
|
|
3958
|
+
const actions = new Set;
|
|
3959
|
+
const reasonByFile = {};
|
|
3960
|
+
for (const kind of kindSet) {
|
|
3961
|
+
if (kind === "css") {
|
|
3962
|
+
roles.add("css");
|
|
3963
|
+
actions.add("rebuild-css");
|
|
3964
|
+
}
|
|
3965
|
+
if (kind === "config") {
|
|
3966
|
+
roles.add("config");
|
|
3967
|
+
actions.add("restart-dev-host");
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
for (const file of fileList) {
|
|
3971
|
+
const reasons = new Set;
|
|
3972
|
+
const fileRoles = this.#rolesForFile(file, { isGenerated: generatedSet.has(path22.resolve(file)), reasons });
|
|
3973
|
+
for (const role of fileRoles)
|
|
3974
|
+
roles.add(role);
|
|
3975
|
+
if (reasons.has("runtime-metadata"))
|
|
3976
|
+
actions.add("restart-builder");
|
|
3977
|
+
if (reasons.size > 0)
|
|
3978
|
+
reasonByFile[path22.resolve(file)] = [...reasons].sort();
|
|
3979
|
+
}
|
|
3980
|
+
if (roles.has("barrel"))
|
|
3981
|
+
actions.add("sync-generated");
|
|
3982
|
+
if (roles.has("server") || roles.has("shared"))
|
|
3983
|
+
actions.add("restart-backend");
|
|
3984
|
+
if (roles.has("client") || roles.has("shared"))
|
|
3985
|
+
actions.add("rebuild-client");
|
|
3986
|
+
if (roles.has("css"))
|
|
3987
|
+
actions.add("rebuild-css");
|
|
3988
|
+
return {
|
|
3989
|
+
generation,
|
|
3990
|
+
files: fileList,
|
|
3991
|
+
generatedFiles: uniqueResolved(generatedFiles),
|
|
3992
|
+
roles: [...roles].sort(),
|
|
3993
|
+
actions: [...actions].sort(),
|
|
3994
|
+
reasonByFile
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
3997
|
+
#rolesForFile(file, { isGenerated, reasons }) {
|
|
3998
|
+
const roles = new Set;
|
|
3999
|
+
const abs = path22.resolve(file);
|
|
4000
|
+
const base = path22.basename(abs);
|
|
4001
|
+
const ext = path22.extname(abs).toLowerCase();
|
|
4002
|
+
const isSource = SOURCE_EXTS.has(ext);
|
|
4003
|
+
const rel = path22.relative(this.#workspaceRoot, abs);
|
|
4004
|
+
const parts = rel.split(path22.sep).filter(Boolean);
|
|
4005
|
+
if (CONFIG_BASENAMES.has(base)) {
|
|
4006
|
+
roles.add("config");
|
|
4007
|
+
reasons.add("config-file");
|
|
4008
|
+
}
|
|
4009
|
+
if (ext === ".css") {
|
|
4010
|
+
roles.add("css");
|
|
4011
|
+
reasons.add("css-file");
|
|
4012
|
+
}
|
|
4013
|
+
if (isGenerated || isSource && this.#isBarrelFacetChild(parts)) {
|
|
4014
|
+
roles.add("barrel");
|
|
4015
|
+
reasons.add(isGenerated ? "generated-index" : "barrel-facet-child");
|
|
4016
|
+
}
|
|
4017
|
+
if (isSource && this.#isServerBiased(base, parts)) {
|
|
4018
|
+
roles.add("server");
|
|
4019
|
+
reasons.add("server-path");
|
|
4020
|
+
}
|
|
4021
|
+
if (isSource && this.#isClientBiased(base, parts)) {
|
|
4022
|
+
roles.add("client");
|
|
4023
|
+
reasons.add("client-path");
|
|
4024
|
+
}
|
|
4025
|
+
if (isSource && this.#isSharedBiased(base, parts)) {
|
|
4026
|
+
roles.add("shared");
|
|
4027
|
+
reasons.add("shared-path");
|
|
4028
|
+
}
|
|
4029
|
+
if (isSource && this.#isRuntimeMetadataFile(parts, base)) {
|
|
4030
|
+
reasons.add("runtime-metadata");
|
|
4031
|
+
}
|
|
4032
|
+
if (roles.has("server") && roles.has("client")) {
|
|
4033
|
+
roles.delete("server");
|
|
4034
|
+
roles.delete("client");
|
|
4035
|
+
roles.add("shared");
|
|
4036
|
+
reasons.add("server-client-overlap");
|
|
4037
|
+
}
|
|
4038
|
+
if (roles.size === 0 && SOURCE_EXTS.has(ext) && this.#isWorkspaceSource(rel)) {
|
|
4039
|
+
roles.add("shared");
|
|
4040
|
+
reasons.add("workspace-source-fallback");
|
|
4041
|
+
}
|
|
4042
|
+
return roles;
|
|
4043
|
+
}
|
|
4044
|
+
#isServerBiased(base, parts) {
|
|
4045
|
+
return parts.includes("srvkit") || SERVER_SUFFIXES.some((suffix) => base.endsWith(suffix)) || base === "main.ts" || base === "server.ts";
|
|
4046
|
+
}
|
|
4047
|
+
#isClientBiased(base, parts) {
|
|
4048
|
+
return parts.includes("ui") || parts.includes("webkit") || parts.includes("page") || CLIENT_SUFFIXES.some((suffix) => base.endsWith(suffix));
|
|
4049
|
+
}
|
|
4050
|
+
#isSharedBiased(base, parts) {
|
|
4051
|
+
return parts.includes("common") || SHARED_SUFFIXES.some((suffix) => base.endsWith(suffix)) || RUNTIME_METADATA_BASENAMES.has(base);
|
|
4052
|
+
}
|
|
4053
|
+
#isRuntimeMetadataFile(parts, base) {
|
|
4054
|
+
const parent = parts.at(-2);
|
|
4055
|
+
if (parent === "lib" && RUNTIME_METADATA_BASENAMES.has(base))
|
|
4056
|
+
return true;
|
|
4057
|
+
const libIndex = parts.lastIndexOf("lib");
|
|
4058
|
+
if (libIndex < 0 || parts.length <= libIndex + 1)
|
|
4059
|
+
return false;
|
|
4060
|
+
return base.endsWith(".dictionary.ts") || base.endsWith(".signal.ts");
|
|
4061
|
+
}
|
|
4062
|
+
#isBarrelFacetChild(parts) {
|
|
4063
|
+
if (parts.length < 4)
|
|
4064
|
+
return false;
|
|
4065
|
+
const [scope, , facet, child] = parts;
|
|
4066
|
+
if (scope !== "apps" && scope !== "libs")
|
|
4067
|
+
return false;
|
|
4068
|
+
if (!facet || !BARREL_FACETS.has(facet))
|
|
4069
|
+
return false;
|
|
4070
|
+
if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
|
|
4071
|
+
return false;
|
|
4072
|
+
return true;
|
|
4073
|
+
}
|
|
4074
|
+
#isWorkspaceSource(rel) {
|
|
4075
|
+
if (!rel || rel.startsWith("..") || path22.isAbsolute(rel))
|
|
4076
|
+
return false;
|
|
4077
|
+
const [scope] = rel.split(path22.sep);
|
|
4078
|
+
return scope === "apps" || scope === "libs" || scope === "pkgs";
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
var uniqueResolved = (files) => [...new Set(files.map((file) => path22.resolve(file)))].sort();
|
|
4082
|
+
|
|
4083
|
+
// pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
|
|
4084
|
+
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile2, rm as rm2, stat as stat2 } from "fs/promises";
|
|
4085
|
+
import path23 from "path";
|
|
4086
|
+
import { capitalize } from "akanjs/common";
|
|
4087
|
+
var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
4088
|
+
var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
|
|
4089
|
+
var FACET_EXCLUDED_FILE_RE = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
|
|
4090
|
+
var FACET_PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
4091
|
+
var FACET_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
4092
|
+
var MODULE_UI_TYPES = ["Template", "Unit", "Util", "View", "Zone"];
|
|
4093
|
+
var SERVICE_UI_TYPES = ["Util", "Zone"];
|
|
4094
|
+
var SCALAR_UI_TYPES = ["Template", "Unit"];
|
|
4095
|
+
|
|
4096
|
+
class DevGeneratedIndexSync {
|
|
4097
|
+
#workspaceRoot;
|
|
4098
|
+
constructor({ workspaceRoot }) {
|
|
4099
|
+
this.#workspaceRoot = path23.resolve(workspaceRoot);
|
|
4100
|
+
}
|
|
4101
|
+
async syncForBatch(files) {
|
|
4102
|
+
const indexPaths = new Set;
|
|
4103
|
+
const errors = [];
|
|
4104
|
+
for (const file of files) {
|
|
4105
|
+
const facetIndex = this.#facetIndexFor(file);
|
|
4106
|
+
if (facetIndex)
|
|
4107
|
+
indexPaths.add(facetIndex);
|
|
4108
|
+
const moduleIndex = await this.#moduleIndexForDirectoryEvent(file).catch((err) => {
|
|
4109
|
+
errors.push(`[generated-index] module detection failed for ${file}: ${formatError2(err)}`);
|
|
4110
|
+
return null;
|
|
4111
|
+
});
|
|
4112
|
+
if (moduleIndex)
|
|
4113
|
+
indexPaths.add(moduleIndex);
|
|
4114
|
+
}
|
|
4115
|
+
const changedFiles = [];
|
|
4116
|
+
for (const indexPath of [...indexPaths].sort()) {
|
|
4117
|
+
try {
|
|
4118
|
+
const changed = await this.#syncIndex(indexPath);
|
|
4119
|
+
if (changed)
|
|
4120
|
+
changedFiles.push(indexPath);
|
|
4121
|
+
} catch (err) {
|
|
4122
|
+
errors.push(`[generated-index] sync failed for ${indexPath}: ${formatError2(err)}`);
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
return { changedFiles, errors };
|
|
4126
|
+
}
|
|
4127
|
+
#facetIndexFor(file) {
|
|
4128
|
+
const abs = path23.resolve(file);
|
|
4129
|
+
const rel = path23.relative(this.#workspaceRoot, abs);
|
|
4130
|
+
if (rel.startsWith("..") || path23.isAbsolute(rel))
|
|
4131
|
+
return null;
|
|
4132
|
+
const parts = rel.split(path23.sep).filter(Boolean);
|
|
4133
|
+
if (parts.length < 4)
|
|
4134
|
+
return null;
|
|
4135
|
+
const [scope, project, facet, child] = parts;
|
|
4136
|
+
if (scope !== "apps" && scope !== "libs" || !project || !facet || !BARREL_FACETS2.has(facet))
|
|
4137
|
+
return null;
|
|
4138
|
+
if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
|
|
4139
|
+
return null;
|
|
4140
|
+
return path23.join(this.#workspaceRoot, scope, project, facet, "index.ts");
|
|
4141
|
+
}
|
|
4142
|
+
async#moduleIndexForDirectoryEvent(file) {
|
|
4143
|
+
const abs = path23.resolve(file);
|
|
4144
|
+
const rel = path23.relative(this.#workspaceRoot, abs);
|
|
4145
|
+
if (rel.startsWith("..") || path23.isAbsolute(rel))
|
|
4146
|
+
return null;
|
|
4147
|
+
const parts = rel.split(path23.sep).filter(Boolean);
|
|
4148
|
+
if (parts.length !== 4 && parts.length !== 5)
|
|
4149
|
+
return null;
|
|
4150
|
+
const [scope, project, libSegment, moduleSegment, scalarSegment] = parts;
|
|
4151
|
+
if (scope !== "apps" && scope !== "libs" || !project || libSegment !== "lib")
|
|
4152
|
+
return null;
|
|
4153
|
+
const isExistingDirectory = await stat2(abs).then((s) => s.isDirectory()).catch(() => false);
|
|
4154
|
+
const looksLikeDeletedDirectory = !path23.extname(abs);
|
|
4155
|
+
if (!isExistingDirectory && !looksLikeDeletedDirectory)
|
|
4156
|
+
return null;
|
|
4157
|
+
if (moduleSegment === "__scalar" && scalarSegment) {
|
|
4158
|
+
return path23.join(this.#workspaceRoot, scope, project, "lib", "__scalar", scalarSegment, "index.ts");
|
|
4159
|
+
}
|
|
4160
|
+
if (!moduleSegment || moduleSegment === "__scalar")
|
|
4161
|
+
return null;
|
|
4162
|
+
return path23.join(this.#workspaceRoot, scope, project, "lib", moduleSegment, "index.ts");
|
|
4163
|
+
}
|
|
4164
|
+
async#syncIndex(indexPath) {
|
|
4165
|
+
const dir = path23.dirname(indexPath);
|
|
4166
|
+
const content = await this.#contentForIndex(indexPath);
|
|
4167
|
+
if (content === null) {
|
|
4168
|
+
if (!await exists(indexPath))
|
|
4169
|
+
return false;
|
|
4170
|
+
await rm2(indexPath, { force: true });
|
|
4171
|
+
return true;
|
|
4172
|
+
}
|
|
4173
|
+
const current = await readFile2(indexPath, "utf8").catch(() => null);
|
|
4174
|
+
if (current === content)
|
|
4175
|
+
return false;
|
|
4176
|
+
await mkdir4(dir, { recursive: true });
|
|
4177
|
+
await FileSys2.writeTextAtomic(indexPath, content);
|
|
4178
|
+
return true;
|
|
4179
|
+
}
|
|
4180
|
+
async#contentForIndex(indexPath) {
|
|
4181
|
+
const parts = path23.relative(this.#workspaceRoot, indexPath).split(path23.sep).filter(Boolean);
|
|
4182
|
+
const facet = parts.at(-2);
|
|
4183
|
+
if (facet && BARREL_FACETS2.has(facet))
|
|
4184
|
+
return this.#facetContent(path23.dirname(indexPath));
|
|
4185
|
+
return this.#moduleContent(path23.dirname(indexPath));
|
|
4186
|
+
}
|
|
4187
|
+
async#facetContent(dir) {
|
|
4188
|
+
const nameCasePattern = path23.basename(dir) === "ui" ? FACET_PASCAL_CASE_RE : FACET_CAMEL_CASE_RE;
|
|
4189
|
+
const entries = await readdir3(dir, { withFileTypes: true }).catch(() => []);
|
|
4190
|
+
const exportNames = entries.flatMap((entry) => {
|
|
4191
|
+
const name = entry.name;
|
|
4192
|
+
if (name.startsWith("."))
|
|
4193
|
+
return [];
|
|
4194
|
+
if (entry.isDirectory())
|
|
4195
|
+
return nameCasePattern.test(name) ? [name] : [];
|
|
4196
|
+
if (!entry.isFile())
|
|
4197
|
+
return [];
|
|
4198
|
+
if (!FACET_SOURCE_FILE_RE.test(name) || FACET_EXCLUDED_FILE_RE.test(name))
|
|
4199
|
+
return [];
|
|
4200
|
+
const exportName = name.replace(FACET_SOURCE_FILE_RE, "");
|
|
4201
|
+
return nameCasePattern.test(exportName) ? [exportName] : [];
|
|
4202
|
+
}).sort();
|
|
4203
|
+
if (exportNames.length === 0)
|
|
4204
|
+
return null;
|
|
4205
|
+
return `${exportNames.map((name) => `export * from "./${name}";`).join(`
|
|
4206
|
+
`)}
|
|
4207
|
+
`;
|
|
4208
|
+
}
|
|
4209
|
+
async#moduleContent(dir) {
|
|
4210
|
+
const rel = path23.relative(this.#workspaceRoot, dir);
|
|
4211
|
+
const parts = rel.split(path23.sep).filter(Boolean);
|
|
4212
|
+
const moduleSegment = parts.at(-1);
|
|
4213
|
+
if (!moduleSegment)
|
|
4214
|
+
return null;
|
|
4215
|
+
const isScalar = parts.at(-2) === "__scalar";
|
|
4216
|
+
const rawModel = moduleSegment.startsWith("_") ? moduleSegment.slice(1) : moduleSegment;
|
|
4217
|
+
if (!rawModel)
|
|
4218
|
+
return null;
|
|
4219
|
+
const modelName = capitalize(rawModel);
|
|
4220
|
+
const allowedTypes = isScalar ? SCALAR_UI_TYPES : moduleSegment.startsWith("_") ? SERVICE_UI_TYPES : MODULE_UI_TYPES;
|
|
4221
|
+
const fileTypes = [];
|
|
4222
|
+
for (const type of allowedTypes) {
|
|
4223
|
+
if (await exists(path23.join(dir, `${modelName}.${type}.tsx`)))
|
|
4224
|
+
fileTypes.push(type);
|
|
4225
|
+
}
|
|
4226
|
+
if (fileTypes.length === 0)
|
|
4227
|
+
return null;
|
|
4228
|
+
return `
|
|
4229
|
+
${fileTypes.map((type) => `import * as ${type} from "./${modelName}.${type}";`).join(`
|
|
4230
|
+
`)}
|
|
4231
|
+
|
|
4232
|
+
export const ${modelName} = { ${fileTypes.join(", ")} };`;
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
var exists = async (file) => stat2(file).then(() => true).catch(() => false);
|
|
4236
|
+
var formatError2 = (err) => err instanceof Error ? err.message : String(err);
|
|
4237
|
+
|
|
4238
|
+
// pkgs/@akanjs/devkit/frontendBuild/ssrDevBundler.ts
|
|
4239
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
4240
|
+
import path24 from "path";
|
|
4241
|
+
import { SSR_DEV_DIRNAME, SSR_DEV_ROUTE_PREFIX } from "akanjs/server/hmr/csrDevManifest";
|
|
4242
|
+
class SsrDevBundler extends DevRegistryBundler {
|
|
4243
|
+
static #formatVersion = 2;
|
|
4244
|
+
static bootstrapKey = "";
|
|
4245
|
+
reloadsOnEntryChange = false;
|
|
4246
|
+
#discovery = null;
|
|
4247
|
+
#sharedDiscovery;
|
|
4248
|
+
constructor(app, { discovery } = {}) {
|
|
4249
|
+
super(app, { dirName: SSR_DEV_DIRNAME, routePrefix: SSR_DEV_ROUTE_PREFIX, library: true });
|
|
4250
|
+
this.#sharedDiscovery = discovery ?? null;
|
|
4251
|
+
}
|
|
4252
|
+
async context() {
|
|
4253
|
+
const akanConfig = await this.app.getConfig();
|
|
4254
|
+
const define = bundleDefine(this.app, "start", "ssr");
|
|
4255
|
+
const optimizeImports = [...akanConfig.optimizeImports];
|
|
4256
|
+
const configKey = Bun.hash(JSON.stringify(["ssr", SsrDevBundler.#formatVersion, Bun.version, define, optimizeImports, VENDOR_SPECIFIERS])).toString(36);
|
|
4257
|
+
return {
|
|
4258
|
+
pageEntries: [],
|
|
4259
|
+
basePaths: [...akanConfig.basePaths],
|
|
4260
|
+
htmlBasePaths: [],
|
|
4261
|
+
define,
|
|
4262
|
+
optimizeImports,
|
|
4263
|
+
configKey,
|
|
4264
|
+
refreshFile: null,
|
|
4265
|
+
externals: VENDOR_SPECIFIERS
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4268
|
+
isMetadataFile(file) {
|
|
4269
|
+
return super.isMetadataFile(file) && !file.endsWith(".dictionary.ts") && path24.basename(file) !== "dict.ts";
|
|
4270
|
+
}
|
|
4271
|
+
async writeEntries() {
|
|
4272
|
+
await mkdir5(this.entryDir, { recursive: true });
|
|
4273
|
+
const file = path24.join(this.entryDir, "bootstrap.ts");
|
|
4274
|
+
const hasStore = await Bun.file(path24.join(this.app.cwdPath, "lib", "st.ts")).exists();
|
|
4275
|
+
const source = hasStore ? `import ${JSON.stringify(`@apps/${this.app.name}/client`)};
|
|
4276
|
+
` : `export {};
|
|
4277
|
+
`;
|
|
4278
|
+
const unchanged = await Bun.file(file).exists() && await Bun.file(file).text() === source;
|
|
4279
|
+
if (!unchanged)
|
|
4280
|
+
await Bun.write(file, source);
|
|
4281
|
+
const entryFile = CsrDevPaths.realpath(file);
|
|
4282
|
+
return { files: { [SsrDevBundler.bootstrapKey]: entryFile }, changed: unchanged ? [] : [entryFile] };
|
|
4283
|
+
}
|
|
4284
|
+
async clientEntries() {
|
|
4285
|
+
const pageEntries = await resolveSsrPageEntriesForApp(this.app, await this.app.getPageKeys());
|
|
4286
|
+
const seedIndex = computeRouteSeedIndex(pageEntries);
|
|
4287
|
+
const seeds = [...new Set([...seedIndex.globalLayoutFiles, ...seedIndex.entries.flatMap((entry) => entry.seeds)])];
|
|
4288
|
+
const discovery = this.#sharedDiscovery?.() ?? await this.#ownDiscovery();
|
|
4289
|
+
return (await discovery.discover(seeds)).map((file) => CsrDevPaths.realpath(file));
|
|
4290
|
+
}
|
|
4291
|
+
async#ownDiscovery() {
|
|
4292
|
+
this.#discovery ??= await GraphClientEntryDiscovery.create(this.app);
|
|
4293
|
+
return this.#discovery;
|
|
4294
|
+
}
|
|
4295
|
+
async rootFiles(entryFiles) {
|
|
4296
|
+
return [...entryFiles, ...await this.clientEntries()];
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
export { computeRouteSeedIndex, saveRouteSeedIndex, BarrelAnalyzer, createTsconfigPackageResolver, rewriteBarrelImports, GraphClientEntryDiscovery, bundleDefine, ClientEntriesBundler, VENDOR_SPECIFIERS, RouteClientBuilder, AutoImportSync, CsrEntryFiles, PagesEntrySourceGenerator, ServerGraphFile, PagesBundleBuilder, CsrDevPatcher, CsrDevBundler, DevChangePlanner, DevGeneratedIndexSync, SsrDevBundler };
|