@akanjs/cli 3.1.0-rc.4 → 3.1.0-rc.5
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-ymte1tv8.js → CodeAgent-rkjyxgec.js} +2 -2
- package/{CodeTui-4rhdm1qw.js → CodeTui-h7pyj6xa.js} +1 -1
- package/TunnelShare-661cxz5e.js +14 -0
- package/{agent.command-r5x1mch5.js → agent.command-3qxf4xcj.js} +5 -5
- package/akanCode.js +2 -2
- package/{application.command-3dkr7rdz.js → application.command-m1kcd7jf.js} +12 -11
- package/{applicationBuildRunner-h0j2swx1.js → applicationBuildRunner-dj0xkrep.js} +16 -9
- package/buildBatch.proc.js +7 -7
- package/{cloud.command-4kyxfw0y.js → cloud.command-3jzym8q7.js} +17 -16
- package/cloud.script-r4nskbc6.js +24 -0
- package/{code.command-sjvzt8fc.js → code.command-0ad6gw2a.js} +6 -6
- package/{context.command-j9frz0dz.js → context.command-0qx447mb.js} +17 -16
- package/context.runner-1vsjztjg.js +25 -0
- package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
- package/{guideline.command-1epw6hey.js → guideline.command-cv5bmrx2.js} +2 -2
- package/guidelines/conventions/conventions.instruction.md +450 -1084
- package/guidelines/cssRule/cssRule.instruction.md +16 -1
- package/guidelines/fieldRule/fieldRule.instruction.md +57 -0
- package/guidelines/framework/framework.instruction.md +4 -7
- package/guidelines/mcpRule/mcpRule.instruction.md +11 -8
- package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
- package/guidelines/modelStore/modelStore.instruction.md +9 -0
- package/guidelines/pageRule/pageRule.instruction.md +80 -0
- package/guidelines/queryRule/queryRule.instruction.md +35 -8
- package/guidelines/runtimeRule/runtimeRule.instruction.md +89 -2
- package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
- package/guidelines/transportRule/transportRule.instruction.md +76 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +49 -4
- package/incrementalBuilder.proc.js +4 -4
- package/{index-jw6st2rd.js → index-192ha4xh.js} +356 -307
- package/{index-1hkr7mrb.js → index-1p98exm2.js} +2 -2
- package/{index-8byzcvxp.js → index-1xj2t7cb.js} +7 -7
- package/{index-er3hekv9.js → index-20bxxyvn.js} +80 -80
- package/{index-xe3x2kw5.js → index-35hc95h5.js} +1 -1
- package/{index-p5nrc5he.js → index-4mhy6qme.js} +4 -4
- package/{index-0qc5nd61.js → index-5edzv8fn.js} +5 -5
- package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
- package/index-62md4dwy.js +260 -0
- package/{index-z3v7abrk.js → index-665zp9rs.js} +38 -15
- package/{index-1t2xw0p0.js → index-6xn2n442.js} +4 -4
- package/{index-jg3fj8h0.js → index-86jt2a0e.js} +1 -1
- package/{index-k33me5sp.js → index-8fp3b47h.js} +419 -418
- package/{index-shastb7m.js → index-9z426msb.js} +17 -12
- package/{index-gdybjw3x.js → index-awbdfcf2.js} +4 -4
- package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
- package/{index-jppthsff.js → index-cpb3nvma.js} +57 -58
- package/{index-b60ccq2s.js → index-d47ypebv.js} +17 -15
- package/{index-gc05vqd2.js → index-d72hfmdm.js} +13 -13
- package/{index-akha197v.js → index-fmzcs8v6.js} +4 -4
- package/{index-28ftaz3d.js → index-fyq1z6kt.js} +29 -27
- package/{index-vwmv64ad.js → index-g1mbppa9.js} +238 -236
- package/{index-hedt951c.js → index-h47cf1gg.js} +7 -7
- package/{index-hfqs9jvc.js → index-j4mkhz93.js} +3 -3
- package/{index-08a5ga6n.js → index-jw1zm273.js} +28 -30
- package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
- package/{index-8935h3n1.js → index-mdjc93e1.js} +1 -1
- package/{index-2wyn17fa.js → index-pydbnfez.js} +2 -2
- package/{index-bjh4wbtk.js → index-qwa5k1j8.js} +1 -1
- package/{index-81a1ywp6.js → index-tamvyvj3.js} +3 -3
- package/{index-dez4t5sd.js → index-trpasv9q.js} +260 -259
- package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
- package/{index-2k0t8p3p.js → index-va06nr10.js} +1086 -1075
- package/{index-9c3cd767.js → index-vk0pa0mn.js} +20 -21
- package/{index-qyaqzh8e.js → index-w44cy4vs.js} +2216 -2216
- package/index.js +22 -22
- package/{library.command-n58dgakb.js → library.command-erp2f56n.js} +3 -3
- package/{localRegistry.command-f2768sww.js → localRegistry.command-xxpqgxjw.js} +15 -14
- package/{module.command-xzjg5k7n.js → module.command-gs2r615e.js} +5 -5
- package/{package.command-24sh1j1q.js → package.command-7343f74r.js} +4 -4
- package/package.json +2 -2
- package/{page.command-fqnm88na.js → page.command-4h4a8pnh.js} +3 -3
- package/{pipeline.command-vn6taz75.js → pipeline.command-a2nrk26m.js} +3 -3
- package/{primitive.command-4j1g1a0d.js → primitive.command-w6t0asqz.js} +6 -6
- package/{projectTypecheck-3y7ayty0.js → projectTypecheck-f534enqj.js} +1 -1
- package/{quality.command-5hqmcdwt.js → quality.command-77x7vbbx.js} +4 -4
- package/{repair.command-rw5tk460.js → repair.command-y6sstev7.js} +5 -5
- package/{scalar.command-0jpap45m.js → scalar.command-20b8cy0k.js} +4 -4
- package/{subspace.command-y7n9sghk.js → subspace.command-92xkc0s0.js} +11 -10
- package/templates/lib/cnst.ts +0 -1
- package/templates/workspaceRoot/.gitignore.template +24 -53
- package/{tunnel.command-5gjwh1f8.js → tunnel.command-2kw8vt9g.js} +8 -7
- package/{workflow.command-3t22f7eb.js → workflow.command-2djwmpqf.js} +10 -10
- package/{workspace.command-cq5xg2jq.js → workspace.command-5se6spqb.js} +28 -27
- package/TunnelShare-p8ymkww8.js +0 -13
- package/cloud.script-jc3hc5d9.js +0 -23
- package/context.runner-v0def5my.js +0 -24
- package/index-n68jx79w.js +0 -120
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
FileSys2
|
|
4
|
+
} from "./index-52w0fvh8.js";
|
|
2
5
|
import {
|
|
3
6
|
SOURCE_EXTS,
|
|
4
7
|
WatchRootResolver
|
|
5
|
-
} from "./index-
|
|
8
|
+
} from "./index-5nd1wtkv.js";
|
|
6
9
|
import {
|
|
7
10
|
bundleDefine,
|
|
8
11
|
CsrEntryFiles,
|
|
@@ -16,1359 +19,1019 @@ import {
|
|
|
16
19
|
CsrDevPaths,
|
|
17
20
|
PagesEntrySourceGenerator,
|
|
18
21
|
PagesBundleBuilder
|
|
19
|
-
} from "./index-
|
|
20
|
-
import {
|
|
21
|
-
FileSys2
|
|
22
|
-
} from "./index-52w0fvh8.js";
|
|
22
|
+
} from "./index-192ha4xh.js";
|
|
23
23
|
import {
|
|
24
24
|
AsyncDefaultExportDetector,
|
|
25
25
|
resolveSsrPageEntriesForApp
|
|
26
|
-
} from "./index-
|
|
26
|
+
} from "./index-va06nr10.js";
|
|
27
27
|
|
|
28
|
-
// pkgs/@akanjs/devkit/
|
|
28
|
+
// pkgs/@akanjs/devkit/frontendBuild/autoImportSync.ts
|
|
29
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
29
30
|
import path from "path";
|
|
30
|
-
import
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
31
|
+
import ts from "typescript";
|
|
32
|
+
var TEST_FILE_RE = /\.(test|spec)\.(ts|tsx)$/;
|
|
33
|
+
var AKAN_BASE = {
|
|
34
|
+
names: ["Int", "Float", "ID", "Any", "Upload", "enumOf", "dayjs"],
|
|
35
|
+
specifier: "akanjs/base",
|
|
36
|
+
kind: "named"
|
|
37
|
+
};
|
|
38
|
+
var AKAN_BASE_TYPES = { names: ["Dayjs"], specifier: "akanjs/base", kind: "named", typeOnly: true };
|
|
39
|
+
var CNST_NS = { names: ["cnst"], specifier: "../cnst", kind: "namespace" };
|
|
40
|
+
var DB_NS = { names: ["db"], specifier: "../db", kind: "namespace" };
|
|
41
|
+
var SRV_NS = { names: ["srv"], specifier: "../srv", kind: "namespace" };
|
|
42
|
+
var ERR_DICT = { names: ["Err"], specifier: "../dict", kind: "named" };
|
|
43
|
+
var RULES = {
|
|
44
|
+
constant: [AKAN_BASE, AKAN_BASE_TYPES, { names: ["via"], specifier: "akanjs/constant", kind: "named" }],
|
|
45
|
+
document: [
|
|
46
|
+
AKAN_BASE,
|
|
47
|
+
AKAN_BASE_TYPES,
|
|
48
|
+
CNST_NS,
|
|
49
|
+
DB_NS,
|
|
50
|
+
ERR_DICT,
|
|
51
|
+
{
|
|
52
|
+
names: ["by", "from", "into", "SchemaOf", "DataInputOf", "DocumentUpdateHelper", "documentQueryHelper", "Mdl"],
|
|
53
|
+
specifier: "akanjs/document",
|
|
54
|
+
kind: "named"
|
|
50
55
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
],
|
|
57
|
+
service: [
|
|
58
|
+
AKAN_BASE,
|
|
59
|
+
AKAN_BASE_TYPES,
|
|
60
|
+
DB_NS,
|
|
61
|
+
SRV_NS,
|
|
62
|
+
CNST_NS,
|
|
63
|
+
ERR_DICT,
|
|
64
|
+
{ names: ["serve"], specifier: "akanjs/service", kind: "named" },
|
|
65
|
+
{
|
|
66
|
+
names: ["DataInputOf", "ListQueryOption", "DatabaseRegistry", "getFilterInfoByKey"],
|
|
67
|
+
specifier: "akanjs/document",
|
|
68
|
+
kind: "named"
|
|
63
69
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
entries: index.entries.map((entry) => ({
|
|
77
|
-
...entry,
|
|
78
|
-
seeds: entry.seeds.map((seed) => serializeArtifactPath(seed, normalizedArtifactDir))
|
|
79
|
-
})),
|
|
80
|
-
globalLayoutFiles: index.globalLayoutFiles.map((file) => serializeArtifactPath(file, normalizedArtifactDir))
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
async function saveRouteSeedIndex(artifactDir, index, options = {}) {
|
|
84
|
-
const absPath = path.join(path.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
|
|
85
|
-
await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
|
|
86
|
-
`);
|
|
87
|
-
return absPath;
|
|
88
|
-
}
|
|
89
|
-
function serializeArtifactPath(artifactPath, artifactDir) {
|
|
90
|
-
if (!path.isAbsolute(artifactPath))
|
|
91
|
-
return artifactPath;
|
|
92
|
-
return path.relative(artifactDir, artifactPath).split(path.sep).join("/");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// pkgs/@akanjs/devkit/frontendBuild/clientEntryDiscovery.ts
|
|
96
|
-
import fs from "fs";
|
|
97
|
-
import path2 from "path";
|
|
98
|
-
var USE_CLIENT_RE = /^\s*(?:\/\*[\s\S]*?\*\/\s*|\/\/[^\n]*\n\s*)*["']use client["']/;
|
|
99
|
-
var NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
|
|
100
|
-
var AKANJS_NODE_MODULE_RE = /[\\/]node_modules[\\/]akanjs[\\/]/;
|
|
101
|
-
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;
|
|
102
|
-
var shouldSkipNodeModule = (absPath) => NODE_MODULES_RE.test(absPath) && !AKANJS_NODE_MODULE_RE.test(absPath);
|
|
103
|
-
|
|
104
|
-
class GraphClientEntryDiscovery {
|
|
105
|
-
#akanConfig;
|
|
106
|
-
#resolvePackage;
|
|
107
|
-
#analyzer;
|
|
108
|
-
#tsTranspiler = new Bun.Transpiler({ loader: "tsx" });
|
|
109
|
-
#fileExistsCache = new Map;
|
|
110
|
-
#factsCache = new Map;
|
|
111
|
-
#resolvedFileCache = new Map;
|
|
112
|
-
#resolvedSpecifierCache = new Map;
|
|
113
|
-
#reachableEntriesCache = new Map;
|
|
114
|
-
#readFiles = new Set;
|
|
115
|
-
#epoch = 0;
|
|
116
|
-
constructor(akanConfig, resolvePackage) {
|
|
117
|
-
this.#akanConfig = akanConfig;
|
|
118
|
-
this.#resolvePackage = resolvePackage;
|
|
119
|
-
this.#analyzer = new BarrelAnalyzer({ resolvePackage });
|
|
120
|
-
}
|
|
121
|
-
static async create(app) {
|
|
122
|
-
return new GraphClientEntryDiscovery(await app.getConfig(), await createTsconfigPackageResolver(app));
|
|
123
|
-
}
|
|
124
|
-
async discover(seeds) {
|
|
125
|
-
const entries = new Set;
|
|
126
|
-
for (const seed of seeds) {
|
|
127
|
-
const walk = { visiting: new Set, partial: new Set, memo: new Map };
|
|
128
|
-
for (const entry of await this.#discoverFromFile(seed, walk))
|
|
129
|
-
entries.add(entry);
|
|
70
|
+
],
|
|
71
|
+
signal: [
|
|
72
|
+
AKAN_BASE,
|
|
73
|
+
AKAN_BASE_TYPES,
|
|
74
|
+
SRV_NS,
|
|
75
|
+
CNST_NS,
|
|
76
|
+
ERR_DICT,
|
|
77
|
+
{
|
|
78
|
+
names: ["endpoint", "internal", "slice", "Public", "Req", "Ws", "None", "Res"],
|
|
79
|
+
specifier: "akanjs/signal",
|
|
80
|
+
kind: "named"
|
|
130
81
|
}
|
|
131
|
-
|
|
82
|
+
],
|
|
83
|
+
dictionary: [
|
|
84
|
+
{
|
|
85
|
+
names: ["modelDictionary", "scalarDictionary", "serviceDictionary"],
|
|
86
|
+
specifier: "akanjs/dictionary",
|
|
87
|
+
kind: "named"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
srvkit: [
|
|
91
|
+
AKAN_BASE,
|
|
92
|
+
AKAN_BASE_TYPES,
|
|
93
|
+
{ names: ["Logger", "HttpClient", "sleep"], specifier: "akanjs/common", kind: "named" },
|
|
94
|
+
{ names: ["adapt"], specifier: "akanjs/service", kind: "named" },
|
|
95
|
+
{ names: ["SignalContext", "Guard", "InternalArg", "Middleware"], specifier: "akanjs/signal", kind: "named" }
|
|
96
|
+
],
|
|
97
|
+
common: [AKAN_BASE, AKAN_BASE_TYPES],
|
|
98
|
+
store: [
|
|
99
|
+
CNST_NS,
|
|
100
|
+
{ names: ["store"], specifier: "akanjs/store", kind: "named" },
|
|
101
|
+
{ names: ["fetch", "usePage", "sig"], specifier: "../useClient", kind: "named" },
|
|
102
|
+
{ names: ["st"], specifier: "../st", kind: "named" },
|
|
103
|
+
{ names: ["RootStore"], specifier: "../st", kind: "named", typeOnly: true }
|
|
104
|
+
],
|
|
105
|
+
client: [
|
|
106
|
+
{
|
|
107
|
+
names: ["cnst", "fetch", "st", "usePage"],
|
|
108
|
+
specifier: (ctx) => `@${ctx.scope}/${ctx.project}/client`,
|
|
109
|
+
kind: "named"
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
};
|
|
113
|
+
var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
114
|
+
var DOMAIN_ROLE_KINDS = {
|
|
115
|
+
constant: ["constant"],
|
|
116
|
+
dictionary: ["constant", "document", "signal"],
|
|
117
|
+
common: ["constant"]
|
|
118
|
+
};
|
|
119
|
+
var LIB_BARRELS = {
|
|
120
|
+
Err: { barrel: "dict", kind: "named" },
|
|
121
|
+
db: { barrel: "db", kind: "namespace" },
|
|
122
|
+
cnst: { barrel: "cnst", kind: "namespace" },
|
|
123
|
+
srv: { barrel: "srv", kind: "namespace" }
|
|
124
|
+
};
|
|
125
|
+
var GLOBAL_NAMES = new Set(["fetch"]);
|
|
126
|
+
var DOMAIN_DENYLIST = new Set([
|
|
127
|
+
"Map",
|
|
128
|
+
"Set",
|
|
129
|
+
"WeakMap",
|
|
130
|
+
"WeakSet",
|
|
131
|
+
"Date",
|
|
132
|
+
"Promise",
|
|
133
|
+
"Array",
|
|
134
|
+
"Object",
|
|
135
|
+
"Error",
|
|
136
|
+
"TypeError",
|
|
137
|
+
"RangeError",
|
|
138
|
+
"RegExp",
|
|
139
|
+
"Symbol",
|
|
140
|
+
"Proxy",
|
|
141
|
+
"Reflect",
|
|
142
|
+
"JSON",
|
|
143
|
+
"Math",
|
|
144
|
+
"Number",
|
|
145
|
+
"BigInt",
|
|
146
|
+
"Function",
|
|
147
|
+
"Boolean",
|
|
148
|
+
"String",
|
|
149
|
+
"Record",
|
|
150
|
+
"Partial",
|
|
151
|
+
"Required",
|
|
152
|
+
"Readonly",
|
|
153
|
+
"Pick",
|
|
154
|
+
"Omit",
|
|
155
|
+
"Exclude",
|
|
156
|
+
"Extract",
|
|
157
|
+
"NonNullable",
|
|
158
|
+
"ReturnType",
|
|
159
|
+
"Parameters",
|
|
160
|
+
"Awaited",
|
|
161
|
+
"InstanceType"
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
class AutoImportSync {
|
|
165
|
+
#workspaceRoot;
|
|
166
|
+
#domainCache = new Map;
|
|
167
|
+
constructor({ workspaceRoot }) {
|
|
168
|
+
this.#workspaceRoot = path.resolve(workspaceRoot);
|
|
132
169
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
170
|
+
async syncForBatch(files) {
|
|
171
|
+
const changedFiles = [];
|
|
172
|
+
const errors = [];
|
|
173
|
+
const seen = new Set;
|
|
137
174
|
for (const file of files) {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
moved = true;
|
|
142
|
-
if (!exists)
|
|
143
|
-
this.#readFiles.delete(absPath);
|
|
144
|
-
this.#factsCache.delete(absPath);
|
|
145
|
-
this.#fileExistsCache.delete(absPath);
|
|
175
|
+
const pkgRoot = this.#domainPkgRootOf(file);
|
|
176
|
+
if (pkgRoot)
|
|
177
|
+
this.#domainCache.delete(pkgRoot);
|
|
146
178
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}, () => {
|
|
163
|
-
entry.found = false;
|
|
164
|
-
});
|
|
165
|
-
cache.set(key, entry);
|
|
166
|
-
return entry.promise;
|
|
167
|
-
}
|
|
168
|
-
async#fileExists(p) {
|
|
169
|
-
const absPath = path2.resolve(p);
|
|
170
|
-
return await this.#lookup(this.#fileExistsCache, absPath, () => Bun.file(absPath).exists(), (exists) => exists);
|
|
171
|
-
}
|
|
172
|
-
#facts(file) {
|
|
173
|
-
const absPath = path2.resolve(file);
|
|
174
|
-
let cached = this.#factsCache.get(absPath);
|
|
175
|
-
if (!cached) {
|
|
176
|
-
cached = (async () => {
|
|
177
|
-
const content = await Bun.file(absPath).text().catch(() => null);
|
|
178
|
-
if (content === null)
|
|
179
|
-
return null;
|
|
180
|
-
this.#readFiles.add(absPath);
|
|
181
|
-
if (USE_CLIENT_RE.test(content))
|
|
182
|
-
return { isClientEntry: true, imports: [] };
|
|
183
|
-
return { isClientEntry: false, imports: this.#scanImports(await this.#rewrite(content)) };
|
|
184
|
-
})();
|
|
185
|
-
this.#factsCache.set(absPath, cached);
|
|
179
|
+
for (const file of files) {
|
|
180
|
+
const abs = path.resolve(file);
|
|
181
|
+
if (seen.has(abs))
|
|
182
|
+
continue;
|
|
183
|
+
seen.add(abs);
|
|
184
|
+
const ctx = this.#contextFor(abs);
|
|
185
|
+
if (!ctx)
|
|
186
|
+
continue;
|
|
187
|
+
try {
|
|
188
|
+
const changed = await this.#syncFile(abs, ctx);
|
|
189
|
+
if (changed)
|
|
190
|
+
changedFiles.push(abs);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
errors.push(`[auto-import] sync failed for ${file}: ${formatError(err)}`);
|
|
193
|
+
}
|
|
186
194
|
}
|
|
187
|
-
return
|
|
195
|
+
return { changedFiles, errors };
|
|
188
196
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return
|
|
196
|
-
|
|
197
|
+
#contextFor(abs) {
|
|
198
|
+
const rel = path.relative(this.#workspaceRoot, abs);
|
|
199
|
+
if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
200
|
+
return null;
|
|
201
|
+
const base = path.basename(abs);
|
|
202
|
+
if (TEST_FILE_RE.test(base) || base === "index.ts" || base === "index.tsx" || base.endsWith(".d.ts"))
|
|
203
|
+
return null;
|
|
204
|
+
const parts = rel.split(path.sep).filter(Boolean);
|
|
205
|
+
const [scope, project, facet] = parts;
|
|
206
|
+
if (scope !== "apps" && scope !== "libs" || !project || !facet)
|
|
207
|
+
return null;
|
|
208
|
+
if (facet === "lib" && parts[3] === "__lib")
|
|
209
|
+
return null;
|
|
210
|
+
const role = roleFor(facet, base);
|
|
211
|
+
if (!role)
|
|
212
|
+
return null;
|
|
213
|
+
return { role, scope, project };
|
|
197
214
|
}
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
215
|
+
async#syncFile(abs, ctx) {
|
|
216
|
+
const stats = await stat(abs).catch(() => null);
|
|
217
|
+
if (!stats?.isFile())
|
|
218
|
+
return false;
|
|
219
|
+
const source = await readFile(abs, "utf8");
|
|
220
|
+
const resolveExtra = await this.#extraResolverFor(abs, ctx);
|
|
221
|
+
const next = transformSource(source, abs, ctx, resolveExtra);
|
|
222
|
+
if (next === null || next === source)
|
|
223
|
+
return false;
|
|
224
|
+
await FileSys2.writeTextAtomic(abs, next);
|
|
225
|
+
return true;
|
|
204
226
|
}
|
|
205
|
-
async#
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
for (const
|
|
216
|
-
const
|
|
217
|
-
if (
|
|
218
|
-
return
|
|
227
|
+
async#extraResolverFor(abs, ctx) {
|
|
228
|
+
const resolvers = [];
|
|
229
|
+
if (ctx.role === "srvkit" || ctx.role === "common")
|
|
230
|
+
resolvers.push(await this.#libBarrelResolver(abs, ctx));
|
|
231
|
+
const kinds = DOMAIN_ROLE_KINDS[ctx.role];
|
|
232
|
+
if (kinds)
|
|
233
|
+
resolvers.push(await this.#domainResolver(abs, ctx, kinds));
|
|
234
|
+
if (resolvers.length === 0)
|
|
235
|
+
return;
|
|
236
|
+
return (symbol) => {
|
|
237
|
+
for (const resolve of resolvers) {
|
|
238
|
+
const target = resolve(symbol);
|
|
239
|
+
if (target)
|
|
240
|
+
return target;
|
|
219
241
|
}
|
|
220
242
|
return null;
|
|
221
|
-
}
|
|
243
|
+
};
|
|
222
244
|
}
|
|
223
|
-
async#
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
245
|
+
async#domainResolver(abs, ctx, kinds) {
|
|
246
|
+
const index = await this.#domainIndex(path.join(this.#workspaceRoot, ctx.scope, ctx.project));
|
|
247
|
+
const fileDir = path.dirname(abs);
|
|
248
|
+
return (symbol) => {
|
|
249
|
+
if (!PASCAL_CASE_RE.test(symbol) || DOMAIN_DENYLIST.has(symbol))
|
|
250
|
+
return null;
|
|
251
|
+
const entries = (index.get(symbol) ?? []).filter((entry) => kinds.includes(entry.kind));
|
|
252
|
+
if (entries.length !== 1)
|
|
253
|
+
return null;
|
|
254
|
+
return { specifier: relativeSpecifier(fileDir, entries[0].file), kind: "named" };
|
|
255
|
+
};
|
|
233
256
|
}
|
|
234
|
-
async#
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (memo) {
|
|
242
|
-
if (walk.partial.has(absPath))
|
|
243
|
-
for (const onPath of walk.visiting)
|
|
244
|
-
walk.partial.add(onPath);
|
|
245
|
-
return new Set(memo);
|
|
246
|
-
}
|
|
247
|
-
if (walk.visiting.has(absPath)) {
|
|
248
|
-
let below = false;
|
|
249
|
-
for (const onPath of walk.visiting) {
|
|
250
|
-
if (below)
|
|
251
|
-
walk.partial.add(onPath);
|
|
252
|
-
else
|
|
253
|
-
below = onPath === absPath;
|
|
254
|
-
}
|
|
255
|
-
return new Set;
|
|
256
|
-
}
|
|
257
|
-
if (shouldSkipNodeModule(absPath))
|
|
258
|
-
return new Set;
|
|
259
|
-
walk.visiting.add(absPath);
|
|
260
|
-
const entries = new Set;
|
|
261
|
-
const facts = await this.#facts(absPath);
|
|
262
|
-
if (!facts)
|
|
263
|
-
return this.#finishDiscovery(absPath, walk, entries, epoch);
|
|
264
|
-
if (facts.isClientEntry) {
|
|
265
|
-
entries.add(absPath);
|
|
266
|
-
return this.#finishDiscovery(absPath, walk, entries, epoch);
|
|
267
|
-
}
|
|
268
|
-
const importerDir = path2.dirname(absPath);
|
|
269
|
-
for (const imp of facts.imports) {
|
|
270
|
-
const spec = imp.path;
|
|
271
|
-
if (!spec)
|
|
272
|
-
continue;
|
|
273
|
-
if (NON_SOURCE_EXT_RE.test(spec))
|
|
274
|
-
continue;
|
|
275
|
-
const resolved = await this.#resolveSpecifier(spec, importerDir);
|
|
276
|
-
if (!resolved)
|
|
277
|
-
continue;
|
|
278
|
-
if (shouldSkipNodeModule(resolved))
|
|
279
|
-
continue;
|
|
280
|
-
for (const entry of await this.#discoverFromFile(resolved, walk))
|
|
281
|
-
entries.add(entry);
|
|
257
|
+
async#libBarrelResolver(abs, ctx) {
|
|
258
|
+
const fileDir = path.dirname(abs);
|
|
259
|
+
const libDir = path.join(this.#workspaceRoot, ctx.scope, ctx.project, "lib");
|
|
260
|
+
const available = new Map;
|
|
261
|
+
for (const [symbol, { barrel, kind }] of Object.entries(LIB_BARRELS)) {
|
|
262
|
+
if (await fileExists(path.join(libDir, `${barrel}.ts`)))
|
|
263
|
+
available.set(symbol, { specifier: relativeSpecifier(fileDir, path.join(libDir, barrel)), kind });
|
|
282
264
|
}
|
|
283
|
-
return
|
|
265
|
+
return (symbol) => available.get(symbol) ?? null;
|
|
284
266
|
}
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
267
|
+
async#domainIndex(pkgRoot) {
|
|
268
|
+
const cached = this.#domainCache.get(pkgRoot);
|
|
269
|
+
if (cached)
|
|
270
|
+
return cached;
|
|
271
|
+
const index = await buildDomainIndex(path.join(pkgRoot, "lib"));
|
|
272
|
+
this.#domainCache.set(pkgRoot, index);
|
|
273
|
+
return index;
|
|
274
|
+
}
|
|
275
|
+
#domainPkgRootOf(file) {
|
|
276
|
+
const rel = path.relative(this.#workspaceRoot, path.resolve(file));
|
|
277
|
+
if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
278
|
+
return null;
|
|
279
|
+
const [scope, project, facet] = rel.split(path.sep).filter(Boolean);
|
|
280
|
+
if (scope !== "apps" && scope !== "libs" || !project || facet !== "lib")
|
|
281
|
+
return null;
|
|
282
|
+
if (!domainKindOf(path.basename(file)))
|
|
283
|
+
return null;
|
|
284
|
+
return path.join(this.#workspaceRoot, scope, project);
|
|
291
285
|
}
|
|
292
286
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
287
|
+
var roleFor = (facet, base) => {
|
|
288
|
+
if (facet === "lib") {
|
|
289
|
+
if (base.endsWith(".constant.ts"))
|
|
290
|
+
return "constant";
|
|
291
|
+
if (base.endsWith(".document.ts"))
|
|
292
|
+
return "document";
|
|
293
|
+
if (base.endsWith(".service.ts"))
|
|
294
|
+
return "service";
|
|
295
|
+
if (base.endsWith(".signal.ts"))
|
|
296
|
+
return "signal";
|
|
297
|
+
if (base.endsWith(".dictionary.ts"))
|
|
298
|
+
return "dictionary";
|
|
299
|
+
if (base.endsWith(".store.ts"))
|
|
300
|
+
return "store";
|
|
301
|
+
if (base.endsWith(".tsx"))
|
|
302
|
+
return "client";
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
if (facet === "ui" || facet === "page")
|
|
306
|
+
return base.endsWith(".tsx") ? "client" : null;
|
|
307
|
+
if (facet === "webkit")
|
|
308
|
+
return base.endsWith(".ts") || base.endsWith(".tsx") ? "client" : null;
|
|
309
|
+
if (facet === "srvkit")
|
|
310
|
+
return base.endsWith(".ts") ? "srvkit" : null;
|
|
311
|
+
if (facet === "common")
|
|
312
|
+
return base.endsWith(".ts") || base.endsWith(".tsx") ? "common" : null;
|
|
313
|
+
return null;
|
|
308
314
|
};
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
#external;
|
|
316
|
-
#externalSubpaths;
|
|
317
|
-
#externalAliases;
|
|
318
|
-
#command;
|
|
319
|
-
#target;
|
|
320
|
-
#outputSubdir;
|
|
321
|
-
#reactFastRefresh;
|
|
322
|
-
#outdir;
|
|
323
|
-
#servePrefix;
|
|
324
|
-
#manifest = {};
|
|
325
|
-
#ssrManifest = { moduleLoading: null, moduleMap: {} };
|
|
326
|
-
#entryUrlsByAbsPath = new Map;
|
|
327
|
-
#entryOutputAbsByAbsPath = new Map;
|
|
328
|
-
#entryDepsByAbsPath = new Map;
|
|
329
|
-
#clientReferenceIdByAbsPath = new Map;
|
|
330
|
-
#opaqueEntries = null;
|
|
331
|
-
#metafileOutputsByAbs = new Map;
|
|
332
|
-
constructor(options) {
|
|
333
|
-
this.#app = options.app;
|
|
334
|
-
this.#entries = options.entries;
|
|
335
|
-
this.#plugins = options.plugins ?? [];
|
|
336
|
-
this.#external = options.external ?? [];
|
|
337
|
-
this.#externalSubpaths = options.externalSubpaths ?? [];
|
|
338
|
-
this.#externalAliases = options.externalAliases ?? {};
|
|
339
|
-
this.#command = options.command ?? "start";
|
|
340
|
-
this.#target = options.target ?? "browser";
|
|
341
|
-
this.#outputSubdir = options.outputSubdir ?? "client";
|
|
342
|
-
this.#reactFastRefresh = options.reactFastRefresh ?? false;
|
|
343
|
-
const artifactDir = `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
|
|
344
|
-
this.#outdir = `${artifactDir}/${this.#outputSubdir}`;
|
|
345
|
-
this.#servePrefix = `/_akan/${this.#outputSubdir}`;
|
|
315
|
+
var targetFor = (symbol, ctx) => {
|
|
316
|
+
for (const rule of RULES[ctx.role]) {
|
|
317
|
+
if (!rule.names.includes(symbol))
|
|
318
|
+
continue;
|
|
319
|
+
const specifier = typeof rule.specifier === "function" ? rule.specifier(ctx) : rule.specifier;
|
|
320
|
+
return { specifier, kind: rule.kind, typeOnly: rule.typeOnly };
|
|
346
321
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
throw new Error("[ClientEntriesBundler] metafile is missing");
|
|
370
|
-
this.#metafileOutputsByAbs = new Map(Object.entries(metafile.outputs).map(([k, v]) => [this.#absFromOutdir(k), v]));
|
|
371
|
-
for (const artifact of result.outputs.filter((a) => a.kind === "entry-point"))
|
|
372
|
-
this.#addEntryArtifact(artifact.path);
|
|
373
|
-
return {
|
|
374
|
-
manifest: this.#manifest,
|
|
375
|
-
ssrManifest: this.#ssrManifest,
|
|
376
|
-
entryUrlsByAbsPath: this.#entryUrlsByAbsPath,
|
|
377
|
-
entryOutputAbsByAbsPath: this.#entryOutputAbsByAbsPath,
|
|
378
|
-
entryDepsByAbsPath: this.#entryDepsByAbsPath,
|
|
379
|
-
clientReferenceIdByAbsPath: this.#clientReferenceIdByAbsPath
|
|
380
|
-
};
|
|
322
|
+
return null;
|
|
323
|
+
};
|
|
324
|
+
var transformSource = (source, fileName, ctx, resolveExtra) => {
|
|
325
|
+
const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, scriptKindFor(fileName));
|
|
326
|
+
const bound = collectBoundNames(sf);
|
|
327
|
+
const used = collectUsedReferences(sf);
|
|
328
|
+
const bareGlobals = collectBareGlobalReferences(sf);
|
|
329
|
+
const namedBySpecifier = new Map;
|
|
330
|
+
const namespaceImports = [];
|
|
331
|
+
for (const name of used) {
|
|
332
|
+
if (bound.has(name) || bareGlobals.has(name))
|
|
333
|
+
continue;
|
|
334
|
+
const target = targetFor(name, ctx) ?? resolveExtra?.(name) ?? null;
|
|
335
|
+
if (!target)
|
|
336
|
+
continue;
|
|
337
|
+
if (target.kind === "namespace")
|
|
338
|
+
namespaceImports.push({ name, specifier: target.specifier });
|
|
339
|
+
else {
|
|
340
|
+
const names = namedBySpecifier.get(target.specifier) ?? new Map;
|
|
341
|
+
names.set(name, target.typeOnly ?? false);
|
|
342
|
+
namedBySpecifier.set(target.specifier, names);
|
|
343
|
+
}
|
|
381
344
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
345
|
+
if (namedBySpecifier.size === 0 && namespaceImports.length === 0)
|
|
346
|
+
return null;
|
|
347
|
+
const importDecls = sf.statements.filter(ts.isImportDeclaration);
|
|
348
|
+
const anchor = importDecls.at(-1) ?? null;
|
|
349
|
+
const namedImportsBySpecifier = collectExistingNamedImports(importDecls);
|
|
350
|
+
const edits = [];
|
|
351
|
+
const newStatements = [];
|
|
352
|
+
for (const [specifier, names] of namedBySpecifier) {
|
|
353
|
+
const existing = namedImportsBySpecifier.get(specifier);
|
|
354
|
+
if (existing) {
|
|
355
|
+
const merged = new Map(existing.names);
|
|
356
|
+
for (const [name, isType] of names)
|
|
357
|
+
if (!merged.has(name))
|
|
358
|
+
merged.set(name, isType);
|
|
359
|
+
edits.push({
|
|
360
|
+
start: existing.decl.getStart(sf),
|
|
361
|
+
end: existing.decl.getEnd(),
|
|
362
|
+
text: formatNamedImport(specifier, merged)
|
|
363
|
+
});
|
|
364
|
+
} else
|
|
365
|
+
newStatements.push(formatNamedImport(specifier, names));
|
|
397
366
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
367
|
+
for (const ns of namespaceImports)
|
|
368
|
+
newStatements.push(`import * as ${ns.name} from "${ns.specifier}";`);
|
|
369
|
+
if (newStatements.length > 0) {
|
|
370
|
+
const anchorEdit = anchor ? edits.find((edit) => edit.start === anchor.getStart(sf)) : undefined;
|
|
371
|
+
if (anchorEdit)
|
|
372
|
+
anchorEdit.text = `${anchorEdit.text}
|
|
373
|
+
${newStatements.join(`
|
|
374
|
+
`)}`;
|
|
375
|
+
else if (anchor)
|
|
376
|
+
edits.push({ start: anchor.getEnd(), end: anchor.getEnd(), text: `
|
|
377
|
+
${newStatements.join(`
|
|
378
|
+
`)}` });
|
|
379
|
+
else {
|
|
380
|
+
const prologueEnd = directivePrologueEnd(sf);
|
|
381
|
+
if (prologueEnd >= 0)
|
|
382
|
+
edits.push({ start: prologueEnd, end: prologueEnd, text: `
|
|
383
|
+
${newStatements.join(`
|
|
384
|
+
`)}` });
|
|
385
|
+
else
|
|
386
|
+
edits.push({ start: 0, end: 0, text: `${newStatements.join(`
|
|
408
387
|
`)}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
async#scanEntryExportNames(absEntry) {
|
|
412
|
-
return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
|
|
413
|
-
}
|
|
414
|
-
#createExternalSpecifiersPlugin() {
|
|
415
|
-
const set = new Set(this.#external);
|
|
416
|
-
const subpathSet = new Set(this.#externalSubpaths);
|
|
417
|
-
const aliases = this.#externalAliases;
|
|
418
|
-
const specifiers = [...new Set([...this.#external, ...this.#externalSubpaths, ...Object.keys(aliases)])];
|
|
419
|
-
const escaped = specifiers.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
420
|
-
const filter = new RegExp(`^(${escaped.join("|")})(?:/.*)?$`);
|
|
421
|
-
return {
|
|
422
|
-
name: "akan-externalize-specifiers",
|
|
423
|
-
setup(build) {
|
|
424
|
-
build.onResolve({ filter }, (args) => {
|
|
425
|
-
const alias = aliases[args.path];
|
|
426
|
-
if (alias)
|
|
427
|
-
return { path: alias, external: true };
|
|
428
|
-
if (!ClientEntriesBundler.#matchesExternalSpecifier(args.path, set, subpathSet))
|
|
429
|
-
return;
|
|
430
|
-
return { path: args.path, external: true };
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
static #matchesExternalSpecifier(specifier, exactExternals, subpathExternals) {
|
|
436
|
-
if (exactExternals.has(specifier) || subpathExternals.has(specifier))
|
|
437
|
-
return true;
|
|
438
|
-
for (const external of subpathExternals)
|
|
439
|
-
if (specifier.startsWith(`${external}/`))
|
|
440
|
-
return true;
|
|
441
|
-
return false;
|
|
442
|
-
}
|
|
443
|
-
async#rewriteExternalImportSpecifiers(outputs) {
|
|
444
|
-
if (Object.keys(this.#externalAliases).length === 0)
|
|
445
|
-
return;
|
|
446
|
-
await Promise.all(outputs.filter((output) => output.path.endsWith(".js")).map(async (output) => {
|
|
447
|
-
const source = await Bun.file(output.path).text();
|
|
448
|
-
const rewritten = ClientEntriesBundler.rewriteExternalImportSpecifiers(source, this.#externalAliases);
|
|
449
|
-
if (rewritten !== source)
|
|
450
|
-
await Bun.write(output.path, rewritten);
|
|
451
|
-
}));
|
|
452
|
-
}
|
|
453
|
-
static rewriteExternalImportSpecifiers(source, aliases) {
|
|
454
|
-
let rewritten = source;
|
|
455
|
-
for (const [specifier, alias] of Object.entries(aliases)) {
|
|
456
|
-
if (!alias)
|
|
457
|
-
continue;
|
|
458
|
-
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
459
|
-
rewritten = rewritten.replace(new RegExp(`(\\bfrom\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
|
|
460
|
-
return `${prefix}${alias}${suffix}`;
|
|
461
|
-
}).replace(new RegExp(`(\\bimport\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
|
|
462
|
-
return `${prefix}${alias}${suffix}`;
|
|
463
|
-
});
|
|
388
|
+
|
|
389
|
+
` });
|
|
464
390
|
}
|
|
465
|
-
return rewritten;
|
|
466
|
-
}
|
|
467
|
-
#toServeUrl(absOutPath) {
|
|
468
|
-
const rel = path3.relative(this.#outdir, absOutPath).split(path3.sep).join("/");
|
|
469
|
-
return `${this.#servePrefix}/${rel}`;
|
|
470
|
-
}
|
|
471
|
-
#absFromOutdir(p) {
|
|
472
|
-
return path3.isAbsolute(p) ? p : path3.resolve(this.#outdir, p);
|
|
473
|
-
}
|
|
474
|
-
#absFromEntryPoint(p) {
|
|
475
|
-
if (path3.isAbsolute(p))
|
|
476
|
-
return path3.resolve(p);
|
|
477
|
-
const candidates = [path3.resolve(process.cwd(), p), path3.resolve(this.#app.cwdPath, p)];
|
|
478
|
-
return candidates.find((candidate) => fs2.existsSync(candidate)) ?? candidates[0];
|
|
479
391
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (
|
|
493
|
-
|
|
494
|
-
|
|
392
|
+
if (edits.length === 0)
|
|
393
|
+
return null;
|
|
394
|
+
edits.sort((a, b) => b.start - a.start);
|
|
395
|
+
let out = source;
|
|
396
|
+
for (const edit of edits)
|
|
397
|
+
out = out.slice(0, edit.start) + edit.text + out.slice(edit.end);
|
|
398
|
+
return out === source ? null : out;
|
|
399
|
+
};
|
|
400
|
+
var collectBoundNames = (sf) => {
|
|
401
|
+
const names = new Set;
|
|
402
|
+
const visit = (node) => {
|
|
403
|
+
if (ts.isImportClause(node)) {
|
|
404
|
+
if (node.name)
|
|
405
|
+
names.add(node.name.text);
|
|
406
|
+
const bindings = node.namedBindings;
|
|
407
|
+
if (bindings && ts.isNamespaceImport(bindings))
|
|
408
|
+
names.add(bindings.name.text);
|
|
409
|
+
if (bindings && ts.isNamedImports(bindings))
|
|
410
|
+
for (const el of bindings.elements)
|
|
411
|
+
names.add(el.name.text);
|
|
495
412
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
deps.add(dep);
|
|
521
|
-
}
|
|
522
|
-
return deps;
|
|
523
|
-
}
|
|
524
|
-
#addEntryArtifact(absOut) {
|
|
525
|
-
if (!this.#opaqueEntries)
|
|
526
|
-
throw new Error("[ClientEntriesBundler] opaque entries are missing");
|
|
527
|
-
const info = this.#metafileOutputsByAbs.get(absOut);
|
|
528
|
-
const buildEntryPointAbs = info?.entryPoint ? this.#absFromEntryPoint(info.entryPoint) : null;
|
|
529
|
-
const entryPointAbs = buildEntryPointAbs ? this.#opaqueEntries.originalByAlias.get(buildEntryPointAbs) ?? buildEntryPointAbs : null;
|
|
530
|
-
if (!entryPointAbs)
|
|
531
|
-
return;
|
|
532
|
-
const chunkUrls = this.#collectChunkUrls(absOut);
|
|
533
|
-
const entryUrl = chunkUrls[0] ?? this.#toServeUrl(absOut);
|
|
534
|
-
this.#entryUrlsByAbsPath.set(entryPointAbs, entryUrl);
|
|
535
|
-
this.#entryOutputAbsByAbsPath.set(entryPointAbs, absOut);
|
|
536
|
-
this.#entryDepsByAbsPath.set(entryPointAbs, [...this.#collectClientDeps(absOut)].sort());
|
|
537
|
-
const clientReferenceId = toClientReferencePath(entryPointAbs, this.#app.workspace.workspaceRoot);
|
|
538
|
-
this.#clientReferenceIdByAbsPath.set(entryPointAbs, clientReferenceId);
|
|
539
|
-
const flatChunks = [];
|
|
540
|
-
for (const url of chunkUrls)
|
|
541
|
-
flatChunks.push(url, url);
|
|
542
|
-
const exportNames = info?.exports && info.exports.length > 0 ? info.exports : ["default"];
|
|
543
|
-
const ssrEntriesByName = {};
|
|
544
|
-
for (const name of exportNames) {
|
|
545
|
-
const key = `${clientReferenceId}#${name}`;
|
|
546
|
-
this.#manifest[key] = { id: entryUrl, chunks: flatChunks, name, async: true };
|
|
547
|
-
ssrEntriesByName[name] = { id: absOut, chunks: [absOut, absOut], name, async: true };
|
|
413
|
+
if ((ts.isVariableDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.name && ts.isIdentifier(node.name))
|
|
414
|
+
names.add(node.name.text);
|
|
415
|
+
ts.forEachChild(node, visit);
|
|
416
|
+
};
|
|
417
|
+
ts.forEachChild(sf, visit);
|
|
418
|
+
return names;
|
|
419
|
+
};
|
|
420
|
+
var collectUsedReferences = (sf) => {
|
|
421
|
+
const used = new Set;
|
|
422
|
+
const visit = (node) => {
|
|
423
|
+
if (ts.isIdentifier(node) && isReferencePosition(node))
|
|
424
|
+
used.add(node.text);
|
|
425
|
+
ts.forEachChild(node, visit);
|
|
426
|
+
};
|
|
427
|
+
ts.forEachChild(sf, visit);
|
|
428
|
+
return used;
|
|
429
|
+
};
|
|
430
|
+
var collectBareGlobalReferences = (sf) => {
|
|
431
|
+
const bare = new Set;
|
|
432
|
+
const visit = (node) => {
|
|
433
|
+
if (ts.isIdentifier(node) && GLOBAL_NAMES.has(node.text) && isReferencePosition(node)) {
|
|
434
|
+
const parent = node.parent;
|
|
435
|
+
if (!(parent && ts.isPropertyAccessExpression(parent) && parent.expression === node))
|
|
436
|
+
bare.add(node.text);
|
|
548
437
|
}
|
|
549
|
-
|
|
438
|
+
ts.forEachChild(node, visit);
|
|
439
|
+
};
|
|
440
|
+
ts.forEachChild(sf, visit);
|
|
441
|
+
return bare;
|
|
442
|
+
};
|
|
443
|
+
var isReferencePosition = (node) => {
|
|
444
|
+
const parent = node.parent;
|
|
445
|
+
if (!parent)
|
|
446
|
+
return true;
|
|
447
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === node)
|
|
448
|
+
return false;
|
|
449
|
+
if (ts.isQualifiedName(parent) && parent.right === node)
|
|
450
|
+
return false;
|
|
451
|
+
if (ts.isPropertyAssignment(parent) && parent.name === node)
|
|
452
|
+
return false;
|
|
453
|
+
if (ts.isPropertySignature(parent) && parent.name === node)
|
|
454
|
+
return false;
|
|
455
|
+
if (ts.isBindingElement(parent) && parent.propertyName === node)
|
|
456
|
+
return false;
|
|
457
|
+
if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent))
|
|
458
|
+
return false;
|
|
459
|
+
return true;
|
|
460
|
+
};
|
|
461
|
+
var collectExistingNamedImports = (importDecls) => {
|
|
462
|
+
const map = new Map;
|
|
463
|
+
for (const decl of importDecls) {
|
|
464
|
+
const clause = decl.importClause;
|
|
465
|
+
const bindings = clause?.namedBindings;
|
|
466
|
+
if (!clause || !bindings || !ts.isNamedImports(bindings))
|
|
467
|
+
continue;
|
|
468
|
+
if (!ts.isStringLiteral(decl.moduleSpecifier))
|
|
469
|
+
continue;
|
|
470
|
+
const specifier = decl.moduleSpecifier.text;
|
|
471
|
+
if (map.has(specifier))
|
|
472
|
+
continue;
|
|
473
|
+
const names = new Map;
|
|
474
|
+
for (const el of bindings.elements)
|
|
475
|
+
names.set(el.name.text, clause.isTypeOnly || el.isTypeOnly);
|
|
476
|
+
map.set(specifier, { decl, names });
|
|
550
477
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
"
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
"akanjs/fetch"
|
|
568
|
-
];
|
|
569
|
-
|
|
570
|
-
// pkgs/@akanjs/devkit/frontendBuild/routeClientBuilder.ts
|
|
571
|
-
var SSR_CLIENT_ALIAS_EXTERNALS = [
|
|
572
|
-
"react",
|
|
573
|
-
"react-dom",
|
|
574
|
-
"react-dom/client",
|
|
575
|
-
"react/jsx-runtime",
|
|
576
|
-
"react/jsx-dev-runtime"
|
|
577
|
-
];
|
|
578
|
-
var SSR_CLIENT_EXTERNALS = [...SSR_CLIENT_ALIAS_EXTERNALS, "akanjs/fetch"];
|
|
579
|
-
|
|
580
|
-
class RouteClientBuilder {
|
|
581
|
-
#app;
|
|
582
|
-
#seeds;
|
|
583
|
-
#graphSeeds;
|
|
584
|
-
#knownEntries;
|
|
585
|
-
#command;
|
|
586
|
-
#discovery;
|
|
587
|
-
#entries;
|
|
588
|
-
#browser;
|
|
589
|
-
constructor(options) {
|
|
590
|
-
this.#app = options.app;
|
|
591
|
-
this.#seeds = options.seeds;
|
|
592
|
-
this.#graphSeeds = options.graphSeeds;
|
|
593
|
-
this.#knownEntries = options.knownEntries ?? new Set;
|
|
594
|
-
this.#command = options.command ?? "start";
|
|
595
|
-
this.#discovery = options.discovery;
|
|
596
|
-
this.#entries = options.entries;
|
|
597
|
-
this.#browser = options.browser ?? (this.#command === "start" ? "registry" : "chunks");
|
|
478
|
+
return map;
|
|
479
|
+
};
|
|
480
|
+
var formatNamedImport = (specifier, names) => {
|
|
481
|
+
const entries = [...names.entries()].sort((a, b) => compareNames(a[0], b[0]));
|
|
482
|
+
if (entries.every(([, isType]) => isType))
|
|
483
|
+
return `import type { ${entries.map(([name]) => name).join(", ")} } from "${specifier}";`;
|
|
484
|
+
const parts = entries.map(([name, isType]) => isType ? `type ${name}` : name);
|
|
485
|
+
return `import { ${parts.join(", ")} } from "${specifier}";`;
|
|
486
|
+
};
|
|
487
|
+
var directivePrologueEnd = (sf) => {
|
|
488
|
+
let end = -1;
|
|
489
|
+
for (const stmt of sf.statements) {
|
|
490
|
+
if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression))
|
|
491
|
+
end = stmt.getEnd();
|
|
492
|
+
else
|
|
493
|
+
break;
|
|
598
494
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if (!acceptedEntries.has(originalEntry))
|
|
633
|
-
continue;
|
|
634
|
-
const deps = new Set([originalEntry]);
|
|
635
|
-
for (const dep of referenceBundle.entryDepsByAbsPath.get(buildEntry) ?? [])
|
|
636
|
-
deps.add(path4.resolve(dep));
|
|
637
|
-
const sortedDeps = [...deps].sort();
|
|
638
|
-
clientDepsByEntry[originalEntry] = sortedDeps;
|
|
639
|
-
if (routeEntries.has(originalEntry))
|
|
640
|
-
for (const dep of sortedDeps)
|
|
641
|
-
clientDeps.add(dep);
|
|
495
|
+
return end;
|
|
496
|
+
};
|
|
497
|
+
var scriptKindFor = (fileName) => fileName.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
498
|
+
var compareNames = (a, b) => {
|
|
499
|
+
const [la, lb] = [a.toLowerCase(), b.toLowerCase()];
|
|
500
|
+
if (la !== lb)
|
|
501
|
+
return la < lb ? -1 : 1;
|
|
502
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
503
|
+
};
|
|
504
|
+
var formatError = (err) => err instanceof Error ? err.message : String(err);
|
|
505
|
+
var fileExists = async (file) => stat(file).then((s) => s.isFile()).catch(() => false);
|
|
506
|
+
var domainKindOf = (base) => {
|
|
507
|
+
if (base.endsWith(".constant.ts"))
|
|
508
|
+
return "constant";
|
|
509
|
+
if (base.endsWith(".document.ts"))
|
|
510
|
+
return "document";
|
|
511
|
+
if (base.endsWith(".signal.ts"))
|
|
512
|
+
return "signal";
|
|
513
|
+
return null;
|
|
514
|
+
};
|
|
515
|
+
var buildDomainIndex = async (libDir) => {
|
|
516
|
+
const index = new Map;
|
|
517
|
+
for (const file of await collectDomainFiles(libDir)) {
|
|
518
|
+
const kind = domainKindOf(path.basename(file));
|
|
519
|
+
if (!kind)
|
|
520
|
+
continue;
|
|
521
|
+
const src = await readFile(file, "utf8").catch(() => null);
|
|
522
|
+
if (src === null)
|
|
523
|
+
continue;
|
|
524
|
+
for (const name of exportedPascalNames(src, file)) {
|
|
525
|
+
const entries = index.get(name) ?? [];
|
|
526
|
+
entries.push({ file, kind });
|
|
527
|
+
index.set(name, entries);
|
|
642
528
|
}
|
|
643
|
-
return {
|
|
644
|
-
manifestDelta,
|
|
645
|
-
ssrManifestDelta: { moduleLoading: null, moduleMap: ssrModuleMap },
|
|
646
|
-
newEntries: entries.filter((e) => !this.#knownEntries.has(e)),
|
|
647
|
-
discoveredEntries: discovered,
|
|
648
|
-
clientDeps: [...clientDeps].sort(),
|
|
649
|
-
clientDepsByEntry,
|
|
650
|
-
...registry ? { registryEntries: [...acceptedEntries].sort() } : {}
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
static #registryRow(paths, entry, row) {
|
|
654
|
-
return {
|
|
655
|
-
id: `${SSR_DEV_ID_PREFIX}${paths.idOf(entry)}`,
|
|
656
|
-
chunks: [SSR_DEV_CHUNK, SSR_DEV_CHUNK],
|
|
657
|
-
name: row.name,
|
|
658
|
-
async: true
|
|
659
|
-
};
|
|
660
529
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
530
|
+
return index;
|
|
531
|
+
};
|
|
532
|
+
var collectDomainFiles = async (dir) => {
|
|
533
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
534
|
+
const files = [];
|
|
535
|
+
for (const entry of entries) {
|
|
536
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
537
|
+
continue;
|
|
538
|
+
const full = path.join(dir, entry.name);
|
|
539
|
+
if (entry.isDirectory())
|
|
540
|
+
files.push(...await collectDomainFiles(full));
|
|
541
|
+
else if (domainKindOf(entry.name) && !TEST_FILE_RE.test(entry.name))
|
|
542
|
+
files.push(full);
|
|
664
543
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
544
|
+
return files;
|
|
545
|
+
};
|
|
546
|
+
var exportedPascalNames = (source, fileName) => {
|
|
547
|
+
const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true);
|
|
548
|
+
const isExported = (node) => ts.canHaveModifiers(node) && (ts.getModifiers(node) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
549
|
+
const names = [];
|
|
550
|
+
for (const stmt of sf.statements) {
|
|
551
|
+
if ((ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt) || ts.isEnumDeclaration(stmt)) && stmt.name) {
|
|
552
|
+
if (isExported(stmt))
|
|
553
|
+
names.push(stmt.name.text);
|
|
554
|
+
} else if (ts.isVariableStatement(stmt) && isExported(stmt)) {
|
|
555
|
+
for (const decl of stmt.declarationList.declarations)
|
|
556
|
+
if (ts.isIdentifier(decl.name))
|
|
557
|
+
names.push(decl.name.text);
|
|
558
|
+
} else if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
|
|
559
|
+
for (const el of stmt.exportClause.elements)
|
|
560
|
+
names.push(el.name.text);
|
|
561
|
+
}
|
|
674
562
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
563
|
+
return names.filter((name) => PASCAL_CASE_RE.test(name));
|
|
564
|
+
};
|
|
565
|
+
var relativeSpecifier = (fromDir, toFile) => {
|
|
566
|
+
const rel = path.relative(fromDir, toFile).replace(/\.tsx?$/, "").split(path.sep).join("/");
|
|
567
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// pkgs/@akanjs/devkit/frontendBuild/clientEntryDiscovery.ts
|
|
571
|
+
import fs from "fs";
|
|
572
|
+
import path2 from "path";
|
|
573
|
+
var USE_CLIENT_RE = /^\s*(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*|\/\/[^\n]*\n\s*)*["']use client["']/;
|
|
574
|
+
var NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
|
|
575
|
+
var AKANJS_NODE_MODULE_RE = /[\\/]node_modules[\\/]akanjs[\\/]/;
|
|
576
|
+
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;
|
|
577
|
+
var shouldSkipNodeModule = (absPath) => NODE_MODULES_RE.test(absPath) && !AKANJS_NODE_MODULE_RE.test(absPath);
|
|
578
|
+
|
|
579
|
+
class GraphClientEntryDiscovery {
|
|
580
|
+
#akanConfig;
|
|
581
|
+
#resolvePackage;
|
|
582
|
+
#analyzer;
|
|
583
|
+
#tsTranspiler = new Bun.Transpiler({ loader: "tsx" });
|
|
584
|
+
#fileExistsCache = new Map;
|
|
585
|
+
#factsCache = new Map;
|
|
586
|
+
#resolvedFileCache = new Map;
|
|
587
|
+
#resolvedSpecifierCache = new Map;
|
|
588
|
+
#reachableEntriesCache = new Map;
|
|
589
|
+
#readFiles = new Set;
|
|
590
|
+
#epoch = 0;
|
|
591
|
+
constructor(akanConfig, resolvePackage) {
|
|
592
|
+
this.#akanConfig = akanConfig;
|
|
593
|
+
this.#resolvePackage = resolvePackage;
|
|
594
|
+
this.#analyzer = new BarrelAnalyzer({ resolvePackage });
|
|
689
595
|
}
|
|
690
|
-
async
|
|
691
|
-
return new
|
|
692
|
-
app: this.#app,
|
|
693
|
-
entries: bootstrapEntries.buildEntries,
|
|
694
|
-
plugins: [await createBarrelImportsPlugin(this.#app)],
|
|
695
|
-
...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
|
|
696
|
-
outputSubdir: "client-ssr",
|
|
697
|
-
command: this.#command
|
|
698
|
-
}).bundle();
|
|
596
|
+
static async create(app) {
|
|
597
|
+
return new GraphClientEntryDiscovery(await app.getConfig(), await createTsconfigPackageResolver(app));
|
|
699
598
|
}
|
|
700
|
-
async
|
|
701
|
-
|
|
702
|
-
|
|
599
|
+
async discover(seeds) {
|
|
600
|
+
const entries = new Set;
|
|
601
|
+
for (const seed of seeds) {
|
|
602
|
+
const walk = { visiting: new Set, partial: new Set, memo: new Map };
|
|
603
|
+
for (const entry of await this.#discoverFromFile(seed, walk))
|
|
604
|
+
entries.add(entry);
|
|
703
605
|
}
|
|
704
|
-
|
|
705
|
-
await mkdir(outdir, { recursive: true });
|
|
706
|
-
const originalByBuildEntry = new Map;
|
|
707
|
-
const buildEntries = await Promise.all(entries.map(async (entry) => {
|
|
708
|
-
const absEntry = path4.resolve(entry);
|
|
709
|
-
const hash = Bun.hash(`${this.#app.name}
|
|
710
|
-
${absEntry}`).toString(36);
|
|
711
|
-
const base = path4.basename(absEntry).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
712
|
-
const wrapperEntry = path4.join(outdir, `${base}-${hash}.tsx`);
|
|
713
|
-
const exportNames = await this.#scanExportNames(absEntry);
|
|
714
|
-
await Bun.write(wrapperEntry, RouteClientBuilder.createStoreBootstrapEntrySource({
|
|
715
|
-
appName: this.#app.name,
|
|
716
|
-
originalEntry: absEntry,
|
|
717
|
-
exportNames
|
|
718
|
-
}));
|
|
719
|
-
originalByBuildEntry.set(path4.resolve(wrapperEntry), absEntry);
|
|
720
|
-
return wrapperEntry;
|
|
721
|
-
}));
|
|
722
|
-
return { buildEntries, originalByBuildEntry };
|
|
723
|
-
}
|
|
724
|
-
async#scanExportNames(absEntry) {
|
|
725
|
-
return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
|
|
606
|
+
return Array.from(entries).sort();
|
|
726
607
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
608
|
+
invalidate(files) {
|
|
609
|
+
if (files.length === 0)
|
|
610
|
+
return;
|
|
611
|
+
let moved = false;
|
|
612
|
+
for (const file of files) {
|
|
613
|
+
const absPath = path2.resolve(file);
|
|
614
|
+
const exists = fs.existsSync(absPath);
|
|
615
|
+
if (exists !== this.#readFiles.has(absPath))
|
|
616
|
+
moved = true;
|
|
617
|
+
if (!exists)
|
|
618
|
+
this.#readFiles.delete(absPath);
|
|
619
|
+
this.#factsCache.delete(absPath);
|
|
620
|
+
this.#fileExistsCache.delete(absPath);
|
|
736
621
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
622
|
+
this.#epoch += 1;
|
|
623
|
+
this.#reachableEntriesCache.clear();
|
|
624
|
+
if (!moved)
|
|
625
|
+
return;
|
|
626
|
+
this.#fileExistsCache.clear();
|
|
627
|
+
this.#resolvedFileCache.clear();
|
|
628
|
+
this.#resolvedSpecifierCache.clear();
|
|
742
629
|
}
|
|
743
|
-
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
name: match[4] ?? ""
|
|
753
|
-
}));
|
|
754
|
-
if (candidates.length === 0)
|
|
755
|
-
return [];
|
|
756
|
-
const probeName = (idx) => `__akanNamedDefault${idx}`;
|
|
757
|
-
let probe = "";
|
|
758
|
-
let cursor = 0;
|
|
759
|
-
candidates.forEach(({ index, text, lineStart, indent, asyncKeyword }, idx) => {
|
|
760
|
-
probe += `${source.slice(cursor, index)}${lineStart}${indent}export ${asyncKeyword}function ${probeName(idx)}`;
|
|
761
|
-
cursor = index + text.length;
|
|
630
|
+
#lookup(cache, key, run, isFound) {
|
|
631
|
+
const cached = cache.get(key);
|
|
632
|
+
if (cached && (cached.found || cached.epoch === this.#epoch))
|
|
633
|
+
return cached.promise;
|
|
634
|
+
const entry = { epoch: this.#epoch, promise: run() };
|
|
635
|
+
entry.promise.then((value) => {
|
|
636
|
+
entry.found = isFound(value);
|
|
637
|
+
}, () => {
|
|
638
|
+
entry.found = false;
|
|
762
639
|
});
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
return candidates.filter((_, idx) => exported.has(probeName(idx)));
|
|
640
|
+
cache.set(key, entry);
|
|
641
|
+
return entry.promise;
|
|
766
642
|
}
|
|
767
|
-
|
|
643
|
+
async#fileExists(p) {
|
|
644
|
+
const absPath = path2.resolve(p);
|
|
645
|
+
return await this.#lookup(this.#fileExistsCache, absPath, () => Bun.file(absPath).exists(), (exists) => exists);
|
|
646
|
+
}
|
|
647
|
+
#facts(file) {
|
|
648
|
+
const absPath = path2.resolve(file);
|
|
649
|
+
let cached = this.#factsCache.get(absPath);
|
|
650
|
+
if (!cached) {
|
|
651
|
+
cached = (async () => {
|
|
652
|
+
const content = await Bun.file(absPath).text().catch(() => null);
|
|
653
|
+
if (content === null)
|
|
654
|
+
return null;
|
|
655
|
+
this.#readFiles.add(absPath);
|
|
656
|
+
if (USE_CLIENT_RE.test(content))
|
|
657
|
+
return { isClientEntry: true, imports: [] };
|
|
658
|
+
return { isClientEntry: false, imports: this.#scanImports(await this.#rewrite(content)) };
|
|
659
|
+
})();
|
|
660
|
+
this.#factsCache.set(absPath, cached);
|
|
661
|
+
}
|
|
662
|
+
return cached;
|
|
663
|
+
}
|
|
664
|
+
async#rewrite(content) {
|
|
665
|
+
if (this.#akanConfig.barrelImports.length === 0)
|
|
666
|
+
return content;
|
|
768
667
|
try {
|
|
769
|
-
return
|
|
668
|
+
return await rewriteBarrelImports(content, this.#akanConfig.barrelImports, this.#analyzer) ?? content;
|
|
669
|
+
} catch {
|
|
670
|
+
return content;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
#scanImports(source) {
|
|
674
|
+
try {
|
|
675
|
+
return this.#tsTranspiler.scanImports(source);
|
|
770
676
|
} catch {
|
|
677
|
+
return [];
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
async#resolveFileCandidate(absPathNoExt) {
|
|
681
|
+
const cacheKey = path2.resolve(absPathNoExt);
|
|
682
|
+
return await this.#lookup(this.#resolvedFileCache, cacheKey, async () => {
|
|
683
|
+
if (await this.#fileExists(cacheKey))
|
|
684
|
+
return cacheKey;
|
|
685
|
+
for (const ext of SOURCE_EXTS) {
|
|
686
|
+
const f = `${cacheKey}${ext}`;
|
|
687
|
+
if (await this.#fileExists(f))
|
|
688
|
+
return f;
|
|
689
|
+
}
|
|
690
|
+
for (const ext of SOURCE_EXTS) {
|
|
691
|
+
const f = path2.join(cacheKey, `index${ext}`);
|
|
692
|
+
if (await this.#fileExists(f))
|
|
693
|
+
return f;
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
}, (file) => file !== null);
|
|
697
|
+
}
|
|
698
|
+
async#resolveSpecifier(spec, importerDir) {
|
|
699
|
+
const cacheKey = `${importerDir}\x00${spec}`;
|
|
700
|
+
return await this.#lookup(this.#resolvedSpecifierCache, cacheKey, async () => {
|
|
701
|
+
if (spec.startsWith(".") || spec.startsWith("/")) {
|
|
702
|
+
const abs = spec.startsWith("/") ? spec : path2.resolve(importerDir, spec);
|
|
703
|
+
return await this.#resolveFileCandidate(abs);
|
|
704
|
+
}
|
|
705
|
+
const pkg = await this.#resolvePackage(spec);
|
|
706
|
+
return pkg ? pkg.entryFile : null;
|
|
707
|
+
}, (file) => file !== null);
|
|
708
|
+
}
|
|
709
|
+
async#discoverFromFile(file, walk) {
|
|
710
|
+
const epoch = this.#epoch;
|
|
711
|
+
const absPath = path2.resolve(file);
|
|
712
|
+
const cached = this.#reachableEntriesCache.get(absPath);
|
|
713
|
+
if (cached)
|
|
714
|
+
return new Set(cached);
|
|
715
|
+
const memo = walk.memo.get(absPath);
|
|
716
|
+
if (memo) {
|
|
717
|
+
if (walk.partial.has(absPath))
|
|
718
|
+
for (const onPath of walk.visiting)
|
|
719
|
+
walk.partial.add(onPath);
|
|
720
|
+
return new Set(memo);
|
|
721
|
+
}
|
|
722
|
+
if (walk.visiting.has(absPath)) {
|
|
723
|
+
let below = false;
|
|
724
|
+
for (const onPath of walk.visiting) {
|
|
725
|
+
if (below)
|
|
726
|
+
walk.partial.add(onPath);
|
|
727
|
+
else
|
|
728
|
+
below = onPath === absPath;
|
|
729
|
+
}
|
|
771
730
|
return new Set;
|
|
772
731
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
externalSubpaths: ["akanjs/fetch"],
|
|
784
|
-
externalAliases: RouteClientBuilder.resolveSsrClientRuntimeAliases()
|
|
785
|
-
};
|
|
732
|
+
if (shouldSkipNodeModule(absPath))
|
|
733
|
+
return new Set;
|
|
734
|
+
walk.visiting.add(absPath);
|
|
735
|
+
const entries = new Set;
|
|
736
|
+
const facts = await this.#facts(absPath);
|
|
737
|
+
if (!facts)
|
|
738
|
+
return this.#finishDiscovery(absPath, walk, entries, epoch);
|
|
739
|
+
if (facts.isClientEntry) {
|
|
740
|
+
entries.add(absPath);
|
|
741
|
+
return this.#finishDiscovery(absPath, walk, entries, epoch);
|
|
786
742
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
743
|
+
const importerDir = path2.dirname(absPath);
|
|
744
|
+
for (const imp of facts.imports) {
|
|
745
|
+
const spec = imp.path;
|
|
746
|
+
if (!spec)
|
|
747
|
+
continue;
|
|
748
|
+
if (NON_SOURCE_EXT_RE.test(spec))
|
|
749
|
+
continue;
|
|
750
|
+
const resolved = await this.#resolveSpecifier(spec, importerDir);
|
|
751
|
+
if (!resolved)
|
|
752
|
+
continue;
|
|
753
|
+
if (shouldSkipNodeModule(resolved))
|
|
754
|
+
continue;
|
|
755
|
+
for (const entry of await this.#discoverFromFile(resolved, walk))
|
|
756
|
+
entries.add(entry);
|
|
794
757
|
}
|
|
758
|
+
return this.#finishDiscovery(absPath, walk, entries, epoch);
|
|
795
759
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
];
|
|
803
|
-
if (args.exportNames.includes("default"))
|
|
804
|
-
lines.push(`export { default } from ${originalEntry};`);
|
|
805
|
-
return `${lines.join(`
|
|
806
|
-
`)}
|
|
807
|
-
`;
|
|
808
|
-
}
|
|
809
|
-
static resolveOriginalManifestEntry(manifestKey, originalByBuildEntry, clientReferenceIdByBuildEntry = new Map, workspaceRoot = process.cwd()) {
|
|
810
|
-
const hashIdx = manifestKey.lastIndexOf("#");
|
|
811
|
-
if (hashIdx < 0)
|
|
812
|
-
return null;
|
|
813
|
-
const buildReferenceId = manifestKey.slice(0, hashIdx);
|
|
814
|
-
const name = manifestKey.slice(hashIdx + 1);
|
|
815
|
-
const buildEntry = [...clientReferenceIdByBuildEntry.entries()].find(([, referenceId]) => referenceId === buildReferenceId)?.[0] ?? buildReferenceId;
|
|
816
|
-
const originalEntry = originalByBuildEntry.get(buildEntry) ?? buildEntry;
|
|
817
|
-
const originalReferenceId = toClientReferencePath(originalEntry, workspaceRoot);
|
|
818
|
-
return { buildEntry, originalEntry, name, key: `${originalReferenceId}#${name}` };
|
|
760
|
+
#finishDiscovery(absPath, walk, entries, epoch) {
|
|
761
|
+
walk.visiting.delete(absPath);
|
|
762
|
+
walk.memo.set(absPath, entries);
|
|
763
|
+
if (epoch === this.#epoch && !walk.partial.has(absPath))
|
|
764
|
+
this.#reachableEntriesCache.set(absPath, entries);
|
|
765
|
+
return new Set(entries);
|
|
819
766
|
}
|
|
820
767
|
}
|
|
821
768
|
|
|
822
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
823
|
-
import {
|
|
824
|
-
import
|
|
825
|
-
import
|
|
826
|
-
var TEST_FILE_RE = /\.(test|spec)\.(ts|tsx)$/;
|
|
827
|
-
var AKAN_BASE = {
|
|
828
|
-
names: ["Int", "Float", "ID", "Any", "Upload", "enumOf", "dayjs"],
|
|
829
|
-
specifier: "akanjs/base",
|
|
830
|
-
kind: "named"
|
|
831
|
-
};
|
|
832
|
-
var AKAN_BASE_TYPES = { names: ["Dayjs"], specifier: "akanjs/base", kind: "named", typeOnly: true };
|
|
833
|
-
var CNST_NS = { names: ["cnst"], specifier: "../cnst", kind: "namespace" };
|
|
834
|
-
var DB_NS = { names: ["db"], specifier: "../db", kind: "namespace" };
|
|
835
|
-
var SRV_NS = { names: ["srv"], specifier: "../srv", kind: "namespace" };
|
|
836
|
-
var ERR_DICT = { names: ["Err"], specifier: "../dict", kind: "named" };
|
|
837
|
-
var RULES = {
|
|
838
|
-
constant: [AKAN_BASE, AKAN_BASE_TYPES, { names: ["via"], specifier: "akanjs/constant", kind: "named" }],
|
|
839
|
-
document: [
|
|
840
|
-
AKAN_BASE,
|
|
841
|
-
AKAN_BASE_TYPES,
|
|
842
|
-
CNST_NS,
|
|
843
|
-
DB_NS,
|
|
844
|
-
ERR_DICT,
|
|
845
|
-
{
|
|
846
|
-
names: ["by", "from", "into", "SchemaOf", "DataInputOf", "DocumentUpdateHelper", "documentQueryHelper", "Mdl"],
|
|
847
|
-
specifier: "akanjs/document",
|
|
848
|
-
kind: "named"
|
|
849
|
-
}
|
|
850
|
-
],
|
|
851
|
-
service: [
|
|
852
|
-
AKAN_BASE,
|
|
853
|
-
AKAN_BASE_TYPES,
|
|
854
|
-
DB_NS,
|
|
855
|
-
SRV_NS,
|
|
856
|
-
CNST_NS,
|
|
857
|
-
ERR_DICT,
|
|
858
|
-
{ names: ["serve"], specifier: "akanjs/service", kind: "named" },
|
|
859
|
-
{
|
|
860
|
-
names: ["DataInputOf", "ListQueryOption", "DatabaseRegistry", "getFilterInfoByKey"],
|
|
861
|
-
specifier: "akanjs/document",
|
|
862
|
-
kind: "named"
|
|
863
|
-
}
|
|
864
|
-
],
|
|
865
|
-
signal: [
|
|
866
|
-
AKAN_BASE,
|
|
867
|
-
AKAN_BASE_TYPES,
|
|
868
|
-
SRV_NS,
|
|
869
|
-
CNST_NS,
|
|
870
|
-
ERR_DICT,
|
|
871
|
-
{
|
|
872
|
-
names: ["endpoint", "internal", "slice", "Public", "Req", "Ws", "None", "Res"],
|
|
873
|
-
specifier: "akanjs/signal",
|
|
874
|
-
kind: "named"
|
|
875
|
-
}
|
|
876
|
-
],
|
|
877
|
-
dictionary: [
|
|
878
|
-
{
|
|
879
|
-
names: ["modelDictionary", "scalarDictionary", "serviceDictionary"],
|
|
880
|
-
specifier: "akanjs/dictionary",
|
|
881
|
-
kind: "named"
|
|
882
|
-
}
|
|
883
|
-
],
|
|
884
|
-
srvkit: [
|
|
885
|
-
AKAN_BASE,
|
|
886
|
-
AKAN_BASE_TYPES,
|
|
887
|
-
{ names: ["Logger", "HttpClient", "sleep"], specifier: "akanjs/common", kind: "named" },
|
|
888
|
-
{ names: ["adapt"], specifier: "akanjs/service", kind: "named" },
|
|
889
|
-
{ names: ["SignalContext", "Guard", "InternalArg", "Middleware"], specifier: "akanjs/signal", kind: "named" }
|
|
890
|
-
],
|
|
891
|
-
common: [AKAN_BASE, AKAN_BASE_TYPES],
|
|
892
|
-
store: [
|
|
893
|
-
CNST_NS,
|
|
894
|
-
{ names: ["store"], specifier: "akanjs/store", kind: "named" },
|
|
895
|
-
{ names: ["fetch", "usePage", "sig"], specifier: "../useClient", kind: "named" },
|
|
896
|
-
{ names: ["st"], specifier: "../st", kind: "named" },
|
|
897
|
-
{ names: ["RootStore"], specifier: "../st", kind: "named", typeOnly: true }
|
|
898
|
-
],
|
|
899
|
-
client: [
|
|
900
|
-
{
|
|
901
|
-
names: ["cnst", "fetch", "st", "usePage"],
|
|
902
|
-
specifier: (ctx) => `@${ctx.scope}/${ctx.project}/client`,
|
|
903
|
-
kind: "named"
|
|
904
|
-
}
|
|
905
|
-
]
|
|
906
|
-
};
|
|
907
|
-
var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
908
|
-
var DOMAIN_ROLE_KINDS = {
|
|
909
|
-
constant: ["constant"],
|
|
910
|
-
dictionary: ["constant", "document", "signal"],
|
|
911
|
-
common: ["constant"]
|
|
912
|
-
};
|
|
913
|
-
var LIB_BARRELS = {
|
|
914
|
-
Err: { barrel: "dict", kind: "named" },
|
|
915
|
-
db: { barrel: "db", kind: "namespace" },
|
|
916
|
-
cnst: { barrel: "cnst", kind: "namespace" },
|
|
917
|
-
srv: { barrel: "srv", kind: "namespace" }
|
|
918
|
-
};
|
|
919
|
-
var GLOBAL_NAMES = new Set(["fetch"]);
|
|
920
|
-
var DOMAIN_DENYLIST = new Set([
|
|
921
|
-
"Map",
|
|
922
|
-
"Set",
|
|
923
|
-
"WeakMap",
|
|
924
|
-
"WeakSet",
|
|
925
|
-
"Date",
|
|
926
|
-
"Promise",
|
|
927
|
-
"Array",
|
|
928
|
-
"Object",
|
|
929
|
-
"Error",
|
|
930
|
-
"TypeError",
|
|
931
|
-
"RangeError",
|
|
932
|
-
"RegExp",
|
|
933
|
-
"Symbol",
|
|
934
|
-
"Proxy",
|
|
935
|
-
"Reflect",
|
|
936
|
-
"JSON",
|
|
937
|
-
"Math",
|
|
938
|
-
"Number",
|
|
939
|
-
"BigInt",
|
|
940
|
-
"Function",
|
|
941
|
-
"Boolean",
|
|
942
|
-
"String",
|
|
943
|
-
"Record",
|
|
944
|
-
"Partial",
|
|
945
|
-
"Required",
|
|
946
|
-
"Readonly",
|
|
947
|
-
"Pick",
|
|
948
|
-
"Omit",
|
|
949
|
-
"Exclude",
|
|
950
|
-
"Extract",
|
|
951
|
-
"NonNullable",
|
|
952
|
-
"ReturnType",
|
|
953
|
-
"Parameters",
|
|
954
|
-
"Awaited",
|
|
955
|
-
"InstanceType"
|
|
956
|
-
]);
|
|
769
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
|
|
770
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
771
|
+
import path11 from "path";
|
|
772
|
+
import { CSR_DEV_DIRNAME, CSR_DEV_ROUTE_PREFIX } from "akanjs/server/hmr/csrDevManifest";
|
|
957
773
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
774
|
+
// pkgs/@akanjs/devkit/frontendBuild/devRegistryBundler.ts
|
|
775
|
+
import fs7 from "fs";
|
|
776
|
+
import path10 from "path";
|
|
777
|
+
import { isAkanRuntimeMetadataFile } from "akanjs/server/hmr/runtimeMetadataFile";
|
|
778
|
+
|
|
779
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
|
|
780
|
+
import fs2 from "fs";
|
|
781
|
+
import { mkdir, readdir as readdir2, rm } from "fs/promises";
|
|
782
|
+
import path3 from "path";
|
|
783
|
+
import {
|
|
784
|
+
appGenerationOf,
|
|
785
|
+
CSR_DEV_APP_FILE,
|
|
786
|
+
CSR_DEV_KEPT_PATCHES,
|
|
787
|
+
CSR_DEV_MANIFEST_FILE,
|
|
788
|
+
CSR_DEV_PATCHING_MARKER,
|
|
789
|
+
csrDevModuleFile
|
|
790
|
+
} from "akanjs/server/hmr/csrDevManifest";
|
|
791
|
+
class CsrDevArtifactWriter {
|
|
792
|
+
static #keptVendors = 3;
|
|
793
|
+
static #buildingMarker = ".building";
|
|
794
|
+
#outDir;
|
|
795
|
+
#routePrefix;
|
|
796
|
+
#library;
|
|
797
|
+
constructor(outDir, { routePrefix, library = false }) {
|
|
798
|
+
this.#outDir = outDir;
|
|
799
|
+
this.#routePrefix = routePrefix;
|
|
800
|
+
this.#library = library;
|
|
963
801
|
}
|
|
964
|
-
async
|
|
965
|
-
|
|
966
|
-
const errors = [];
|
|
967
|
-
const seen = new Set;
|
|
968
|
-
for (const file of files) {
|
|
969
|
-
const pkgRoot = this.#domainPkgRootOf(file);
|
|
970
|
-
if (pkgRoot)
|
|
971
|
-
this.#domainCache.delete(pkgRoot);
|
|
972
|
-
}
|
|
973
|
-
for (const file of files) {
|
|
974
|
-
const abs = path5.resolve(file);
|
|
975
|
-
if (seen.has(abs))
|
|
976
|
-
continue;
|
|
977
|
-
seen.add(abs);
|
|
978
|
-
const ctx = this.#contextFor(abs);
|
|
979
|
-
if (!ctx)
|
|
980
|
-
continue;
|
|
981
|
-
try {
|
|
982
|
-
const changed = await this.#syncFile(abs, ctx);
|
|
983
|
-
if (changed)
|
|
984
|
-
changedFiles.push(abs);
|
|
985
|
-
} catch (err) {
|
|
986
|
-
errors.push(`[auto-import] sync failed for ${file}: ${formatError(err)}`);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
return { changedFiles, errors };
|
|
802
|
+
async markBuilding() {
|
|
803
|
+
await Bun.write(path3.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker), String(process.pid));
|
|
990
804
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
if (rel.startsWith("..") || path5.isAbsolute(rel))
|
|
994
|
-
return null;
|
|
995
|
-
const base = path5.basename(abs);
|
|
996
|
-
if (TEST_FILE_RE.test(base) || base === "index.ts" || base === "index.tsx" || base.endsWith(".d.ts"))
|
|
997
|
-
return null;
|
|
998
|
-
const parts = rel.split(path5.sep).filter(Boolean);
|
|
999
|
-
const [scope, project, facet] = parts;
|
|
1000
|
-
if (scope !== "apps" && scope !== "libs" || !project || !facet)
|
|
1001
|
-
return null;
|
|
1002
|
-
if (facet === "lib" && parts[3] === "__lib")
|
|
1003
|
-
return null;
|
|
1004
|
-
const role = roleFor(facet, base);
|
|
1005
|
-
if (!role)
|
|
1006
|
-
return null;
|
|
1007
|
-
return { role, scope, project };
|
|
805
|
+
async clearBuilding() {
|
|
806
|
+
await CsrDevArtifactWriter.#remove(path3.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker));
|
|
1008
807
|
}
|
|
1009
|
-
async
|
|
1010
|
-
|
|
1011
|
-
if (!stats?.isFile())
|
|
1012
|
-
return false;
|
|
1013
|
-
const source = await readFile(abs, "utf8");
|
|
1014
|
-
const resolveExtra = await this.#extraResolverFor(abs, ctx);
|
|
1015
|
-
const next = transformSource(source, abs, ctx, resolveExtra);
|
|
1016
|
-
if (next === null || next === source)
|
|
1017
|
-
return false;
|
|
1018
|
-
await FileSys2.writeTextAtomic(abs, next);
|
|
1019
|
-
return true;
|
|
808
|
+
async isBuilding() {
|
|
809
|
+
return await Bun.file(path3.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker)).exists();
|
|
1020
810
|
}
|
|
1021
|
-
async
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
if (
|
|
1027
|
-
resolvers.push(await this.#domainResolver(abs, ctx, kinds));
|
|
1028
|
-
if (resolvers.length === 0)
|
|
1029
|
-
return;
|
|
1030
|
-
return (symbol) => {
|
|
1031
|
-
for (const resolve of resolvers) {
|
|
1032
|
-
const target = resolve(symbol);
|
|
1033
|
-
if (target)
|
|
1034
|
-
return target;
|
|
1035
|
-
}
|
|
811
|
+
async forgetPatch(generation) {
|
|
812
|
+
await Promise.all([`patch-${generation}.js`, `patch-${generation}.js.layout.json`].map((name) => CsrDevArtifactWriter.#remove(path3.join(this.#outDir, name))));
|
|
813
|
+
}
|
|
814
|
+
async readJson(name) {
|
|
815
|
+
const file = Bun.file(path3.join(this.#outDir, name));
|
|
816
|
+
if (!await file.exists())
|
|
1036
817
|
return null;
|
|
1037
|
-
|
|
818
|
+
return await file.json().catch(() => null);
|
|
1038
819
|
}
|
|
1039
|
-
async
|
|
1040
|
-
|
|
1041
|
-
const fileDir = path5.dirname(abs);
|
|
1042
|
-
return (symbol) => {
|
|
1043
|
-
if (!PASCAL_CASE_RE.test(symbol) || DOMAIN_DENYLIST.has(symbol))
|
|
1044
|
-
return null;
|
|
1045
|
-
const entries = (index.get(symbol) ?? []).filter((entry) => kinds.includes(entry.kind));
|
|
1046
|
-
if (entries.length !== 1)
|
|
1047
|
-
return null;
|
|
1048
|
-
return { specifier: relativeSpecifier(fileDir, entries[0].file), kind: "named" };
|
|
1049
|
-
};
|
|
820
|
+
async writeJson(name, value) {
|
|
821
|
+
await this.#writeAtomic(name, JSON.stringify(value));
|
|
1050
822
|
}
|
|
1051
|
-
async
|
|
1052
|
-
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
823
|
+
async writeModules(compiled) {
|
|
824
|
+
await mkdir(path3.join(this.#outDir, "modules"), { recursive: true });
|
|
825
|
+
const helpers = new Map(compiled.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
|
|
826
|
+
await Promise.all([
|
|
827
|
+
...compiled.flatMap((module) => [
|
|
828
|
+
Bun.write(this.#modulePath(module.id, ".js"), module.factory),
|
|
829
|
+
...module.sourceMap ? [Bun.write(this.#modulePath(module.id, ".js.map"), module.sourceMap)] : []
|
|
830
|
+
]),
|
|
831
|
+
...[...helpers.values()].map((shared) => Bun.write(this.#helpersPath(shared.hash), shared.definition))
|
|
832
|
+
]);
|
|
833
|
+
}
|
|
834
|
+
async readCode(graph) {
|
|
835
|
+
const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && this.hasModule(id));
|
|
836
|
+
const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))];
|
|
837
|
+
const [modules, helpers] = await Promise.all([
|
|
838
|
+
Promise.all(ids.map(async (id) => [id, await Bun.file(this.#modulePath(id, ".js")).text()])),
|
|
839
|
+
Promise.all(hashes.map(async (hash) => [hash, await Bun.file(this.#helpersPath(hash)).text()]))
|
|
840
|
+
]);
|
|
841
|
+
return { modules: new Map(modules), helpers: new Map(helpers) };
|
|
842
|
+
}
|
|
843
|
+
hasModule(id) {
|
|
844
|
+
return fs2.existsSync(this.#modulePath(id, ".js"));
|
|
845
|
+
}
|
|
846
|
+
async forgetModules(ids) {
|
|
847
|
+
await Promise.all(ids.flatMap((id) => [
|
|
848
|
+
CsrDevArtifactWriter.#remove(this.#modulePath(id, ".js")),
|
|
849
|
+
CsrDevArtifactWriter.#remove(this.#modulePath(id, ".js.map"))
|
|
850
|
+
]));
|
|
851
|
+
}
|
|
852
|
+
async writeVendor(graph) {
|
|
853
|
+
const ids = Object.keys(graph.modules).filter((id) => graph.modules[id]?.vendor).sort();
|
|
854
|
+
const stubs = new Set(Object.values(graph.modules).flatMap((module) => module.deps.filter(CsrDevPaths.isStub)));
|
|
855
|
+
const lines = [await this.#helperDefinitions(graph, ids), ...await this.#defineLines(ids)];
|
|
856
|
+
for (const stub of [...stubs].sort()) {
|
|
857
|
+
if (stub.startsWith(CsrDevPaths.emptyStubPrefix)) {
|
|
858
|
+
lines.push(`__akan.define(${JSON.stringify(stub)}, function () {});
|
|
859
|
+
`);
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
const message = `[akan-csr] ${stub.slice(CsrDevPaths.stubPrefix.length)} is a Node built-in the browser does not have`;
|
|
863
|
+
lines.push(`__akan.define(${JSON.stringify(stub)}, function () {
|
|
864
|
+
throw new Error(${JSON.stringify(message)});
|
|
865
|
+
});
|
|
866
|
+
`);
|
|
1058
867
|
}
|
|
1059
|
-
|
|
868
|
+
const code = lines.join("");
|
|
869
|
+
const vendorFile = `vendor-${Bun.hash(code).toString(36)}.js`;
|
|
870
|
+
await this.#writeAtomic(vendorFile, code);
|
|
871
|
+
return vendorFile;
|
|
1060
872
|
}
|
|
1061
|
-
async
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
873
|
+
async writeApp(graph, { generation, vendorFile, epoch }, code) {
|
|
874
|
+
const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor).sort();
|
|
875
|
+
const lines = await this.#defineLines(ids, code);
|
|
876
|
+
const blocks = ids.map((id, index) => [id, lines[index] ?? ""]);
|
|
877
|
+
const start = this.#library ? `__akan.startLibrary(${JSON.stringify({ generation, refresh: graph.refresh, bootstrap: graph.entries[""], vendorFile, epoch })});
|
|
878
|
+
` : `__akan.start(${JSON.stringify({ generation, refresh: graph.refresh })});
|
|
879
|
+
`;
|
|
880
|
+
await this.#writeWithSourceMap(CSR_DEV_APP_FILE, await this.#helperDefinitions(graph, ids, code), blocks, start);
|
|
1068
881
|
}
|
|
1069
|
-
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
882
|
+
async writePatch(generation, modules) {
|
|
883
|
+
const patchFile = `patch-${generation}.js`;
|
|
884
|
+
const blocks = modules.map((module, index) => [module.id, `${JSON.stringify(module.id)}: ${module.factory}${index < modules.length - 1 ? `,
|
|
885
|
+
` : `
|
|
886
|
+
`}`]);
|
|
887
|
+
const helpers = new Map(modules.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
|
|
888
|
+
const header = `${[...helpers.values()].map((shared) => shared.definition).join("")}__akan.update(${generation}, {
|
|
889
|
+
`;
|
|
890
|
+
await this.#writeWithSourceMap(patchFile, header, blocks, `});
|
|
891
|
+
`);
|
|
892
|
+
return `${this.#routePrefix}${patchFile}`;
|
|
1079
893
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
if (base.endsWith(".constant.ts"))
|
|
1084
|
-
return "constant";
|
|
1085
|
-
if (base.endsWith(".document.ts"))
|
|
1086
|
-
return "document";
|
|
1087
|
-
if (base.endsWith(".service.ts"))
|
|
1088
|
-
return "service";
|
|
1089
|
-
if (base.endsWith(".signal.ts"))
|
|
1090
|
-
return "signal";
|
|
1091
|
-
if (base.endsWith(".dictionary.ts"))
|
|
1092
|
-
return "dictionary";
|
|
1093
|
-
if (base.endsWith(".store.ts"))
|
|
1094
|
-
return "store";
|
|
1095
|
-
if (base.endsWith(".tsx"))
|
|
1096
|
-
return "client";
|
|
1097
|
-
return null;
|
|
894
|
+
async writeState(graph, manifest) {
|
|
895
|
+
await this.writeJson("graph.json", graph);
|
|
896
|
+
await this.writeJson(CSR_DEV_MANIFEST_FILE, manifest);
|
|
1098
897
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
return null;
|
|
1108
|
-
};
|
|
1109
|
-
var targetFor = (symbol, ctx) => {
|
|
1110
|
-
for (const rule of RULES[ctx.role]) {
|
|
1111
|
-
if (!rule.names.includes(symbol))
|
|
1112
|
-
continue;
|
|
1113
|
-
const specifier = typeof rule.specifier === "function" ? rule.specifier(ctx) : rule.specifier;
|
|
1114
|
-
return { specifier, kind: rule.kind, typeOnly: rule.typeOnly };
|
|
898
|
+
async commitPatch(graph, manifest, announce, { modules, code }) {
|
|
899
|
+
await this.writeJson(CSR_DEV_MANIFEST_FILE, manifest);
|
|
900
|
+
announce();
|
|
901
|
+
await CsrDevArtifactWriter.#appWriteDelay();
|
|
902
|
+
await this.writeModules(modules);
|
|
903
|
+
await this.writeJson("graph.json", graph);
|
|
904
|
+
await this.writeApp(graph, manifest, code);
|
|
905
|
+
await this.writeJson(CSR_DEV_MANIFEST_FILE, { ...manifest, appGeneration: manifest.generation });
|
|
1115
906
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
907
|
+
async healApp(graph, manifest, code) {
|
|
908
|
+
if (appGenerationOf(manifest) >= manifest.generation)
|
|
909
|
+
return manifest;
|
|
910
|
+
await this.writeApp(graph, manifest, code);
|
|
911
|
+
const healed = { ...manifest, appGeneration: manifest.generation };
|
|
912
|
+
await this.writeJson(CSR_DEV_MANIFEST_FILE, healed);
|
|
913
|
+
return healed;
|
|
914
|
+
}
|
|
915
|
+
async prune(vendorFile, generation) {
|
|
916
|
+
const names = await readdir2(this.#outDir);
|
|
917
|
+
const stale = names.filter((name) => {
|
|
918
|
+
const patch = /^patch-(\d+)\.js(?:\.layout\.json)?$/.exec(name);
|
|
919
|
+
return patch ? Number(patch[1]) <= generation - CSR_DEV_KEPT_PATCHES : false;
|
|
920
|
+
});
|
|
921
|
+
const vendors = names.filter((name) => /^vendor-[\w-]+\.js$/.test(name) && name !== vendorFile).map((name) => ({ name, mtimeMs: CsrDevPaths.mtimeOf(path3.join(this.#outDir, name)) })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(CsrDevArtifactWriter.#keptVendors - 1).map(({ name }) => name);
|
|
922
|
+
await Promise.all([...stale, ...vendors].map((name) => CsrDevArtifactWriter.#remove(path3.join(this.#outDir, name))));
|
|
923
|
+
}
|
|
924
|
+
async pruneAfterFullBuild(graph, vendorFile, { generation, assets }) {
|
|
925
|
+
const kept = new Set(Object.keys(graph.modules).flatMap((id) => [this.#modulePath(id, ".js"), this.#modulePath(id, ".js.map")]));
|
|
926
|
+
for (const module of Object.values(graph.modules))
|
|
927
|
+
if (module.helpers)
|
|
928
|
+
kept.add(this.#helpersPath(module.helpers));
|
|
929
|
+
for (const asset of assets)
|
|
930
|
+
kept.add(path3.resolve(asset));
|
|
931
|
+
const listed = async (dir) => (await readdir2(path3.join(this.#outDir, dir)).catch(() => [])).map((name) => path3.join(this.#outDir, dir, name));
|
|
932
|
+
const stale = [
|
|
933
|
+
...(await listed("modules")).filter((file) => !kept.has(file)),
|
|
934
|
+
...(await listed("assets")).filter((file) => !kept.has(file)),
|
|
935
|
+
...(await listed(".")).filter((file) => {
|
|
936
|
+
const name = path3.basename(file);
|
|
937
|
+
return /^patch-\d+\.js(?:\.layout\.json)?$/.test(name) || name.endsWith(".tmp");
|
|
938
|
+
}),
|
|
939
|
+
path3.join(this.#outDir, CSR_DEV_PATCHING_MARKER)
|
|
940
|
+
];
|
|
941
|
+
await Promise.all(stale.map((file) => CsrDevArtifactWriter.#remove(file)));
|
|
942
|
+
await this.prune(vendorFile, generation);
|
|
1138
943
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const anchor = importDecls.at(-1) ?? null;
|
|
1143
|
-
const namedImportsBySpecifier = collectExistingNamedImports(importDecls);
|
|
1144
|
-
const edits = [];
|
|
1145
|
-
const newStatements = [];
|
|
1146
|
-
for (const [specifier, names] of namedBySpecifier) {
|
|
1147
|
-
const existing = namedImportsBySpecifier.get(specifier);
|
|
1148
|
-
if (existing) {
|
|
1149
|
-
const merged = new Map(existing.names);
|
|
1150
|
-
for (const [name, isType] of names)
|
|
1151
|
-
if (!merged.has(name))
|
|
1152
|
-
merged.set(name, isType);
|
|
1153
|
-
edits.push({
|
|
1154
|
-
start: existing.decl.getStart(sf),
|
|
1155
|
-
end: existing.decl.getEnd(),
|
|
1156
|
-
text: formatNamedImport(specifier, merged)
|
|
1157
|
-
});
|
|
1158
|
-
} else
|
|
1159
|
-
newStatements.push(formatNamedImport(specifier, names));
|
|
944
|
+
async#helperDefinitions(graph, ids, code) {
|
|
945
|
+
const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))].sort();
|
|
946
|
+
return (await Promise.all(hashes.map(async (hash) => code?.helpers.get(hash) ?? Bun.file(this.#helpersPath(hash)).text()))).join("");
|
|
1160
947
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
${newStatements.join(`
|
|
1168
|
-
`)}`;
|
|
1169
|
-
else if (anchor)
|
|
1170
|
-
edits.push({ start: anchor.getEnd(), end: anchor.getEnd(), text: `
|
|
1171
|
-
${newStatements.join(`
|
|
1172
|
-
`)}` });
|
|
1173
|
-
else {
|
|
1174
|
-
const prologueEnd = directivePrologueEnd(sf);
|
|
1175
|
-
if (prologueEnd >= 0)
|
|
1176
|
-
edits.push({ start: prologueEnd, end: prologueEnd, text: `
|
|
1177
|
-
${newStatements.join(`
|
|
1178
|
-
`)}` });
|
|
1179
|
-
else
|
|
1180
|
-
edits.push({ start: 0, end: 0, text: `${newStatements.join(`
|
|
1181
|
-
`)}
|
|
1182
|
-
|
|
1183
|
-
` });
|
|
1184
|
-
}
|
|
948
|
+
async#defineLines(ids, code) {
|
|
949
|
+
return await Promise.all(ids.map(async (id) => {
|
|
950
|
+
const factory = code?.modules.get(id) ?? await Bun.file(this.#modulePath(id, ".js")).text();
|
|
951
|
+
return `__akan.define(${JSON.stringify(id)}, ${factory});
|
|
952
|
+
`;
|
|
953
|
+
}));
|
|
1185
954
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
return out === source ? null : out;
|
|
1193
|
-
};
|
|
1194
|
-
var collectBoundNames = (sf) => {
|
|
1195
|
-
const names = new Set;
|
|
1196
|
-
const visit = (node) => {
|
|
1197
|
-
if (ts.isImportClause(node)) {
|
|
1198
|
-
if (node.name)
|
|
1199
|
-
names.add(node.name.text);
|
|
1200
|
-
const bindings = node.namedBindings;
|
|
1201
|
-
if (bindings && ts.isNamespaceImport(bindings))
|
|
1202
|
-
names.add(bindings.name.text);
|
|
1203
|
-
if (bindings && ts.isNamedImports(bindings))
|
|
1204
|
-
for (const el of bindings.elements)
|
|
1205
|
-
names.add(el.name.text);
|
|
1206
|
-
}
|
|
1207
|
-
if ((ts.isVariableDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.name && ts.isIdentifier(node.name))
|
|
1208
|
-
names.add(node.name.text);
|
|
1209
|
-
ts.forEachChild(node, visit);
|
|
1210
|
-
};
|
|
1211
|
-
ts.forEachChild(sf, visit);
|
|
1212
|
-
return names;
|
|
1213
|
-
};
|
|
1214
|
-
var collectUsedReferences = (sf) => {
|
|
1215
|
-
const used = new Set;
|
|
1216
|
-
const visit = (node) => {
|
|
1217
|
-
if (ts.isIdentifier(node) && isReferencePosition(node))
|
|
1218
|
-
used.add(node.text);
|
|
1219
|
-
ts.forEachChild(node, visit);
|
|
1220
|
-
};
|
|
1221
|
-
ts.forEachChild(sf, visit);
|
|
1222
|
-
return used;
|
|
1223
|
-
};
|
|
1224
|
-
var collectBareGlobalReferences = (sf) => {
|
|
1225
|
-
const bare = new Set;
|
|
1226
|
-
const visit = (node) => {
|
|
1227
|
-
if (ts.isIdentifier(node) && GLOBAL_NAMES.has(node.text) && isReferencePosition(node)) {
|
|
1228
|
-
const parent = node.parent;
|
|
1229
|
-
if (!(parent && ts.isPropertyAccessExpression(parent) && parent.expression === node))
|
|
1230
|
-
bare.add(node.text);
|
|
955
|
+
async#writeWithSourceMap(name, header, blocks, footer) {
|
|
956
|
+
const layout = { lineCount: 0, modules: [] };
|
|
957
|
+
let line = CsrDevPaths.lineCount(header);
|
|
958
|
+
for (const [id, text] of blocks) {
|
|
959
|
+
layout.modules.push([id, line + 1]);
|
|
960
|
+
line += CsrDevPaths.lineCount(text);
|
|
1231
961
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
var isReferencePosition = (node) => {
|
|
1238
|
-
const parent = node.parent;
|
|
1239
|
-
if (!parent)
|
|
1240
|
-
return true;
|
|
1241
|
-
if (ts.isPropertyAccessExpression(parent) && parent.name === node)
|
|
1242
|
-
return false;
|
|
1243
|
-
if (ts.isQualifiedName(parent) && parent.right === node)
|
|
1244
|
-
return false;
|
|
1245
|
-
if (ts.isPropertyAssignment(parent) && parent.name === node)
|
|
1246
|
-
return false;
|
|
1247
|
-
if (ts.isPropertySignature(parent) && parent.name === node)
|
|
1248
|
-
return false;
|
|
1249
|
-
if (ts.isBindingElement(parent) && parent.propertyName === node)
|
|
1250
|
-
return false;
|
|
1251
|
-
if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent))
|
|
1252
|
-
return false;
|
|
1253
|
-
return true;
|
|
1254
|
-
};
|
|
1255
|
-
var collectExistingNamedImports = (importDecls) => {
|
|
1256
|
-
const map = new Map;
|
|
1257
|
-
for (const decl of importDecls) {
|
|
1258
|
-
const clause = decl.importClause;
|
|
1259
|
-
const bindings = clause?.namedBindings;
|
|
1260
|
-
if (!clause || !bindings || !ts.isNamedImports(bindings))
|
|
1261
|
-
continue;
|
|
1262
|
-
if (!ts.isStringLiteral(decl.moduleSpecifier))
|
|
1263
|
-
continue;
|
|
1264
|
-
const specifier = decl.moduleSpecifier.text;
|
|
1265
|
-
if (map.has(specifier))
|
|
1266
|
-
continue;
|
|
1267
|
-
const names = new Map;
|
|
1268
|
-
for (const el of bindings.elements)
|
|
1269
|
-
names.set(el.name.text, clause.isTypeOnly || el.isTypeOnly);
|
|
1270
|
-
map.set(specifier, { decl, names });
|
|
962
|
+
const code = `${header}${blocks.map(([, text]) => text).join("")}${footer}//# sourceMappingURL=${name}.map
|
|
963
|
+
`;
|
|
964
|
+
layout.lineCount = CsrDevPaths.lineCount(code);
|
|
965
|
+
await this.writeJson(`${name}.layout.json`, layout);
|
|
966
|
+
await this.#writeAtomic(name, code);
|
|
1271
967
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
return `import type { ${entries.map(([name]) => name).join(", ")} } from "${specifier}";`;
|
|
1278
|
-
const parts = entries.map(([name, isType]) => isType ? `type ${name}` : name);
|
|
1279
|
-
return `import { ${parts.join(", ")} } from "${specifier}";`;
|
|
1280
|
-
};
|
|
1281
|
-
var directivePrologueEnd = (sf) => {
|
|
1282
|
-
let end = -1;
|
|
1283
|
-
for (const stmt of sf.statements) {
|
|
1284
|
-
if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression))
|
|
1285
|
-
end = stmt.getEnd();
|
|
1286
|
-
else
|
|
1287
|
-
break;
|
|
968
|
+
async#writeAtomic(name, content) {
|
|
969
|
+
const target = path3.join(this.#outDir, name);
|
|
970
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
971
|
+
await Bun.write(temp, content);
|
|
972
|
+
await FileSys2.replace(temp, target);
|
|
1288
973
|
}
|
|
1289
|
-
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
974
|
+
static async#remove(file) {
|
|
975
|
+
await rm(file, { force: true }).catch(() => {
|
|
976
|
+
return;
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
static async#appWriteDelay() {
|
|
980
|
+
const ms = Number(process.env.AKAN_CSR_DEV_APP_WRITE_DELAY_MS);
|
|
981
|
+
if (Number.isInteger(ms) && ms > 0)
|
|
982
|
+
await Bun.sleep(ms);
|
|
983
|
+
}
|
|
984
|
+
#modulePath(id, extension) {
|
|
985
|
+
return path3.join(this.#outDir, csrDevModuleFile(id, extension));
|
|
986
|
+
}
|
|
987
|
+
#helpersPath(hash) {
|
|
988
|
+
return path3.join(this.#outDir, "modules", `helpers-${hash}.js`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
|
|
993
|
+
import path7 from "path";
|
|
994
|
+
|
|
995
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevFactoryCheck.ts
|
|
996
|
+
class CsrDevFactoryCheck {
|
|
997
|
+
static #parsed = new Set;
|
|
998
|
+
static problemOf(factory, helperDefinition = "") {
|
|
999
|
+
const key = Bun.hash(`${helperDefinition}\x00${factory}`);
|
|
1000
|
+
if (CsrDevFactoryCheck.#parsed.has(key))
|
|
1001
|
+
return null;
|
|
1002
|
+
const problem = CsrDevFactoryCheck.#syntaxError(`${helperDefinition}return (${factory});`);
|
|
1003
|
+
if (problem === null) {
|
|
1004
|
+
CsrDevFactoryCheck.#parsed.add(key);
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
if (problem.includes("import.meta")) {
|
|
1008
|
+
const line = factory.split(`
|
|
1009
|
+
`).findIndex((text) => /(?<![\w$.])import\.meta\b/.test(text));
|
|
1010
|
+
return `import.meta is left in it${line > 0 ? ` (compiled line ${line})` : ""}, which a classic script cannot hold: ${problem}`;
|
|
1322
1011
|
}
|
|
1012
|
+
if (CsrDevFactoryCheck.#syntaxError(`${helperDefinition}return (async ${factory});`) === null)
|
|
1013
|
+
return `a top-level await is left in it, which a CommonJS factory cannot wait on: ${problem}`;
|
|
1014
|
+
return problem;
|
|
1323
1015
|
}
|
|
1324
|
-
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
for (const entry of entries) {
|
|
1330
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
1331
|
-
continue;
|
|
1332
|
-
const full = path5.join(dir, entry.name);
|
|
1333
|
-
if (entry.isDirectory())
|
|
1334
|
-
files.push(...await collectDomainFiles(full));
|
|
1335
|
-
else if (domainKindOf(entry.name) && !TEST_FILE_RE.test(entry.name))
|
|
1336
|
-
files.push(full);
|
|
1016
|
+
static thrower(id, problem) {
|
|
1017
|
+
const message = `[akan-csr] ${id} was left out of the dev registry: ${problem}`;
|
|
1018
|
+
return `function () {
|
|
1019
|
+
throw new Error(${JSON.stringify(message)});
|
|
1020
|
+
}`;
|
|
1337
1021
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
for (const stmt of sf.statements) {
|
|
1345
|
-
if ((ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt) || ts.isEnumDeclaration(stmt)) && stmt.name) {
|
|
1346
|
-
if (isExported(stmt))
|
|
1347
|
-
names.push(stmt.name.text);
|
|
1348
|
-
} else if (ts.isVariableStatement(stmt) && isExported(stmt)) {
|
|
1349
|
-
for (const decl of stmt.declarationList.declarations)
|
|
1350
|
-
if (ts.isIdentifier(decl.name))
|
|
1351
|
-
names.push(decl.name.text);
|
|
1352
|
-
} else if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
|
|
1353
|
-
for (const el of stmt.exportClause.elements)
|
|
1354
|
-
names.push(el.name.text);
|
|
1022
|
+
static #syntaxError(source) {
|
|
1023
|
+
try {
|
|
1024
|
+
new Function(source);
|
|
1025
|
+
return null;
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
return error instanceof Error ? error.message : String(error);
|
|
1355
1028
|
}
|
|
1356
1029
|
}
|
|
1357
|
-
|
|
1358
|
-
};
|
|
1359
|
-
var relativeSpecifier = (fromDir, toFile) => {
|
|
1360
|
-
const rel = path5.relative(fromDir, toFile).replace(/\.tsx?$/, "").split(path5.sep).join("/");
|
|
1361
|
-
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1362
|
-
};
|
|
1363
|
-
|
|
1364
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrDevPatcher.ts
|
|
1365
|
-
import fs4 from "fs";
|
|
1366
|
-
import path7 from "path";
|
|
1367
|
-
import { appGenerationOf, CSR_DEV_MANIFEST_FILE } from "akanjs/server/hmr/csrDevManifest";
|
|
1030
|
+
}
|
|
1368
1031
|
|
|
1369
1032
|
// pkgs/@akanjs/devkit/frontendBuild/csrDevResolver.ts
|
|
1370
1033
|
import fs3 from "fs";
|
|
1371
|
-
import
|
|
1034
|
+
import path4 from "path";
|
|
1372
1035
|
class CsrDevResolver {
|
|
1373
1036
|
static inline = "inline";
|
|
1374
1037
|
#paths;
|
|
@@ -1450,7 +1113,7 @@ class CsrDevResolver {
|
|
|
1450
1113
|
}
|
|
1451
1114
|
#record(inputs, only) {
|
|
1452
1115
|
for (const [input, { imports }] of Object.entries(inputs)) {
|
|
1453
|
-
const importer = CsrDevPaths.realpath(
|
|
1116
|
+
const importer = CsrDevPaths.realpath(path4.resolve(input));
|
|
1454
1117
|
if (only && !only.has(importer))
|
|
1455
1118
|
continue;
|
|
1456
1119
|
if (imports.every((record) => record.original))
|
|
@@ -1463,7 +1126,7 @@ class CsrDevResolver {
|
|
|
1463
1126
|
this.#remember(importer, record.original, `${CsrDevPaths.stubPrefix}${record.original}`);
|
|
1464
1127
|
continue;
|
|
1465
1128
|
}
|
|
1466
|
-
const target =
|
|
1129
|
+
const target = path4.resolve(record.path);
|
|
1467
1130
|
if (!fs3.existsSync(target))
|
|
1468
1131
|
continue;
|
|
1469
1132
|
this.#remember(importer, record.original, CsrDevPaths.realpath(target));
|
|
@@ -1495,9 +1158,9 @@ class CsrDevResolver {
|
|
|
1495
1158
|
return false;
|
|
1496
1159
|
if (!specifier.startsWith(".") || CsrDevPaths.isVendorFile(importer))
|
|
1497
1160
|
return true;
|
|
1498
|
-
if (this.#hasBrowserMap(
|
|
1161
|
+
if (this.#hasBrowserMap(path4.dirname(importer)))
|
|
1499
1162
|
return true;
|
|
1500
|
-
const onDisk = CsrDevPaths.resolveRelative(
|
|
1163
|
+
const onDisk = CsrDevPaths.resolveRelative(path4.dirname(importer), specifier, kind);
|
|
1501
1164
|
return onDisk !== null && CsrDevPaths.realpath(onDisk) === known;
|
|
1502
1165
|
}
|
|
1503
1166
|
isRegistryModule(file) {
|
|
@@ -1512,12 +1175,12 @@ class CsrDevResolver {
|
|
|
1512
1175
|
});
|
|
1513
1176
|
}
|
|
1514
1177
|
#disabledInBrowser(importer, specifier, kind) {
|
|
1515
|
-
const map = this.#browserMapOf(
|
|
1178
|
+
const map = this.#browserMapOf(path4.dirname(importer));
|
|
1516
1179
|
if (!map || map.offFiles.size === 0 && map.offNames.size === 0)
|
|
1517
1180
|
return null;
|
|
1518
|
-
if (!specifier.startsWith(".") && !
|
|
1181
|
+
if (!specifier.startsWith(".") && !path4.isAbsolute(specifier))
|
|
1519
1182
|
return map.offNames.has(specifier) ? `${CsrDevPaths.emptyStubPrefix}${specifier}` : null;
|
|
1520
|
-
const target = CsrDevPaths.resolveRelative(
|
|
1183
|
+
const target = CsrDevPaths.resolveRelative(path4.dirname(importer), specifier, kind);
|
|
1521
1184
|
return target && map.offFiles.has(CsrDevPaths.realpath(target)) ? `${CsrDevPaths.emptyStubPrefix}${this.#paths.idOf(target)}` : null;
|
|
1522
1185
|
}
|
|
1523
1186
|
#hasBrowserMap(dir) {
|
|
@@ -1527,8 +1190,8 @@ class CsrDevResolver {
|
|
|
1527
1190
|
const cached = this.#browserMaps.get(dir);
|
|
1528
1191
|
if (cached !== undefined)
|
|
1529
1192
|
return cached;
|
|
1530
|
-
const pkgFile =
|
|
1531
|
-
const parent =
|
|
1193
|
+
const pkgFile = path4.join(dir, "package.json");
|
|
1194
|
+
const parent = path4.dirname(dir);
|
|
1532
1195
|
let map = null;
|
|
1533
1196
|
if (fs3.existsSync(pkgFile)) {
|
|
1534
1197
|
try {
|
|
@@ -1563,10 +1226,10 @@ class CsrDevResolver {
|
|
|
1563
1226
|
#resolveUnknown(importer, specifier, kind) {
|
|
1564
1227
|
if (specifier.startsWith("node:"))
|
|
1565
1228
|
return `${CsrDevPaths.stubPrefix}${specifier}`;
|
|
1566
|
-
const relative = specifier.startsWith(".") ||
|
|
1229
|
+
const relative = specifier.startsWith(".") || path4.isAbsolute(specifier);
|
|
1567
1230
|
const cannotResolve = () => new Error(`[csr-dev] cannot resolve "${specifier}" from ${this.#paths.idOf(importer)}`);
|
|
1568
1231
|
if (relative) {
|
|
1569
|
-
const onDisk = CsrDevPaths.resolveRelative(
|
|
1232
|
+
const onDisk = CsrDevPaths.resolveRelative(path4.dirname(importer), specifier, kind) ?? CsrDevResolver.#onDisk(CsrDevPaths.tryResolve(specifier, path4.dirname(importer)));
|
|
1570
1233
|
if (!onDisk)
|
|
1571
1234
|
throw cannotResolve();
|
|
1572
1235
|
return CsrDevPaths.realpath(onDisk);
|
|
@@ -1574,9 +1237,9 @@ class CsrDevResolver {
|
|
|
1574
1237
|
const sibling = this.#resolvedBySibling(importer, specifier);
|
|
1575
1238
|
if (sibling)
|
|
1576
1239
|
return sibling;
|
|
1577
|
-
const resolved = CsrDevPaths.tryResolve(specifier,
|
|
1578
|
-
const moved = resolved !== null &&
|
|
1579
|
-
const installed = resolved === null && CsrDevPaths.installedSince(specifier,
|
|
1240
|
+
const resolved = CsrDevPaths.tryResolve(specifier, path4.dirname(importer));
|
|
1241
|
+
const moved = resolved !== null && path4.isAbsolute(resolved) && !fs3.existsSync(resolved);
|
|
1242
|
+
const installed = resolved === null && CsrDevPaths.installedSince(specifier, path4.dirname(importer), this.#startedAt);
|
|
1580
1243
|
if (!this.#prepassDone || moved) {
|
|
1581
1244
|
if (resolved === null && !installed && !CsrDevPaths.isVendorFile(importer))
|
|
1582
1245
|
throw cannotResolve();
|
|
@@ -1587,14 +1250,14 @@ class CsrDevResolver {
|
|
|
1587
1250
|
return null;
|
|
1588
1251
|
throw cannotResolve();
|
|
1589
1252
|
}
|
|
1590
|
-
if (!
|
|
1253
|
+
if (!path4.isAbsolute(resolved))
|
|
1591
1254
|
return `${CsrDevPaths.stubPrefix}${specifier}`;
|
|
1592
1255
|
this.#fallbacks.add(specifier);
|
|
1593
1256
|
this.#runtimeResolved.set(importer, (this.#runtimeResolved.get(importer) ?? new Set).add(specifier));
|
|
1594
1257
|
return CsrDevPaths.realpath(resolved);
|
|
1595
1258
|
}
|
|
1596
1259
|
static #onDisk(resolved) {
|
|
1597
|
-
return resolved &&
|
|
1260
|
+
return resolved && path4.isAbsolute(resolved) && fs3.existsSync(resolved) ? resolved : null;
|
|
1598
1261
|
}
|
|
1599
1262
|
#resolvedBySibling(importer, specifier) {
|
|
1600
1263
|
const scope = CsrDevResolver.#packageScopeOf(importer);
|
|
@@ -1614,7 +1277,7 @@ class CsrDevResolver {
|
|
|
1614
1277
|
this.#resolution.set(importer, byImporter.set(specifier, target));
|
|
1615
1278
|
}
|
|
1616
1279
|
#isInlineSpecifier(specifier) {
|
|
1617
|
-
if (specifier.startsWith(".") ||
|
|
1280
|
+
if (specifier.startsWith(".") || path4.isAbsolute(specifier) || specifier.startsWith("node:"))
|
|
1618
1281
|
return false;
|
|
1619
1282
|
return this.#context.optimizeImports.some((pattern) => {
|
|
1620
1283
|
const base = CsrDevResolver.#patternBase(pattern);
|
|
@@ -1625,991 +1288,1259 @@ class CsrDevResolver {
|
|
|
1625
1288
|
return pattern.endsWith("/*") ? pattern.slice(0, -2) : pattern;
|
|
1626
1289
|
}
|
|
1627
1290
|
static #packageNameOf(file) {
|
|
1628
|
-
const marker = `${
|
|
1291
|
+
const marker = `${path4.sep}node_modules${path4.sep}`;
|
|
1629
1292
|
const index = file.lastIndexOf(marker);
|
|
1630
1293
|
if (index < 0)
|
|
1631
1294
|
return null;
|
|
1632
|
-
const [scope, name] = file.slice(index + marker.length).split(
|
|
1295
|
+
const [scope, name] = file.slice(index + marker.length).split(path4.sep);
|
|
1633
1296
|
return (scope?.startsWith("@") ? `${scope}/${name}` : scope) || null;
|
|
1634
1297
|
}
|
|
1635
1298
|
static #packageScopeOf(file) {
|
|
1636
|
-
const marker = `${
|
|
1299
|
+
const marker = `${path4.sep}node_modules${path4.sep}`;
|
|
1637
1300
|
const index = file.lastIndexOf(marker);
|
|
1638
1301
|
if (index < 0)
|
|
1639
1302
|
return "";
|
|
1640
|
-
const [scope, name] = file.slice(index + marker.length).split(
|
|
1641
|
-
return file.slice(0, index + marker.length) + (scope?.startsWith("@") ? `${scope}${
|
|
1303
|
+
const [scope, name] = file.slice(index + marker.length).split(path4.sep);
|
|
1304
|
+
return file.slice(0, index + marker.length) + (scope?.startsWith("@") ? `${scope}${path4.sep}${name}` : scope);
|
|
1642
1305
|
}
|
|
1643
1306
|
}
|
|
1644
1307
|
|
|
1645
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1308
|
+
// pkgs/@akanjs/devkit/frontendBuild/routeClientBuilder.ts
|
|
1309
|
+
import { mkdir as mkdir2 } from "fs/promises";
|
|
1310
|
+
import path6 from "path";
|
|
1311
|
+
import { SSR_DEV_CHUNK, SSR_DEV_ID_PREFIX } from "akanjs/server/hmr/csrDevManifest";
|
|
1312
|
+
|
|
1313
|
+
// pkgs/@akanjs/devkit/frontendBuild/clientEntriesBundler.ts
|
|
1314
|
+
import fs4 from "fs";
|
|
1315
|
+
import path5 from "path";
|
|
1316
|
+
|
|
1317
|
+
// pkgs/@akanjs/devkit/frontendBuild/clientBuildTypes.ts
|
|
1318
|
+
var CLIENT_BUNDLE_NAMING = {
|
|
1319
|
+
entry: "[name]-[hash].[ext]",
|
|
1320
|
+
chunk: "chunks/[hash].[ext]",
|
|
1321
|
+
asset: "assets/[hash].[ext]"
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
// pkgs/@akanjs/devkit/frontendBuild/clientEntriesBundler.ts
|
|
1325
|
+
class ClientEntriesBundler {
|
|
1326
|
+
#app;
|
|
1327
|
+
#entries;
|
|
1328
|
+
#plugins;
|
|
1329
|
+
#external;
|
|
1330
|
+
#externalSubpaths;
|
|
1331
|
+
#externalAliases;
|
|
1332
|
+
#command;
|
|
1333
|
+
#target;
|
|
1334
|
+
#outputSubdir;
|
|
1335
|
+
#reactFastRefresh;
|
|
1336
|
+
#outdir;
|
|
1337
|
+
#servePrefix;
|
|
1338
|
+
#manifest = {};
|
|
1339
|
+
#ssrManifest = { moduleLoading: null, moduleMap: {} };
|
|
1340
|
+
#entryUrlsByAbsPath = new Map;
|
|
1341
|
+
#entryOutputAbsByAbsPath = new Map;
|
|
1342
|
+
#entryDepsByAbsPath = new Map;
|
|
1343
|
+
#clientReferenceIdByAbsPath = new Map;
|
|
1344
|
+
#opaqueEntries = null;
|
|
1345
|
+
#metafileOutputsByAbs = new Map;
|
|
1346
|
+
constructor(options) {
|
|
1347
|
+
this.#app = options.app;
|
|
1348
|
+
this.#entries = options.entries;
|
|
1349
|
+
this.#plugins = options.plugins ?? [];
|
|
1350
|
+
this.#external = options.external ?? [];
|
|
1351
|
+
this.#externalSubpaths = options.externalSubpaths ?? [];
|
|
1352
|
+
this.#externalAliases = options.externalAliases ?? {};
|
|
1353
|
+
this.#command = options.command ?? "start";
|
|
1354
|
+
this.#target = options.target ?? "browser";
|
|
1355
|
+
this.#outputSubdir = options.outputSubdir ?? "client";
|
|
1356
|
+
this.#reactFastRefresh = options.reactFastRefresh ?? false;
|
|
1357
|
+
const artifactDir = `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
|
|
1358
|
+
this.#outdir = `${artifactDir}/${this.#outputSubdir}`;
|
|
1359
|
+
this.#servePrefix = `/_akan/${this.#outputSubdir}`;
|
|
1654
1360
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1361
|
+
async bundle() {
|
|
1362
|
+
const akanConfig = await this.#app.getConfig();
|
|
1363
|
+
this.#opaqueEntries = await this.#createOpaqueEntryAliases();
|
|
1364
|
+
const result = await Bun.build({
|
|
1365
|
+
entrypoints: this.#opaqueEntries.entries,
|
|
1366
|
+
outdir: this.#outdir,
|
|
1367
|
+
splitting: true,
|
|
1368
|
+
target: this.#target,
|
|
1369
|
+
format: "esm",
|
|
1370
|
+
naming: CLIENT_BUNDLE_NAMING,
|
|
1371
|
+
metafile: true,
|
|
1372
|
+
define: bundleDefine(this.#app, this.#command, "ssr"),
|
|
1373
|
+
minify: this.#command === "build",
|
|
1374
|
+
optimizeImports: akanConfig.optimizeImports,
|
|
1375
|
+
reactFastRefresh: this.#command === "start" && this.#outputSubdir === "client" && this.#reactFastRefresh,
|
|
1376
|
+
plugins: this.#external.length > 0 || this.#externalSubpaths.length > 0 || Object.keys(this.#externalAliases).length > 0 ? [...this.#plugins, this.#createExternalSpecifiersPlugin()] : this.#plugins
|
|
1377
|
+
});
|
|
1378
|
+
if (!result.success)
|
|
1379
|
+
throw new AggregateError(result.logs, "[ClientEntriesBundler] Bun.build failed");
|
|
1380
|
+
await this.#rewriteExternalImportSpecifiers(result.outputs);
|
|
1381
|
+
const metafile = result.metafile;
|
|
1382
|
+
if (!metafile)
|
|
1383
|
+
throw new Error("[ClientEntriesBundler] metafile is missing");
|
|
1384
|
+
this.#metafileOutputsByAbs = new Map(Object.entries(metafile.outputs).map(([k, v]) => [this.#absFromOutdir(k), v]));
|
|
1385
|
+
for (const artifact of result.outputs.filter((a) => a.kind === "entry-point"))
|
|
1386
|
+
this.#addEntryArtifact(artifact.path);
|
|
1387
|
+
return {
|
|
1388
|
+
manifest: this.#manifest,
|
|
1389
|
+
ssrManifest: this.#ssrManifest,
|
|
1390
|
+
entryUrlsByAbsPath: this.#entryUrlsByAbsPath,
|
|
1391
|
+
entryOutputAbsByAbsPath: this.#entryOutputAbsByAbsPath,
|
|
1392
|
+
entryDepsByAbsPath: this.#entryDepsByAbsPath,
|
|
1393
|
+
clientReferenceIdByAbsPath: this.#clientReferenceIdByAbsPath
|
|
1394
|
+
};
|
|
1657
1395
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1396
|
+
async#createOpaqueEntryAliases() {
|
|
1397
|
+
const aliasDir = path5.join(this.#app.cwdPath, ".akan", "generated", "client-entry-alias", this.#outputSubdir);
|
|
1398
|
+
fs4.mkdirSync(aliasDir, { recursive: true });
|
|
1399
|
+
const originalByAlias = new Map;
|
|
1400
|
+
const aliasedEntries = await Promise.all(this.#entries.map(async (entry) => {
|
|
1401
|
+
const absEntry = path5.resolve(entry);
|
|
1402
|
+
const hash = Bun.hash(`${this.#app.name}
|
|
1403
|
+
${this.#outputSubdir}
|
|
1404
|
+
${absEntry}`).toString(36);
|
|
1405
|
+
const aliasPath = path5.join(aliasDir, `${hash}.tsx`);
|
|
1406
|
+
await Bun.write(aliasPath, this.#createOpaqueEntryAliasSource(absEntry, await this.#scanEntryExportNames(absEntry)));
|
|
1407
|
+
originalByAlias.set(path5.resolve(aliasPath), absEntry);
|
|
1408
|
+
return aliasPath;
|
|
1409
|
+
}));
|
|
1410
|
+
return { entries: aliasedEntries, originalByAlias, aliasDir };
|
|
1662
1411
|
}
|
|
1663
|
-
|
|
1664
|
-
const
|
|
1665
|
-
if (
|
|
1666
|
-
return
|
|
1667
|
-
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
if (state.graph.configKey !== context.configKey)
|
|
1676
|
-
return this.#handBack("the dev bundle config changed", generation, context);
|
|
1677
|
-
const metadataFile = changedFiles.find((file) => this.#bundler.isMetadataFile(file));
|
|
1678
|
-
if (metadataFile)
|
|
1679
|
-
return this.#handBack(`${path7.basename(metadataFile)} changed`, generation, context);
|
|
1680
|
-
if (!resident && state.graph.metadata !== await this.#bundler.metadataFingerprint())
|
|
1681
|
-
return this.#handBack("signal or dictionary metadata changed since the registry was built", generation, context);
|
|
1682
|
-
this.#state = this.#resident ? state : null;
|
|
1683
|
-
const healable = appGenerationOf(state.manifest) < state.manifest.generation && this.#withoutFactory(state, (file) => fs4.existsSync(file)).length === 0;
|
|
1684
|
-
if (healable)
|
|
1685
|
-
state.manifest = await this.#bundler.writer.healApp(state.graph, state.manifest, await this.#code(state));
|
|
1686
|
-
return await this.#patch(context, state, changedFiles, generation, {
|
|
1687
|
-
announce,
|
|
1688
|
-
roots,
|
|
1689
|
-
onlyRoots,
|
|
1690
|
-
allowWholeAppBuilds
|
|
1691
|
-
});
|
|
1412
|
+
#createOpaqueEntryAliasSource(absEntry, exportNames) {
|
|
1413
|
+
const entryLit = JSON.stringify(path5.resolve(absEntry));
|
|
1414
|
+
if (exportNames.length === 0)
|
|
1415
|
+
return `export * from ${entryLit};
|
|
1416
|
+
`;
|
|
1417
|
+
const namedExports = exportNames.filter((name) => name !== "default");
|
|
1418
|
+
const lines = namedExports.length > 0 ? [`export { ${namedExports.join(", ")} } from ${entryLit};`] : [];
|
|
1419
|
+
if (exportNames.includes("default"))
|
|
1420
|
+
lines.push(`export { default } from ${entryLit};`);
|
|
1421
|
+
return `${lines.join(`
|
|
1422
|
+
`)}
|
|
1423
|
+
`;
|
|
1692
1424
|
}
|
|
1693
|
-
async#
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
const
|
|
1698
|
-
const
|
|
1699
|
-
const
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
if (carried.length === 0)
|
|
1715
|
-
return { kind: "unchanged" };
|
|
1716
|
-
return await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
|
|
1717
|
-
}
|
|
1718
|
-
const resolver = new CsrDevResolver({
|
|
1719
|
-
paths,
|
|
1720
|
-
context,
|
|
1721
|
-
entryFiles: rootFiles.map((file) => CsrDevPaths.realpath(file)),
|
|
1722
|
-
resolution: graph.resolution,
|
|
1723
|
-
runtimeResolved: graph.runtimeResolved
|
|
1724
|
-
});
|
|
1725
|
-
const known = new Set(Object.keys(graph.modules).map((id) => paths.fileOf(id)));
|
|
1726
|
-
const compile = async (ids) => await this.#bundler.compiler(context, resolver).compile(ids.map((id) => paths.fileOf(id)), known, { refuseNewVendor: !allowWholeAppBuilds, refusePrepass: !allowWholeAppBuilds });
|
|
1727
|
-
let result;
|
|
1728
|
-
let orphans;
|
|
1729
|
-
try {
|
|
1730
|
-
({ result, orphans } = await this.#compileLeavingOrphans(compile, graph, changed));
|
|
1731
|
-
for (const module of result.modules)
|
|
1732
|
-
known.add(module.file);
|
|
1733
|
-
result = await this.#withTwinImporters(compile, graph, result);
|
|
1734
|
-
} catch (error) {
|
|
1735
|
-
graph.pending = [...new Set([...graph.pending, ...changed, ...carried])];
|
|
1736
|
-
if (!this.#resident)
|
|
1737
|
-
graph.failed = {
|
|
1738
|
-
roots: [...changed, ...carried],
|
|
1739
|
-
files: await CsrDevPatcher.#hashesOf(paths, CsrDevPatcher.#implicatedFiles(error))
|
|
1740
|
-
};
|
|
1741
|
-
await writer.writeJson("graph.json", graph);
|
|
1742
|
-
throw error;
|
|
1743
|
-
}
|
|
1744
|
-
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;
|
|
1745
|
-
if (refusal) {
|
|
1746
|
-
if (routesMoved)
|
|
1747
|
-
await writer.writeJson("graph.json", { ...graph, entries: {} });
|
|
1748
|
-
if (onlyRoots) {
|
|
1749
|
-
for (const id of changed)
|
|
1750
|
-
if (!graph.modules[id])
|
|
1751
|
-
this.#delegated.set(id, {
|
|
1752
|
-
hash: await CsrDevPaths.hashOf(paths.fileOf(id)),
|
|
1753
|
-
before: JSON.stringify(graph.failed ?? null)
|
|
1754
|
-
});
|
|
1425
|
+
async#scanEntryExportNames(absEntry) {
|
|
1426
|
+
return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
|
|
1427
|
+
}
|
|
1428
|
+
#createExternalSpecifiersPlugin() {
|
|
1429
|
+
const set = new Set(this.#external);
|
|
1430
|
+
const subpathSet = new Set(this.#externalSubpaths);
|
|
1431
|
+
const aliases = this.#externalAliases;
|
|
1432
|
+
const specifiers = [...new Set([...this.#external, ...this.#externalSubpaths, ...Object.keys(aliases)])];
|
|
1433
|
+
const escaped = specifiers.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
1434
|
+
const filter = new RegExp(`^(${escaped.join("|")})(?:/.*)?$`);
|
|
1435
|
+
return {
|
|
1436
|
+
name: "akan-externalize-specifiers",
|
|
1437
|
+
setup(build) {
|
|
1438
|
+
build.onResolve({ filter }, (args) => {
|
|
1439
|
+
const alias = aliases[args.path];
|
|
1440
|
+
if (alias)
|
|
1441
|
+
return { path: alias, external: true };
|
|
1442
|
+
if (!ClientEntriesBundler.#matchesExternalSpecifier(args.path, set, subpathSet))
|
|
1443
|
+
return;
|
|
1444
|
+
return { path: args.path, external: true };
|
|
1445
|
+
});
|
|
1755
1446
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
for (const
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
static #matchesExternalSpecifier(specifier, exactExternals, subpathExternals) {
|
|
1450
|
+
if (exactExternals.has(specifier) || subpathExternals.has(specifier))
|
|
1451
|
+
return true;
|
|
1452
|
+
for (const external of subpathExternals)
|
|
1453
|
+
if (specifier.startsWith(`${external}/`))
|
|
1454
|
+
return true;
|
|
1455
|
+
return false;
|
|
1456
|
+
}
|
|
1457
|
+
async#rewriteExternalImportSpecifiers(outputs) {
|
|
1458
|
+
if (Object.keys(this.#externalAliases).length === 0)
|
|
1459
|
+
return;
|
|
1460
|
+
await Promise.all(outputs.filter((output) => output.path.endsWith(".js")).map(async (output) => {
|
|
1461
|
+
const source = await Bun.file(output.path).text();
|
|
1462
|
+
const rewritten = ClientEntriesBundler.rewriteExternalImportSpecifiers(source, this.#externalAliases);
|
|
1463
|
+
if (rewritten !== source)
|
|
1464
|
+
await Bun.write(output.path, rewritten);
|
|
1465
|
+
}));
|
|
1466
|
+
}
|
|
1467
|
+
static rewriteExternalImportSpecifiers(source, aliases) {
|
|
1468
|
+
let rewritten = source;
|
|
1469
|
+
for (const [specifier, alias] of Object.entries(aliases)) {
|
|
1470
|
+
if (!alias)
|
|
1471
|
+
continue;
|
|
1472
|
+
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1473
|
+
rewritten = rewritten.replace(new RegExp(`(\\bfrom\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
|
|
1474
|
+
return `${prefix}${alias}${suffix}`;
|
|
1475
|
+
}).replace(new RegExp(`(\\bimport\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
|
|
1476
|
+
return `${prefix}${alias}${suffix}`;
|
|
1477
|
+
});
|
|
1766
1478
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1479
|
+
return rewritten;
|
|
1480
|
+
}
|
|
1481
|
+
#toServeUrl(absOutPath) {
|
|
1482
|
+
const rel = path5.relative(this.#outdir, absOutPath).split(path5.sep).join("/");
|
|
1483
|
+
return `${this.#servePrefix}/${rel}`;
|
|
1484
|
+
}
|
|
1485
|
+
#absFromOutdir(p) {
|
|
1486
|
+
return path5.isAbsolute(p) ? p : path5.resolve(this.#outdir, p);
|
|
1487
|
+
}
|
|
1488
|
+
#absFromEntryPoint(p) {
|
|
1489
|
+
if (path5.isAbsolute(p))
|
|
1490
|
+
return path5.resolve(p);
|
|
1491
|
+
const candidates = [path5.resolve(process.cwd(), p), path5.resolve(this.#app.cwdPath, p)];
|
|
1492
|
+
return candidates.find((candidate) => fs4.existsSync(candidate)) ?? candidates[0];
|
|
1493
|
+
}
|
|
1494
|
+
#collectChunkUrls(absOutPath, visited = new Set) {
|
|
1495
|
+
if (visited.has(absOutPath))
|
|
1496
|
+
return [];
|
|
1497
|
+
visited.add(absOutPath);
|
|
1498
|
+
const info = this.#metafileOutputsByAbs.get(absOutPath);
|
|
1499
|
+
if (!info)
|
|
1500
|
+
return [this.#toServeUrl(absOutPath)];
|
|
1501
|
+
const urls = [this.#toServeUrl(absOutPath)];
|
|
1502
|
+
for (const imp of info.imports) {
|
|
1503
|
+
if (imp.kind === "dynamic-import")
|
|
1776
1504
|
continue;
|
|
1777
|
-
|
|
1778
|
-
if (
|
|
1779
|
-
|
|
1505
|
+
const absImp = this.#absFromOutdir(imp.path);
|
|
1506
|
+
if (!absImp.endsWith(".js"))
|
|
1507
|
+
continue;
|
|
1508
|
+
urls.push(...this.#collectChunkUrls(absImp, visited));
|
|
1780
1509
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
if (
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1510
|
+
return urls;
|
|
1511
|
+
}
|
|
1512
|
+
#collectClientDeps(absOutPath, visited = new Set) {
|
|
1513
|
+
if (visited.has(absOutPath))
|
|
1514
|
+
return new Set;
|
|
1515
|
+
visited.add(absOutPath);
|
|
1516
|
+
const deps = new Set;
|
|
1517
|
+
const info = this.#metafileOutputsByAbs.get(absOutPath);
|
|
1518
|
+
if (!info)
|
|
1519
|
+
return deps;
|
|
1520
|
+
if (!this.#opaqueEntries)
|
|
1521
|
+
throw new Error("[ClientEntriesBundler] opaque entries are missing");
|
|
1522
|
+
for (const input of Object.keys(info.inputs ?? {})) {
|
|
1523
|
+
const absInput = this.#absFromEntryPoint(input);
|
|
1524
|
+
if (!absInput.includes("node_modules") && !absInput.startsWith(this.#opaqueEntries.aliasDir))
|
|
1525
|
+
deps.add(absInput);
|
|
1790
1526
|
}
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
moduleCount: Object.keys(graph.modules).length,
|
|
1800
|
-
epoch: previous.epoch
|
|
1801
|
-
});
|
|
1802
|
-
let outcome;
|
|
1803
|
-
if (reason) {
|
|
1804
|
-
await writer.forgetPatch(generation);
|
|
1805
|
-
await writer.writeModules(compiled);
|
|
1806
|
-
const vendorFile = vendorJoined ? await writer.writeVendor(graph) : previous.vendorFile;
|
|
1807
|
-
await writer.writeApp(graph, { generation, vendorFile, epoch: previous.epoch }, code);
|
|
1808
|
-
state.manifest = { ...next, vendorFile, appGeneration: generation };
|
|
1809
|
-
await writer.writeState(graph, state.manifest);
|
|
1810
|
-
await writer.prune(vendorFile, generation);
|
|
1811
|
-
const reloaded = update();
|
|
1812
|
-
announce?.(reloaded);
|
|
1813
|
-
outcome = { kind: "update", update: reloaded };
|
|
1814
|
-
} else {
|
|
1815
|
-
const patched = update(await writer.writePatch(generation, compiled.filter((module) => !module.vendor)));
|
|
1816
|
-
await writer.commitPatch(graph, { ...next, appGeneration: appGenerationOf(previous) }, () => announce?.(patched), { modules: compiled, code });
|
|
1817
|
-
state.manifest = { ...next, appGeneration: generation };
|
|
1818
|
-
await writer.prune(previous.vendorFile, generation);
|
|
1819
|
-
outcome = { kind: "update", update: patched };
|
|
1527
|
+
for (const imp of info.imports) {
|
|
1528
|
+
if (imp.kind === "dynamic-import")
|
|
1529
|
+
continue;
|
|
1530
|
+
const absImp = this.#absFromOutdir(imp.path);
|
|
1531
|
+
if (!absImp.endsWith(".js"))
|
|
1532
|
+
continue;
|
|
1533
|
+
for (const dep of this.#collectClientDeps(absImp, visited))
|
|
1534
|
+
deps.add(dep);
|
|
1820
1535
|
}
|
|
1821
|
-
|
|
1822
|
-
if (carried.length === 0)
|
|
1823
|
-
return outcome;
|
|
1824
|
-
const retried = await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
|
|
1825
|
-
return retried.kind === "delegate" ? retried : outcome;
|
|
1536
|
+
return deps;
|
|
1826
1537
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1538
|
+
#addEntryArtifact(absOut) {
|
|
1539
|
+
if (!this.#opaqueEntries)
|
|
1540
|
+
throw new Error("[ClientEntriesBundler] opaque entries are missing");
|
|
1541
|
+
const info = this.#metafileOutputsByAbs.get(absOut);
|
|
1542
|
+
const buildEntryPointAbs = info?.entryPoint ? this.#absFromEntryPoint(info.entryPoint) : null;
|
|
1543
|
+
const entryPointAbs = buildEntryPointAbs ? this.#opaqueEntries.originalByAlias.get(buildEntryPointAbs) ?? buildEntryPointAbs : null;
|
|
1544
|
+
if (!entryPointAbs)
|
|
1545
|
+
return;
|
|
1546
|
+
const chunkUrls = this.#collectChunkUrls(absOut);
|
|
1547
|
+
const entryUrl = chunkUrls[0] ?? this.#toServeUrl(absOut);
|
|
1548
|
+
this.#entryUrlsByAbsPath.set(entryPointAbs, entryUrl);
|
|
1549
|
+
this.#entryOutputAbsByAbsPath.set(entryPointAbs, absOut);
|
|
1550
|
+
this.#entryDepsByAbsPath.set(entryPointAbs, [...this.#collectClientDeps(absOut)].sort());
|
|
1551
|
+
const clientReferenceId = toClientReferencePath(entryPointAbs, this.#app.workspace.workspaceRoot);
|
|
1552
|
+
this.#clientReferenceIdByAbsPath.set(entryPointAbs, clientReferenceId);
|
|
1553
|
+
const flatChunks = [];
|
|
1554
|
+
for (const url of chunkUrls)
|
|
1555
|
+
flatChunks.push(url, url);
|
|
1556
|
+
const exportNames = info?.exports && info.exports.length > 0 ? info.exports : ["default"];
|
|
1557
|
+
const ssrEntriesByName = {};
|
|
1558
|
+
for (const name of exportNames) {
|
|
1559
|
+
const key = `${clientReferenceId}#${name}`;
|
|
1560
|
+
this.#manifest[key] = { id: entryUrl, chunks: flatChunks, name, async: true };
|
|
1561
|
+
ssrEntriesByName[name] = { id: absOut, chunks: [absOut, absOut], name, async: true };
|
|
1562
|
+
}
|
|
1563
|
+
this.#ssrManifest.moduleMap[entryUrl] = ssrEntriesByName;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// pkgs/@akanjs/devkit/frontendBuild/vendorSpecifiers.ts
|
|
1568
|
+
var VENDOR_SPECIFIERS = [
|
|
1569
|
+
"react",
|
|
1570
|
+
"react-dom",
|
|
1571
|
+
"react-dom/client",
|
|
1572
|
+
"react/jsx-runtime",
|
|
1573
|
+
"react/jsx-dev-runtime",
|
|
1574
|
+
"react-refresh/runtime",
|
|
1575
|
+
"scheduler",
|
|
1576
|
+
"react-server-dom-webpack/client.browser",
|
|
1577
|
+
"akanjs/store",
|
|
1578
|
+
"akanjs/base",
|
|
1579
|
+
"akanjs/common",
|
|
1580
|
+
"akanjs/constant",
|
|
1581
|
+
"akanjs/fetch"
|
|
1582
|
+
];
|
|
1583
|
+
|
|
1584
|
+
// pkgs/@akanjs/devkit/frontendBuild/routeClientBuilder.ts
|
|
1585
|
+
var SSR_CLIENT_ALIAS_EXTERNALS = [
|
|
1586
|
+
"react",
|
|
1587
|
+
"react-dom",
|
|
1588
|
+
"react-dom/client",
|
|
1589
|
+
"react/jsx-runtime",
|
|
1590
|
+
"react/jsx-dev-runtime"
|
|
1591
|
+
];
|
|
1592
|
+
var SSR_CLIENT_EXTERNALS = [...SSR_CLIENT_ALIAS_EXTERNALS, "akanjs/fetch"];
|
|
1593
|
+
|
|
1594
|
+
class RouteClientBuilder {
|
|
1595
|
+
#app;
|
|
1596
|
+
#seeds;
|
|
1597
|
+
#graphSeeds;
|
|
1598
|
+
#knownEntries;
|
|
1599
|
+
#command;
|
|
1600
|
+
#discovery;
|
|
1601
|
+
#entries;
|
|
1602
|
+
#browser;
|
|
1603
|
+
constructor(options) {
|
|
1604
|
+
this.#app = options.app;
|
|
1605
|
+
this.#seeds = options.seeds;
|
|
1606
|
+
this.#graphSeeds = options.graphSeeds;
|
|
1607
|
+
this.#knownEntries = options.knownEntries ?? new Set;
|
|
1608
|
+
this.#command = options.command ?? "start";
|
|
1609
|
+
this.#discovery = options.discovery;
|
|
1610
|
+
this.#entries = options.entries;
|
|
1611
|
+
this.#browser = options.browser ?? (this.#command === "start" ? "registry" : "chunks");
|
|
1612
|
+
}
|
|
1613
|
+
async build() {
|
|
1614
|
+
const discovered = this.#entries ?? await (await this.#getDiscovery()).discover(this.#seeds);
|
|
1615
|
+
if (discovered.every((e) => this.#knownEntries.has(e)))
|
|
1616
|
+
return this.#emptyResult(discovered);
|
|
1617
|
+
const entries = this.#graphSeeds ? [...new Set([...discovered, ...await (await this.#getDiscovery()).discover(this.#graphSeeds)])].sort() : discovered.filter((e) => !this.#knownEntries.has(e));
|
|
1618
|
+
const bootstrapEntries = await this.#createBootstrapEntries(entries);
|
|
1619
|
+
const registry = this.#browser === "registry" ? new CsrDevPaths(this.#app.workspace.workspaceRoot) : null;
|
|
1620
|
+
const browserBundle = registry ? null : await this.#buildBrowserBundle(bootstrapEntries);
|
|
1621
|
+
const ssrBundle = await this.#buildSsrBundle(bootstrapEntries);
|
|
1622
|
+
const referenceBundle = browserBundle ?? ssrBundle;
|
|
1623
|
+
const acceptedEntries = new Set(entries);
|
|
1624
|
+
const routeEntries = new Set(discovered);
|
|
1625
|
+
const manifestDelta = {};
|
|
1626
|
+
const ssrModuleMap = {};
|
|
1627
|
+
const clientDeps = new Set;
|
|
1628
|
+
const clientDepsByEntry = {};
|
|
1629
|
+
for (const [key, bundleRow] of Object.entries(referenceBundle.manifest)) {
|
|
1630
|
+
const manifestEntry = RouteClientBuilder.resolveOriginalManifestEntry(key, bootstrapEntries.originalByBuildEntry, referenceBundle.clientReferenceIdByAbsPath, this.#app.workspace.workspaceRoot);
|
|
1631
|
+
if (!manifestEntry)
|
|
1632
|
+
continue;
|
|
1633
|
+
if (!acceptedEntries.has(manifestEntry.originalEntry))
|
|
1634
|
+
continue;
|
|
1635
|
+
const row = registry ? RouteClientBuilder.#registryRow(registry, manifestEntry.originalEntry, bundleRow) : bundleRow;
|
|
1636
|
+
manifestDelta[manifestEntry.key] = row;
|
|
1637
|
+
const ssrOutput = ssrBundle.entryOutputAbsByAbsPath.get(manifestEntry.buildEntry);
|
|
1638
|
+
if (!ssrOutput)
|
|
1639
|
+
continue;
|
|
1640
|
+
ssrModuleMap[row.id] ??= {};
|
|
1641
|
+
ssrModuleMap[row.id][row.name] = { id: ssrOutput, chunks: [ssrOutput, ssrOutput], name: row.name, async: true };
|
|
1838
1642
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
const
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
const result = rest.length === 0 ? empty : await compile(rest).catch(() => {
|
|
1853
|
-
throw error;
|
|
1854
|
-
});
|
|
1855
|
-
const modules = { ...graph.modules };
|
|
1856
|
-
for (const module of result.modules)
|
|
1857
|
-
modules[module.id] = {
|
|
1858
|
-
vendor: module.vendor,
|
|
1859
|
-
mtimeMs: module.mtimeMs,
|
|
1860
|
-
hash: module.hash,
|
|
1861
|
-
deps: module.deps.map((dep) => CsrDevPaths.isStub(dep) ? dep : paths.idOf(dep))
|
|
1862
|
-
};
|
|
1863
|
-
const reached = await this.#bundler.reachable({ ...graph, modules });
|
|
1864
|
-
if (broken.some((id) => reached.has(id)))
|
|
1865
|
-
throw error;
|
|
1866
|
-
const orphans = Object.keys(modules).filter((id) => !reached.has(id) && !modules[id]?.vendor);
|
|
1867
|
-
return { result, orphans };
|
|
1643
|
+
for (const entry of bootstrapEntries.buildEntries) {
|
|
1644
|
+
const buildEntry = path6.resolve(entry);
|
|
1645
|
+
const originalEntry = path6.resolve(bootstrapEntries.originalByBuildEntry.get(buildEntry) ?? buildEntry);
|
|
1646
|
+
if (!acceptedEntries.has(originalEntry))
|
|
1647
|
+
continue;
|
|
1648
|
+
const deps = new Set([originalEntry]);
|
|
1649
|
+
for (const dep of referenceBundle.entryDepsByAbsPath.get(buildEntry) ?? [])
|
|
1650
|
+
deps.add(path6.resolve(dep));
|
|
1651
|
+
const sortedDeps = [...deps].sort();
|
|
1652
|
+
clientDepsByEntry[originalEntry] = sortedDeps;
|
|
1653
|
+
if (routeEntries.has(originalEntry))
|
|
1654
|
+
for (const dep of sortedDeps)
|
|
1655
|
+
clientDeps.add(dep);
|
|
1868
1656
|
}
|
|
1869
|
-
}
|
|
1870
|
-
#caseTwins(graph, compiled) {
|
|
1871
|
-
const { paths } = this.#bundler;
|
|
1872
|
-
const idsByFold = new Map;
|
|
1873
|
-
for (const id of Object.keys(graph.modules))
|
|
1874
|
-
idsByFold.set(id.toLowerCase(), [...idsByFold.get(id.toLowerCase()) ?? [], id]);
|
|
1875
|
-
return compiled.flatMap((module) => (idsByFold.get(module.id.toLowerCase()) ?? []).filter((id) => id !== module.id && !graph.modules[id]?.vendor && !CsrDevPaths.isNamed(paths.fileOf(id))));
|
|
1876
|
-
}
|
|
1877
|
-
async#withTwinImporters(compile, graph, result) {
|
|
1878
|
-
const twins = new Set(this.#caseTwins(graph, result.modules));
|
|
1879
|
-
if (twins.size === 0)
|
|
1880
|
-
return result;
|
|
1881
|
-
const compiledIds = new Set(result.modules.map((module) => module.id));
|
|
1882
|
-
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);
|
|
1883
|
-
if (importers.length === 0)
|
|
1884
|
-
return result;
|
|
1885
|
-
const more = await compile(importers);
|
|
1886
1657
|
return {
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1658
|
+
manifestDelta,
|
|
1659
|
+
ssrManifestDelta: { moduleLoading: null, moduleMap: ssrModuleMap },
|
|
1660
|
+
newEntries: entries.filter((e) => !this.#knownEntries.has(e)),
|
|
1661
|
+
discoveredEntries: discovered,
|
|
1662
|
+
clientDeps: [...clientDeps].sort(),
|
|
1663
|
+
clientDepsByEntry,
|
|
1664
|
+
...registry ? { registryEntries: [...acceptedEntries].sort() } : {}
|
|
1890
1665
|
};
|
|
1891
1666
|
}
|
|
1892
|
-
static #
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
const found = bases.map((base) => path7.resolve(base, quoted)).find((file) => CsrDevPaths.isScript(file) && fs4.existsSync(file));
|
|
1900
|
-
return found ? [CsrDevPaths.realpath(found)] : [];
|
|
1901
|
-
});
|
|
1902
|
-
});
|
|
1903
|
-
return [...new Set([...failing, ...named])];
|
|
1904
|
-
}
|
|
1905
|
-
static #failingFiles(error) {
|
|
1906
|
-
const messages = error instanceof AggregateError ? error.errors : [];
|
|
1907
|
-
return messages.map((message) => message.position?.file).filter((file) => typeof file === "string" && path7.isAbsolute(file)).map((file) => CsrDevPaths.realpath(file));
|
|
1667
|
+
static #registryRow(paths, entry, row) {
|
|
1668
|
+
return {
|
|
1669
|
+
id: `${SSR_DEV_ID_PREFIX}${paths.idOf(entry)}`,
|
|
1670
|
+
chunks: [SSR_DEV_CHUNK, SSR_DEV_CHUNK],
|
|
1671
|
+
name: row.name,
|
|
1672
|
+
async: true
|
|
1673
|
+
};
|
|
1908
1674
|
}
|
|
1909
|
-
async#
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
if (outside.length === 0)
|
|
1913
|
-
return [];
|
|
1914
|
-
const wanted = await this.#bundler.wantedRoots(outside.map((id) => paths.fileOf(id)), graph);
|
|
1915
|
-
const kept = outside.filter((id) => wanted.has(paths.fileOf(id)));
|
|
1916
|
-
graph.pending = graph.pending.filter((id) => graph.modules[id] || kept.includes(id));
|
|
1917
|
-
const carried = [];
|
|
1918
|
-
for (const id of kept)
|
|
1919
|
-
if (!await this.#parked(graph, id))
|
|
1920
|
-
carried.push(id);
|
|
1921
|
-
return carried;
|
|
1675
|
+
async#getDiscovery() {
|
|
1676
|
+
this.#discovery ??= await GraphClientEntryDiscovery.create(this.#app);
|
|
1677
|
+
return this.#discovery;
|
|
1922
1678
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
handedOver.failed = graph.failed.files;
|
|
1933
|
-
}
|
|
1934
|
-
if (!handedOver.failed)
|
|
1935
|
-
return false;
|
|
1936
|
-
for (const [failedId, hash] of Object.entries(handedOver.failed))
|
|
1937
|
-
if (await CsrDevPaths.hashOf(paths.fileOf(failedId)) !== hash)
|
|
1938
|
-
return false;
|
|
1939
|
-
return true;
|
|
1679
|
+
#emptyResult(discoveredEntries = []) {
|
|
1680
|
+
return {
|
|
1681
|
+
manifestDelta: {},
|
|
1682
|
+
ssrManifestDelta: { moduleLoading: null, moduleMap: {} },
|
|
1683
|
+
newEntries: [],
|
|
1684
|
+
discoveredEntries,
|
|
1685
|
+
clientDeps: [],
|
|
1686
|
+
clientDepsByEntry: {}
|
|
1687
|
+
};
|
|
1940
1688
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1689
|
+
async#buildBrowserBundle(bootstrapEntries) {
|
|
1690
|
+
const reactFastRefresh = process.env.AKAN_REACT_FAST_REFRESH !== "0";
|
|
1691
|
+
return new ClientEntriesBundler({
|
|
1692
|
+
app: this.#app,
|
|
1693
|
+
entries: bootstrapEntries.buildEntries,
|
|
1694
|
+
plugins: [
|
|
1695
|
+
await createBarrelImportsPlugin(this.#app, {
|
|
1696
|
+
pipeAfter: reactFastRefresh ? RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh : undefined
|
|
1697
|
+
})
|
|
1698
|
+
],
|
|
1699
|
+
external: VENDOR_SPECIFIERS,
|
|
1700
|
+
command: this.#command,
|
|
1701
|
+
reactFastRefresh
|
|
1702
|
+
}).bundle();
|
|
1943
1703
|
}
|
|
1944
|
-
#
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1704
|
+
async#buildSsrBundle(bootstrapEntries) {
|
|
1705
|
+
return new ClientEntriesBundler({
|
|
1706
|
+
app: this.#app,
|
|
1707
|
+
entries: bootstrapEntries.buildEntries,
|
|
1708
|
+
plugins: [await createBarrelImportsPlugin(this.#app)],
|
|
1709
|
+
...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
|
|
1710
|
+
outputSubdir: "client-ssr",
|
|
1711
|
+
command: this.#command
|
|
1712
|
+
}).bundle();
|
|
1948
1713
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1714
|
+
async#createBootstrapEntries(entries) {
|
|
1715
|
+
if (!await Bun.file(path6.join(this.#app.cwdPath, "lib", "st.ts")).exists()) {
|
|
1716
|
+
return { buildEntries: entries, originalByBuildEntry: new Map };
|
|
1717
|
+
}
|
|
1718
|
+
const outdir = path6.join(this.#app.cwdPath, ".akan", "generated", "client-entry-bootstrap");
|
|
1719
|
+
await mkdir2(outdir, { recursive: true });
|
|
1720
|
+
const originalByBuildEntry = new Map;
|
|
1721
|
+
const buildEntries = await Promise.all(entries.map(async (entry) => {
|
|
1722
|
+
const absEntry = path6.resolve(entry);
|
|
1723
|
+
const hash = Bun.hash(`${this.#app.name}
|
|
1724
|
+
${absEntry}`).toString(36);
|
|
1725
|
+
const base = path6.basename(absEntry).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
1726
|
+
const wrapperEntry = path6.join(outdir, `${base}-${hash}.tsx`);
|
|
1727
|
+
const exportNames = await this.#scanExportNames(absEntry);
|
|
1728
|
+
await Bun.write(wrapperEntry, RouteClientBuilder.createStoreBootstrapEntrySource({
|
|
1729
|
+
appName: this.#app.name,
|
|
1730
|
+
originalEntry: absEntry,
|
|
1731
|
+
exportNames
|
|
1732
|
+
}));
|
|
1733
|
+
originalByBuildEntry.set(path6.resolve(wrapperEntry), absEntry);
|
|
1734
|
+
return wrapperEntry;
|
|
1735
|
+
}));
|
|
1736
|
+
return { buildEntries, originalByBuildEntry };
|
|
1952
1737
|
}
|
|
1953
|
-
#
|
|
1954
|
-
this.#
|
|
1955
|
-
return { kind: "delegate", reason, generation, context, ...first ? { first } : {} };
|
|
1738
|
+
async#scanExportNames(absEntry) {
|
|
1739
|
+
return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
|
|
1956
1740
|
}
|
|
1957
|
-
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
if (!manifest || !graph || graph.version !== 1 || await this.#bundler.writer.isBuilding())
|
|
1741
|
+
static normalizeNamedDefaultFunctionForFastRefresh(source, { path: filePath } = {}) {
|
|
1742
|
+
const declared = RouteClientBuilder.#declaredNamedDefaultFunctions(source, filePath);
|
|
1743
|
+
if (declared.length === 0)
|
|
1961
1744
|
return null;
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
state.code ??= await this.#bundler.writer.readCode(state.graph);
|
|
1968
|
-
return state.code;
|
|
1969
|
-
}
|
|
1970
|
-
async#changedModules(graph, changedFiles, present) {
|
|
1971
|
-
const { paths } = this.#bundler;
|
|
1972
|
-
const changed = new Set(graph.pending.filter((id) => graph.modules[id]));
|
|
1973
|
-
for (const file of changedFiles) {
|
|
1974
|
-
const id = paths.idOf(file);
|
|
1975
|
-
if (graph.modules[id])
|
|
1976
|
-
changed.add(id);
|
|
1977
|
-
}
|
|
1978
|
-
for (const id of this.#shadowedImporters(graph, changedFiles))
|
|
1979
|
-
changed.add(id);
|
|
1980
|
-
for (const [id, module] of Object.entries(graph.modules)) {
|
|
1981
|
-
if (module.vendor || changed.has(id))
|
|
1982
|
-
continue;
|
|
1983
|
-
const file = paths.fileOf(id);
|
|
1984
|
-
if (CsrDevPaths.mtimeOf(file) === module.mtimeMs)
|
|
1985
|
-
continue;
|
|
1986
|
-
if (await CsrDevPaths.hashOf(file) !== module.hash)
|
|
1987
|
-
changed.add(id);
|
|
1988
|
-
}
|
|
1989
|
-
for (const [id, module] of Object.entries(graph.modules)) {
|
|
1990
|
-
if (changed.has(id))
|
|
1991
|
-
continue;
|
|
1992
|
-
for (const dep of module.deps) {
|
|
1993
|
-
if (graph.modules[dep] || CsrDevPaths.isStub(dep))
|
|
1994
|
-
continue;
|
|
1995
|
-
const file = paths.fileOf(dep);
|
|
1996
|
-
if (present(file) && CsrDevPaths.isNamed(file))
|
|
1997
|
-
changed.add(dep);
|
|
1998
|
-
else if (!module.vendor)
|
|
1999
|
-
changed.add(id);
|
|
2000
|
-
}
|
|
1745
|
+
let next = "";
|
|
1746
|
+
let cursor = 0;
|
|
1747
|
+
for (const { index, text, lineStart, indent, asyncKeyword, name } of declared) {
|
|
1748
|
+
next += `${source.slice(cursor, index)}${lineStart}${indent}${asyncKeyword}function ${name}`;
|
|
1749
|
+
cursor = index + text.length;
|
|
2001
1750
|
}
|
|
2002
|
-
|
|
1751
|
+
next += source.slice(cursor);
|
|
1752
|
+
return `${next}
|
|
1753
|
+
${declared.map(({ name }) => `export default ${name};`).join(`
|
|
1754
|
+
`)}
|
|
1755
|
+
`;
|
|
2003
1756
|
}
|
|
2004
|
-
#
|
|
2005
|
-
const
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
1757
|
+
static #declaredNamedDefaultFunctions(source, filePath) {
|
|
1758
|
+
const candidates = [
|
|
1759
|
+
...source.matchAll(/(^|\n)([^\S\n]*)export\s+default\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)(?=\s*(?:<|\())/g)
|
|
1760
|
+
].map((match) => ({
|
|
1761
|
+
index: match.index,
|
|
1762
|
+
text: match[0],
|
|
1763
|
+
lineStart: match[1] ?? "",
|
|
1764
|
+
indent: match[2] ?? "",
|
|
1765
|
+
asyncKeyword: match[3] ?? "",
|
|
1766
|
+
name: match[4] ?? ""
|
|
2009
1767
|
}));
|
|
2010
|
-
if (
|
|
1768
|
+
if (candidates.length === 0)
|
|
2011
1769
|
return [];
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
1770
|
+
const probeName = (idx) => `__akanNamedDefault${idx}`;
|
|
1771
|
+
let probe = "";
|
|
1772
|
+
let cursor = 0;
|
|
1773
|
+
candidates.forEach(({ index, text, lineStart, indent, asyncKeyword }, idx) => {
|
|
1774
|
+
probe += `${source.slice(cursor, index)}${lineStart}${indent}export ${asyncKeyword}function ${probeName(idx)}`;
|
|
1775
|
+
cursor = index + text.length;
|
|
1776
|
+
});
|
|
1777
|
+
probe += source.slice(cursor);
|
|
1778
|
+
const exported = RouteClientBuilder.#scanExports(probe, filePath);
|
|
1779
|
+
return candidates.filter((_, idx) => exported.has(probeName(idx)));
|
|
2018
1780
|
}
|
|
2019
|
-
#
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
code?.modules.delete(id);
|
|
1781
|
+
static #scanExports(source, filePath) {
|
|
1782
|
+
try {
|
|
1783
|
+
return new Set(new Bun.Transpiler({ loader: filePath ? loaderFor(filePath) : "tsx" }).scan(source).exports);
|
|
1784
|
+
} catch {
|
|
1785
|
+
return new Set;
|
|
2025
1786
|
}
|
|
2026
|
-
|
|
1787
|
+
}
|
|
1788
|
+
static resolveSsrClientRuntimeAliases() {
|
|
1789
|
+
const serverEntry = RouteClientBuilder.resolveAkanServerEntry();
|
|
1790
|
+
return { [Bun.resolveSync("akanjs/fetch", serverEntry)]: "akanjs/fetch" };
|
|
1791
|
+
}
|
|
1792
|
+
static resolveSsrClientBundleOptions(command) {
|
|
1793
|
+
if (command === "start") {
|
|
1794
|
+
return {
|
|
1795
|
+
target: "bun",
|
|
1796
|
+
external: SSR_CLIENT_EXTERNALS,
|
|
1797
|
+
externalSubpaths: ["akanjs/fetch"],
|
|
1798
|
+
externalAliases: RouteClientBuilder.resolveSsrClientRuntimeAliases()
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
return { target: "bun", external: SSR_CLIENT_ALIAS_EXTERNALS };
|
|
1802
|
+
}
|
|
1803
|
+
static resolveAkanServerEntry() {
|
|
1804
|
+
try {
|
|
1805
|
+
return Bun.resolveSync("akanjs/server", import.meta.dir);
|
|
1806
|
+
} catch {
|
|
1807
|
+
return path6.resolve(import.meta.dir, "../../../server/index.ts");
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
static createStoreBootstrapEntrySource(args) {
|
|
1811
|
+
const originalEntry = JSON.stringify(path6.resolve(args.originalEntry));
|
|
1812
|
+
const namedExports = args.exportNames.filter((name) => name !== "default");
|
|
1813
|
+
const lines = [
|
|
1814
|
+
`import ${JSON.stringify(`@apps/${args.appName}/client`)};`,
|
|
1815
|
+
...namedExports.length > 0 ? [`export { ${namedExports.join(", ")} } from ${originalEntry};`] : []
|
|
1816
|
+
];
|
|
1817
|
+
if (args.exportNames.includes("default"))
|
|
1818
|
+
lines.push(`export { default } from ${originalEntry};`);
|
|
1819
|
+
return `${lines.join(`
|
|
1820
|
+
`)}
|
|
1821
|
+
`;
|
|
1822
|
+
}
|
|
1823
|
+
static resolveOriginalManifestEntry(manifestKey, originalByBuildEntry, clientReferenceIdByBuildEntry = new Map, workspaceRoot = process.cwd()) {
|
|
1824
|
+
const hashIdx = manifestKey.lastIndexOf("#");
|
|
1825
|
+
if (hashIdx < 0)
|
|
1826
|
+
return null;
|
|
1827
|
+
const buildReferenceId = manifestKey.slice(0, hashIdx);
|
|
1828
|
+
const name = manifestKey.slice(hashIdx + 1);
|
|
1829
|
+
const buildEntry = [...clientReferenceIdByBuildEntry.entries()].find(([, referenceId]) => referenceId === buildReferenceId)?.[0] ?? buildReferenceId;
|
|
1830
|
+
const originalEntry = originalByBuildEntry.get(buildEntry) ?? buildEntry;
|
|
1831
|
+
const originalReferenceId = toClientReferencePath(originalEntry, workspaceRoot);
|
|
1832
|
+
return { buildEntry, originalEntry, name, key: `${originalReferenceId}#${name}` };
|
|
2027
1833
|
}
|
|
2028
1834
|
}
|
|
2029
1835
|
|
|
2030
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
import path8 from "path";
|
|
2044
|
-
import {
|
|
2045
|
-
appGenerationOf as appGenerationOf2,
|
|
2046
|
-
CSR_DEV_APP_FILE,
|
|
2047
|
-
CSR_DEV_KEPT_PATCHES,
|
|
2048
|
-
CSR_DEV_MANIFEST_FILE as CSR_DEV_MANIFEST_FILE2,
|
|
2049
|
-
CSR_DEV_PATCHING_MARKER,
|
|
2050
|
-
csrDevModuleFile
|
|
2051
|
-
} from "akanjs/server/hmr/csrDevManifest";
|
|
2052
|
-
class CsrDevArtifactWriter {
|
|
2053
|
-
static #keptVendors = 3;
|
|
2054
|
-
static #buildingMarker = ".building";
|
|
1836
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
|
|
1837
|
+
class CsrDevModuleCompiler {
|
|
1838
|
+
static #storeRoot = /(?:^|\/)lib\/st\.ts$/;
|
|
1839
|
+
static #macroImport = /\b(?:with|assert)\s*\{\s*type\s*:\s*["']macro["']/;
|
|
1840
|
+
static #factoryArgument = /(?<![\w$.])(?:require|module|exports|__akanImport|\$RefreshReg\$|\$RefreshSig\$)(?![\w$]|\s*:)/;
|
|
1841
|
+
static #helperPatches = [
|
|
1842
|
+
["__toESM", "(mod, isNodeMode, target)", "toESM"],
|
|
1843
|
+
["__reExport", "(target, mod, secondTarget)", "reExport"]
|
|
1844
|
+
];
|
|
1845
|
+
#app;
|
|
1846
|
+
#paths;
|
|
1847
|
+
#resolver;
|
|
1848
|
+
#context;
|
|
2055
1849
|
#outDir;
|
|
2056
1850
|
#routePrefix;
|
|
2057
|
-
#
|
|
2058
|
-
constructor(
|
|
1851
|
+
#assets = new Set;
|
|
1852
|
+
constructor({ app, paths, resolver, context, outDir, routePrefix }) {
|
|
1853
|
+
this.#app = app;
|
|
1854
|
+
this.#paths = paths;
|
|
1855
|
+
this.#resolver = resolver;
|
|
1856
|
+
this.#context = context;
|
|
2059
1857
|
this.#outDir = outDir;
|
|
2060
1858
|
this.#routePrefix = routePrefix;
|
|
2061
|
-
this.#library = library;
|
|
2062
|
-
}
|
|
2063
|
-
async markBuilding() {
|
|
2064
|
-
await Bun.write(path8.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker), String(process.pid));
|
|
2065
|
-
}
|
|
2066
|
-
async clearBuilding() {
|
|
2067
|
-
await CsrDevArtifactWriter.#remove(path8.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker));
|
|
2068
|
-
}
|
|
2069
|
-
async isBuilding() {
|
|
2070
|
-
return await Bun.file(path8.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker)).exists();
|
|
2071
|
-
}
|
|
2072
|
-
async forgetPatch(generation) {
|
|
2073
|
-
await Promise.all([`patch-${generation}.js`, `patch-${generation}.js.layout.json`].map((name) => CsrDevArtifactWriter.#remove(path8.join(this.#outDir, name))));
|
|
2074
|
-
}
|
|
2075
|
-
async readJson(name) {
|
|
2076
|
-
const file = Bun.file(path8.join(this.#outDir, name));
|
|
2077
|
-
if (!await file.exists())
|
|
2078
|
-
return null;
|
|
2079
|
-
return await file.json().catch(() => null);
|
|
2080
1859
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
1860
|
+
get assets() {
|
|
1861
|
+
return [...this.#assets];
|
|
2083
1862
|
}
|
|
2084
|
-
async
|
|
2085
|
-
|
|
2086
|
-
const
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
1863
|
+
async compile(startFiles, known, { refuseNewVendor = false, refusePrepass = false } = {}) {
|
|
1864
|
+
const compiled = new Map;
|
|
1865
|
+
const seen = new Set([...known, ...startFiles]);
|
|
1866
|
+
let frontier = [...new Set(startFiles)];
|
|
1867
|
+
while (frontier.length > 0) {
|
|
1868
|
+
const refusedVendors = refuseNewVendor ? frontier.filter((file) => CsrDevPaths.isVendorFile(file)) : [];
|
|
1869
|
+
if (refusedVendors.length > 0)
|
|
1870
|
+
return { modules: [...compiled.values()], refusedVendors, unresolved: [] };
|
|
1871
|
+
await this.#resolver.cover(frontier);
|
|
1872
|
+
const round = await this.#compileRound(frontier);
|
|
1873
|
+
if (round.misses.length > 0 && !this.#resolver.prepassDone && refusePrepass)
|
|
1874
|
+
return { modules: [...compiled.values()], refusedVendors: [], unresolved: round.misses };
|
|
1875
|
+
if (round.misses.length > 0 && !this.#resolver.prepassDone) {
|
|
1876
|
+
this.#app.verbose(`[csr-dev] ${round.misses.length} unresolved import(s); rerunning the resolution build`);
|
|
1877
|
+
await this.#resolver.prepass();
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
const next = [];
|
|
1881
|
+
for (const module of round.modules) {
|
|
1882
|
+
compiled.set(module.file, module);
|
|
1883
|
+
for (const dep of module.deps) {
|
|
1884
|
+
if (CsrDevPaths.isStub(dep) || seen.has(dep))
|
|
1885
|
+
continue;
|
|
1886
|
+
seen.add(dep);
|
|
1887
|
+
next.push(dep);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
frontier = next;
|
|
1891
|
+
}
|
|
1892
|
+
const { fallbacks } = this.#resolver;
|
|
1893
|
+
if (fallbacks.size > 0)
|
|
1894
|
+
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 ? ", ..." : ""}`);
|
|
1895
|
+
return { modules: [...compiled.values()], refusedVendors: [], unresolved: [] };
|
|
2094
1896
|
}
|
|
2095
|
-
async
|
|
2096
|
-
const
|
|
2097
|
-
const
|
|
2098
|
-
const
|
|
2099
|
-
|
|
2100
|
-
|
|
1897
|
+
async#compileRound(files) {
|
|
1898
|
+
const appFiles = files.filter((file) => !CsrDevPaths.isVendorFile(file));
|
|
1899
|
+
const vendorFiles = files.filter((file) => CsrDevPaths.isVendorFile(file));
|
|
1900
|
+
const rounds = await Promise.all([
|
|
1901
|
+
appFiles.length > 0 ? this.#bunBuild(appFiles, false) : null,
|
|
1902
|
+
vendorFiles.length > 0 ? this.#bunBuild(vendorFiles, true) : null
|
|
2101
1903
|
]);
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
1904
|
+
const modules = rounds.flatMap((round) => round?.modules ?? []);
|
|
1905
|
+
const misses = rounds.flatMap((round) => round?.misses ?? []);
|
|
1906
|
+
for (const file of rounds.flatMap((round) => round?.tangled ?? [])) {
|
|
1907
|
+
const single = await this.#bunBuild([file], CsrDevPaths.isVendorFile(file));
|
|
1908
|
+
if (single.tangled.length > 0)
|
|
1909
|
+
throw new Error(`[csr-dev] ${this.#paths.idOf(file)} kept inlining another module`);
|
|
1910
|
+
modules.push(...single.modules);
|
|
1911
|
+
misses.push(...single.misses);
|
|
1912
|
+
}
|
|
1913
|
+
return { modules, misses };
|
|
2112
1914
|
}
|
|
2113
|
-
async
|
|
2114
|
-
const
|
|
2115
|
-
const
|
|
2116
|
-
const
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
1915
|
+
async#bunBuild(files, vendor) {
|
|
1916
|
+
const mtimes = new Map(files.map((file) => [CsrDevPaths.realpath(file), CsrDevPaths.mtimeOf(file)]));
|
|
1917
|
+
const hashes = new Map(await Promise.all(files.map(async (file) => [CsrDevPaths.realpath(file), vendor ? "" : await CsrDevPaths.hashOf(file)])));
|
|
1918
|
+
const misses = [];
|
|
1919
|
+
const macroOutputs = vendor ? new Map : await this.#evaluateMacros(files);
|
|
1920
|
+
let result;
|
|
1921
|
+
try {
|
|
1922
|
+
result = await Bun.build({
|
|
1923
|
+
entrypoints: files,
|
|
1924
|
+
root: this.#paths.root,
|
|
1925
|
+
target: "browser",
|
|
1926
|
+
format: "cjs",
|
|
1927
|
+
reactFastRefresh: !vendor,
|
|
1928
|
+
sourcemap: vendor ? "none" : "external",
|
|
1929
|
+
naming: { entry: "[dir]/[name].[ext]", asset: "assets/[name]-[hash].[ext]" },
|
|
1930
|
+
publicPath: this.#routePrefix,
|
|
1931
|
+
metafile: true,
|
|
1932
|
+
define: { ...this.#context.define, "import.meta": "__akanMeta" },
|
|
1933
|
+
optimizeImports: this.#context.optimizeImports,
|
|
1934
|
+
plugins: [this.#registryPlugin(vendor, misses, macroOutputs)]
|
|
1935
|
+
});
|
|
1936
|
+
} catch (error) {
|
|
1937
|
+
if (vendor && error instanceof AggregateError)
|
|
1938
|
+
return await this.#withoutFailed(files, error);
|
|
1939
|
+
throw error;
|
|
1940
|
+
}
|
|
1941
|
+
const { outputs } = result.metafile;
|
|
1942
|
+
const sourceMaps = new Map(result.outputs.filter((artifact) => artifact.kind === "sourcemap").map((artifact) => [artifact.path, artifact]));
|
|
1943
|
+
const modules = [];
|
|
1944
|
+
const tangled = [];
|
|
1945
|
+
for (const artifact of result.outputs) {
|
|
1946
|
+
if (artifact.kind === "asset") {
|
|
1947
|
+
const assetPath = path7.join(this.#outDir, artifact.path);
|
|
1948
|
+
await Bun.write(assetPath, artifact);
|
|
1949
|
+
this.#assets.add(assetPath);
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
if (artifact.kind !== "entry-point")
|
|
1953
|
+
continue;
|
|
1954
|
+
const meta = outputs[artifact.path];
|
|
1955
|
+
if (!meta?.entryPoint)
|
|
1956
|
+
throw new Error(`[csr-dev] no entry point recorded for ${artifact.path}`);
|
|
1957
|
+
const file = CsrDevPaths.realpath(path7.resolve(meta.entryPoint));
|
|
1958
|
+
const inputs = Object.keys(meta.inputs).map((input) => CsrDevPaths.realpath(path7.resolve(input)));
|
|
1959
|
+
if (inputs.some((input) => input !== file && this.#resolver.isRegistryModule(input))) {
|
|
1960
|
+
tangled.push(file);
|
|
2121
1961
|
continue;
|
|
2122
1962
|
}
|
|
2123
|
-
const
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
1963
|
+
const code = await artifact.text();
|
|
1964
|
+
const id = this.#paths.idOf(file);
|
|
1965
|
+
const built = CsrDevModuleCompiler.#factory(id, code);
|
|
1966
|
+
const problem = CsrDevFactoryCheck.problemOf(built.factory, built.helpers?.definition);
|
|
1967
|
+
if (problem && !vendor)
|
|
1968
|
+
throw new Error(`[csr-dev] ${id}: its factory does not parse as a classic script: ${problem}`);
|
|
1969
|
+
if (problem) {
|
|
1970
|
+
modules.push(this.#leftOut(file, problem));
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
const sourceMap = sourceMaps.get(`${artifact.path}.map`);
|
|
1974
|
+
modules.push({
|
|
1975
|
+
id,
|
|
1976
|
+
file,
|
|
1977
|
+
vendor,
|
|
1978
|
+
...built,
|
|
1979
|
+
sourceMap: sourceMap ? this.#rebaseSourceMap(await sourceMap.text()) : undefined,
|
|
1980
|
+
deps: this.#emittedDeps(code),
|
|
1981
|
+
mtimeMs: mtimes.get(file) ?? CsrDevPaths.mtimeOf(file),
|
|
1982
|
+
hash: hashes.get(file) ?? ""
|
|
1983
|
+
});
|
|
2128
1984
|
}
|
|
2129
|
-
|
|
2130
|
-
const vendorFile = `vendor-${Bun.hash(code).toString(36)}.js`;
|
|
2131
|
-
await this.#writeAtomic(vendorFile, code);
|
|
2132
|
-
return vendorFile;
|
|
2133
|
-
}
|
|
2134
|
-
async writeApp(graph, { generation, vendorFile, epoch }, code) {
|
|
2135
|
-
const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor).sort();
|
|
2136
|
-
const lines = await this.#defineLines(ids, code);
|
|
2137
|
-
const blocks = ids.map((id, index) => [id, lines[index] ?? ""]);
|
|
2138
|
-
const start = this.#library ? `__akan.startLibrary(${JSON.stringify({ generation, refresh: graph.refresh, bootstrap: graph.entries[""], vendorFile, epoch })});
|
|
2139
|
-
` : `__akan.start(${JSON.stringify({ generation, refresh: graph.refresh })});
|
|
2140
|
-
`;
|
|
2141
|
-
await this.#writeWithSourceMap(CSR_DEV_APP_FILE, await this.#helperDefinitions(graph, ids, code), blocks, start);
|
|
2142
|
-
}
|
|
2143
|
-
async writePatch(generation, modules) {
|
|
2144
|
-
const patchFile = `patch-${generation}.js`;
|
|
2145
|
-
const blocks = modules.map((module, index) => [module.id, `${JSON.stringify(module.id)}: ${module.factory}${index < modules.length - 1 ? `,
|
|
2146
|
-
` : `
|
|
2147
|
-
`}`]);
|
|
2148
|
-
const helpers = new Map(modules.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
|
|
2149
|
-
const header = `${[...helpers.values()].map((shared) => shared.definition).join("")}__akan.update(${generation}, {
|
|
2150
|
-
`;
|
|
2151
|
-
await this.#writeWithSourceMap(patchFile, header, blocks, `});
|
|
2152
|
-
`);
|
|
2153
|
-
return `${this.#routePrefix}${patchFile}`;
|
|
2154
|
-
}
|
|
2155
|
-
async writeState(graph, manifest) {
|
|
2156
|
-
await this.writeJson("graph.json", graph);
|
|
2157
|
-
await this.writeJson(CSR_DEV_MANIFEST_FILE2, manifest);
|
|
1985
|
+
return { modules, misses, tangled };
|
|
2158
1986
|
}
|
|
2159
|
-
async
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
1987
|
+
async#withoutFailed(files, error) {
|
|
1988
|
+
const reasons = new Map;
|
|
1989
|
+
for (const message of error.errors) {
|
|
1990
|
+
const file = message.position?.file;
|
|
1991
|
+
if (typeof file !== "string" || !path7.isAbsolute(file))
|
|
1992
|
+
continue;
|
|
1993
|
+
const real = CsrDevPaths.realpath(file);
|
|
1994
|
+
const at = message.position?.line ? `:${message.position.line}` : "";
|
|
1995
|
+
reasons.set(real, [...reasons.get(real) ?? [], `${String(message.message)} (${this.#paths.idOf(real)}${at})`]);
|
|
1996
|
+
}
|
|
1997
|
+
const failed = new Set(files.filter((file) => reasons.has(CsrDevPaths.realpath(file))));
|
|
1998
|
+
if (failed.size === 0)
|
|
1999
|
+
throw error;
|
|
2000
|
+
const rest = files.filter((file) => !failed.has(file));
|
|
2001
|
+
const round = rest.length > 0 ? await this.#bunBuild(rest, true) : { modules: [], misses: [], tangled: [] };
|
|
2002
|
+
for (const file of failed) {
|
|
2003
|
+
const text = (reasons.get(CsrDevPaths.realpath(file)) ?? []).join("; ");
|
|
2004
|
+
const awaits = text.includes('"await" can only be used');
|
|
2005
|
+
round.modules.push(this.#leftOut(file, awaits ? `a top-level await, which a CommonJS factory cannot wait on: ${text}` : text));
|
|
2006
|
+
}
|
|
2007
|
+
return round;
|
|
2167
2008
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2009
|
+
#leftOut(file, problem) {
|
|
2010
|
+
const real = CsrDevPaths.realpath(file);
|
|
2011
|
+
const id = this.#paths.idOf(real);
|
|
2012
|
+
this.#app.logger.error(`[csr-dev] ${id} is left out of the dev registry: ${problem}`);
|
|
2013
|
+
return {
|
|
2014
|
+
id,
|
|
2015
|
+
file: real,
|
|
2016
|
+
vendor: true,
|
|
2017
|
+
factory: CsrDevFactoryCheck.thrower(id, problem),
|
|
2018
|
+
helpers: null,
|
|
2019
|
+
deps: [],
|
|
2020
|
+
mtimeMs: CsrDevPaths.mtimeOf(real),
|
|
2021
|
+
hash: ""
|
|
2022
|
+
};
|
|
2175
2023
|
}
|
|
2176
|
-
async
|
|
2177
|
-
const
|
|
2178
|
-
const
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2024
|
+
async#evaluateMacros(files) {
|
|
2025
|
+
const sources = await Promise.all(files.map(async (file) => [file, await Bun.file(file).text()]));
|
|
2026
|
+
const importers = sources.filter(([, source]) => CsrDevModuleCompiler.#macroImport.test(source));
|
|
2027
|
+
const outputs = await Promise.all(importers.map(async ([file]) => {
|
|
2028
|
+
const result = await Bun.build({
|
|
2029
|
+
entrypoints: [file],
|
|
2030
|
+
target: "browser",
|
|
2031
|
+
format: "esm",
|
|
2032
|
+
plugins: [
|
|
2033
|
+
{
|
|
2034
|
+
name: "akan-csr-macro-pass",
|
|
2035
|
+
setup: (build) => void build.onResolve({ filter: /.*/ }, (args) => args.importer ? { path: args.path, external: true } : undefined)
|
|
2036
|
+
}
|
|
2037
|
+
]
|
|
2038
|
+
});
|
|
2039
|
+
const [output] = result.outputs;
|
|
2040
|
+
if (!output)
|
|
2041
|
+
throw new Error(`[csr-dev] ${this.#paths.idOf(file)}: its macro pass wrote no output`);
|
|
2042
|
+
const text = await output.text();
|
|
2043
|
+
return [
|
|
2044
|
+
[file, text],
|
|
2045
|
+
[CsrDevPaths.realpath(file), text]
|
|
2046
|
+
];
|
|
2047
|
+
}));
|
|
2048
|
+
return new Map(outputs.flat());
|
|
2184
2049
|
}
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2050
|
+
#registryPlugin(vendor, misses, macroOutputs) {
|
|
2051
|
+
return {
|
|
2052
|
+
name: "akan-csr-registry",
|
|
2053
|
+
setup: (build) => {
|
|
2054
|
+
build.onLoad({ filter: /\.css$/ }, () => ({ contents: "", loader: "js" }));
|
|
2055
|
+
if (macroOutputs.size > 0) {
|
|
2056
|
+
const exact = [...macroOutputs.keys()].map((file) => file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
2057
|
+
build.onLoad({ filter: new RegExp(`^(?:${exact.join("|")})$`) }, (args) => {
|
|
2058
|
+
const contents = macroOutputs.get(args.path);
|
|
2059
|
+
return contents === undefined ? undefined : { contents, loader: "js" };
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
if (!vendor)
|
|
2063
|
+
build.onLoad({ filter: /\.tsx$/ }, async (args) => {
|
|
2064
|
+
if (CsrDevPaths.isVendorFile(args.path))
|
|
2065
|
+
return;
|
|
2066
|
+
const normalized = RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh(await Bun.file(args.path).text(), { path: args.path });
|
|
2067
|
+
return normalized ? { contents: normalized, loader: "tsx" } : undefined;
|
|
2068
|
+
});
|
|
2069
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
2070
|
+
if (args.kind === "entry-point-build" || !args.importer)
|
|
2071
|
+
return;
|
|
2072
|
+
if (this.#context.externals?.includes(args.path))
|
|
2073
|
+
return { path: `${CsrDevPaths.vendorPrefix}${args.path}`, external: true };
|
|
2074
|
+
const importer = CsrDevPaths.realpath(args.importer);
|
|
2075
|
+
const target = this.#resolver.resolve(importer, args.path, args.kind === "require-call" || args.kind === "require-resolve" ? "require" : "import");
|
|
2076
|
+
if (target === CsrDevResolver.inline)
|
|
2077
|
+
return;
|
|
2078
|
+
if (target === null) {
|
|
2079
|
+
misses.push(`${args.path} from ${this.#paths.idOf(importer)}`);
|
|
2080
|
+
return { path: `akan-miss:${args.path}`, external: true };
|
|
2081
|
+
}
|
|
2082
|
+
const external = CsrDevPaths.isStub(target) ? target : `${CsrDevPaths.modulePrefix}${this.#paths.idOf(target)}`;
|
|
2083
|
+
return { path: external, external: true };
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2204
2087
|
}
|
|
2205
|
-
|
|
2206
|
-
const
|
|
2207
|
-
|
|
2088
|
+
#rebaseSourceMap(text) {
|
|
2089
|
+
const map = JSON.parse(text);
|
|
2090
|
+
const files = map.sources.map((source) => path7.resolve(source));
|
|
2091
|
+
map.sources = files.map((file) => `/${this.#paths.idOf(file)}`);
|
|
2092
|
+
map.sourcesContent = files.map((file, index) => CsrDevPaths.isVendorFile(file) ? null : map.sourcesContent?.[index] ?? null);
|
|
2093
|
+
return JSON.stringify(map);
|
|
2208
2094
|
}
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2095
|
+
#emittedDeps(code) {
|
|
2096
|
+
const deps = new Set;
|
|
2097
|
+
const references = /\b(?:require|import|__akanImport)\("((?:akan-module|stub):[^"]+)"\)/g;
|
|
2098
|
+
for (const [, reference] of code.matchAll(references)) {
|
|
2099
|
+
if (!reference)
|
|
2100
|
+
continue;
|
|
2101
|
+
deps.add(CsrDevPaths.isStub(reference) ? reference : this.#paths.fileOf(reference.slice(CsrDevPaths.modulePrefix.length)));
|
|
2102
|
+
}
|
|
2103
|
+
return [...deps];
|
|
2215
2104
|
}
|
|
2216
|
-
|
|
2217
|
-
const
|
|
2218
|
-
let
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2105
|
+
static #factory(id, code) {
|
|
2106
|
+
const prefix = CsrDevPaths.modulePrefix;
|
|
2107
|
+
let patched = code.replace(/^#!/, "//").replaceAll(`import("${prefix}`, `__akanImport("${prefix}`).replace(/^\/\/# debugId=.*$/m, "");
|
|
2108
|
+
const marker = patched.search(/^\/\/ /m);
|
|
2109
|
+
const body = marker < 0 ? "" : patched.slice(marker);
|
|
2110
|
+
for (const [name, params, method] of CsrDevModuleCompiler.#helperPatches) {
|
|
2111
|
+
const definition = `var ${name} = ${params} => {`;
|
|
2112
|
+
if (patched.includes(definition))
|
|
2113
|
+
patched = patched.replace(definition, `var ${name} = __akan.${method}, __bun${name} = ${params} => {`);
|
|
2114
|
+
else if (patched.includes(`${name}(`) && !new RegExp(`\\b(?:var|let|const|function)\\s+${name}\\b`).test(body))
|
|
2115
|
+
throw new Error(`[csr-dev] ${id}: Bun's ${name} helper no longer reads "${definition}"; CsrDevModuleCompiler must follow`);
|
|
2222
2116
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2117
|
+
if (CsrDevModuleCompiler.#storeRoot.test(id))
|
|
2118
|
+
patched += `
|
|
2119
|
+
module.hot.accept();`;
|
|
2120
|
+
const shared = CsrDevModuleCompiler.#shareHelpers(patched);
|
|
2121
|
+
const meta = /(?<![\w$.])__akanMeta(?![\w$])/.test(patched) ? ` var __akanMeta = { url: new URL(${JSON.stringify(id.startsWith("/") ? id : `/${id}`)}, self.location.href).href };` : "";
|
|
2122
|
+
return {
|
|
2123
|
+
factory: `function (require, module, exports, $RefreshReg$, $RefreshSig$, __akanImport) {${meta}
|
|
2124
|
+
${shared?.code ?? patched}
|
|
2125
|
+
}`,
|
|
2126
|
+
helpers: shared?.helpers ?? null
|
|
2127
|
+
};
|
|
2228
2128
|
}
|
|
2229
|
-
|
|
2230
|
-
const
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2129
|
+
static #shareHelpers(code) {
|
|
2130
|
+
const marker = code.search(/^\/\/ /m);
|
|
2131
|
+
if (marker <= 0)
|
|
2132
|
+
return null;
|
|
2133
|
+
const preamble = code.slice(0, marker);
|
|
2134
|
+
const names = [...preamble.matchAll(/^(?:var|let|const|function)\s+([\w$]+)/gm)].map(([, name]) => name).filter((name) => !!name);
|
|
2135
|
+
if (names.length === 0 || CsrDevModuleCompiler.#factoryArgument.test(preamble))
|
|
2136
|
+
return null;
|
|
2137
|
+
const hash = Bun.hash(preamble).toString(36);
|
|
2138
|
+
const binding = `var { ${names.join(", ")} } = __akan.helpers(${JSON.stringify(hash)});`;
|
|
2139
|
+
return {
|
|
2140
|
+
code: `${binding}${`
|
|
2141
|
+
`.repeat(CsrDevPaths.lineCount(preamble))}${code.slice(marker)}`,
|
|
2142
|
+
helpers: {
|
|
2143
|
+
hash,
|
|
2144
|
+
definition: `__akan.defineHelpers(${JSON.stringify(hash)}, function () {
|
|
2145
|
+
${preamble}return { ${names.join(", ")} };
|
|
2146
|
+
});
|
|
2147
|
+
`
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2234
2150
|
}
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevPatcher.ts
|
|
2154
|
+
import fs5 from "fs";
|
|
2155
|
+
import path8 from "path";
|
|
2156
|
+
import { appGenerationOf as appGenerationOf2, CSR_DEV_MANIFEST_FILE as CSR_DEV_MANIFEST_FILE2 } from "akanjs/server/hmr/csrDevManifest";
|
|
2157
|
+
class CsrDevPatcher {
|
|
2158
|
+
#bundler;
|
|
2159
|
+
#resident;
|
|
2160
|
+
#state = null;
|
|
2161
|
+
#delegated = new Map;
|
|
2162
|
+
constructor(bundler, { resident = false } = {}) {
|
|
2163
|
+
this.#bundler = bundler;
|
|
2164
|
+
this.#resident = resident;
|
|
2239
2165
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
if (Number.isInteger(ms) && ms > 0)
|
|
2243
|
-
await Bun.sleep(ms);
|
|
2166
|
+
forget() {
|
|
2167
|
+
this.#state = null;
|
|
2244
2168
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2169
|
+
releaseHandedOver() {
|
|
2170
|
+
for (const [id, handedOver] of this.#delegated)
|
|
2171
|
+
if (!handedOver.failed)
|
|
2172
|
+
this.#delegated.delete(id);
|
|
2247
2173
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2174
|
+
async update(changedFiles = [], { announce, roots = [], onlyRoots = false, allowWholeAppBuilds = false } = {}) {
|
|
2175
|
+
const context = await this.#bundler.context();
|
|
2176
|
+
if (!context)
|
|
2177
|
+
return { kind: "unchanged" };
|
|
2178
|
+
const resident = this.#state;
|
|
2179
|
+
const state = resident ?? await this.#load();
|
|
2180
|
+
if (!state) {
|
|
2181
|
+
const manifest = await this.#bundler.writer.readJson(CSR_DEV_MANIFEST_FILE2);
|
|
2182
|
+
const reason = !manifest ? "first build" : await this.#bundler.writer.isBuilding() ? "the last whole build was cut short" : "the dev bundle config changed";
|
|
2183
|
+
return this.#handBack(reason, (manifest?.generation ?? 0) + 1, context, { first: !manifest });
|
|
2184
|
+
}
|
|
2185
|
+
const generation = state.manifest.generation + 1;
|
|
2186
|
+
if (state.graph.configKey !== context.configKey)
|
|
2187
|
+
return this.#handBack("the dev bundle config changed", generation, context);
|
|
2188
|
+
const metadataFile = changedFiles.find((file) => this.#bundler.isMetadataFile(file));
|
|
2189
|
+
if (metadataFile)
|
|
2190
|
+
return this.#handBack(`${path8.basename(metadataFile)} changed`, generation, context);
|
|
2191
|
+
if (!resident && state.graph.metadata !== await this.#bundler.metadataFingerprint())
|
|
2192
|
+
return this.#handBack("signal or dictionary metadata changed since the registry was built", generation, context);
|
|
2193
|
+
this.#state = this.#resident ? state : null;
|
|
2194
|
+
const healable = appGenerationOf2(state.manifest) < state.manifest.generation && this.#withoutFactory(state, (file) => fs5.existsSync(file)).length === 0;
|
|
2195
|
+
if (healable)
|
|
2196
|
+
state.manifest = await this.#bundler.writer.healApp(state.graph, state.manifest, await this.#code(state));
|
|
2197
|
+
return await this.#patch(context, state, changedFiles, generation, {
|
|
2198
|
+
announce,
|
|
2199
|
+
roots,
|
|
2200
|
+
onlyRoots,
|
|
2201
|
+
allowWholeAppBuilds
|
|
2202
|
+
});
|
|
2250
2203
|
}
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
const
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2204
|
+
async#patch(context, state, changedFiles, generation, { announce, roots = [], onlyRoots = false, allowWholeAppBuilds }) {
|
|
2205
|
+
const { paths, writer } = this.#bundler;
|
|
2206
|
+
const { graph } = state;
|
|
2207
|
+
const entries = await this.#bundler.writeEntries(context);
|
|
2208
|
+
const entryIds = this.#bundler.entryIds(entries.files);
|
|
2209
|
+
const present = CsrDevPatcher.#presence(changedFiles);
|
|
2210
|
+
const forgotten = this.#forgetDeletedModules(state, present);
|
|
2211
|
+
const changed = onlyRoots ? new Set : await this.#changedModules(graph, changedFiles, present);
|
|
2212
|
+
for (const id of this.#withoutFactory(state, present))
|
|
2213
|
+
changed.add(id);
|
|
2214
|
+
for (const file of entries.changed)
|
|
2215
|
+
changed.add(paths.idOf(file));
|
|
2216
|
+
const rootFiles = [...Object.values(entries.files), ...roots.filter((file) => fs5.existsSync(file))];
|
|
2217
|
+
for (const file of rootFiles) {
|
|
2218
|
+
const id = paths.idOf(file);
|
|
2219
|
+
if (!graph.modules[id] && !(onlyRoots && await this.#parked(graph, id)))
|
|
2220
|
+
changed.add(id);
|
|
2267
2221
|
}
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2222
|
+
const carried = onlyRoots ? [] : await this.#carriedRoots(graph, changed, present);
|
|
2223
|
+
const routesMoved = this.#bundler.reloadsOnEntryChange && (entries.changed.length > 0 || JSON.stringify(entryIds) !== JSON.stringify(graph.entries));
|
|
2224
|
+
if (changed.size === 0) {
|
|
2225
|
+
if (carried.length === 0)
|
|
2226
|
+
return { kind: "unchanged" };
|
|
2227
|
+
return await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
|
|
2272
2228
|
}
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
|
|
2229
|
+
const resolver = new CsrDevResolver({
|
|
2230
|
+
paths,
|
|
2231
|
+
context,
|
|
2232
|
+
entryFiles: rootFiles.map((file) => CsrDevPaths.realpath(file)),
|
|
2233
|
+
resolution: graph.resolution,
|
|
2234
|
+
runtimeResolved: graph.runtimeResolved
|
|
2235
|
+
});
|
|
2236
|
+
const known = new Set(Object.keys(graph.modules).map((id) => paths.fileOf(id)));
|
|
2237
|
+
const compile = async (ids) => await this.#bundler.compiler(context, resolver).compile(ids.map((id) => paths.fileOf(id)), known, { refuseNewVendor: !allowWholeAppBuilds, refusePrepass: !allowWholeAppBuilds });
|
|
2238
|
+
let result;
|
|
2239
|
+
let orphans;
|
|
2284
2240
|
try {
|
|
2285
|
-
|
|
2286
|
-
|
|
2241
|
+
({ result, orphans } = await this.#compileLeavingOrphans(compile, graph, changed));
|
|
2242
|
+
for (const module of result.modules)
|
|
2243
|
+
known.add(module.file);
|
|
2244
|
+
result = await this.#withTwinImporters(compile, graph, result);
|
|
2287
2245
|
} catch (error) {
|
|
2288
|
-
|
|
2246
|
+
graph.pending = [...new Set([...graph.pending, ...changed, ...carried])];
|
|
2247
|
+
if (!this.#resident)
|
|
2248
|
+
graph.failed = {
|
|
2249
|
+
roots: [...changed, ...carried],
|
|
2250
|
+
files: await CsrDevPatcher.#hashesOf(paths, CsrDevPatcher.#implicatedFiles(error))
|
|
2251
|
+
};
|
|
2252
|
+
await writer.writeJson("graph.json", graph);
|
|
2253
|
+
throw error;
|
|
2289
2254
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
];
|
|
2302
|
-
#app;
|
|
2303
|
-
#paths;
|
|
2304
|
-
#resolver;
|
|
2305
|
-
#context;
|
|
2306
|
-
#outDir;
|
|
2307
|
-
#routePrefix;
|
|
2308
|
-
#assets = new Set;
|
|
2309
|
-
constructor({ app, paths, resolver, context, outDir, routePrefix }) {
|
|
2310
|
-
this.#app = app;
|
|
2311
|
-
this.#paths = paths;
|
|
2312
|
-
this.#resolver = resolver;
|
|
2313
|
-
this.#context = context;
|
|
2314
|
-
this.#outDir = outDir;
|
|
2315
|
-
this.#routePrefix = routePrefix;
|
|
2316
|
-
}
|
|
2317
|
-
get assets() {
|
|
2318
|
-
return [...this.#assets];
|
|
2319
|
-
}
|
|
2320
|
-
async compile(startFiles, known, { refuseNewVendor = false, refusePrepass = false } = {}) {
|
|
2321
|
-
const compiled = new Map;
|
|
2322
|
-
const seen = new Set([...known, ...startFiles]);
|
|
2323
|
-
let frontier = [...new Set(startFiles)];
|
|
2324
|
-
while (frontier.length > 0) {
|
|
2325
|
-
const refusedVendors = refuseNewVendor ? frontier.filter((file) => CsrDevPaths.isVendorFile(file)) : [];
|
|
2326
|
-
if (refusedVendors.length > 0)
|
|
2327
|
-
return { modules: [...compiled.values()], refusedVendors, unresolved: [] };
|
|
2328
|
-
await this.#resolver.cover(frontier);
|
|
2329
|
-
const round = await this.#compileRound(frontier);
|
|
2330
|
-
if (round.misses.length > 0 && !this.#resolver.prepassDone && refusePrepass)
|
|
2331
|
-
return { modules: [...compiled.values()], refusedVendors: [], unresolved: round.misses };
|
|
2332
|
-
if (round.misses.length > 0 && !this.#resolver.prepassDone) {
|
|
2333
|
-
this.#app.verbose(`[csr-dev] ${round.misses.length} unresolved import(s); rerunning the resolution build`);
|
|
2334
|
-
await this.#resolver.prepass();
|
|
2335
|
-
continue;
|
|
2336
|
-
}
|
|
2337
|
-
const next = [];
|
|
2338
|
-
for (const module of round.modules) {
|
|
2339
|
-
compiled.set(module.file, module);
|
|
2340
|
-
for (const dep of module.deps) {
|
|
2341
|
-
if (CsrDevPaths.isStub(dep) || seen.has(dep))
|
|
2342
|
-
continue;
|
|
2343
|
-
seen.add(dep);
|
|
2344
|
-
next.push(dep);
|
|
2345
|
-
}
|
|
2255
|
+
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;
|
|
2256
|
+
if (refusal) {
|
|
2257
|
+
if (routesMoved)
|
|
2258
|
+
await writer.writeJson("graph.json", { ...graph, entries: {} });
|
|
2259
|
+
if (onlyRoots) {
|
|
2260
|
+
for (const id of changed)
|
|
2261
|
+
if (!graph.modules[id])
|
|
2262
|
+
this.#delegated.set(id, {
|
|
2263
|
+
hash: await CsrDevPaths.hashOf(paths.fileOf(id)),
|
|
2264
|
+
before: JSON.stringify(graph.failed ?? null)
|
|
2265
|
+
});
|
|
2346
2266
|
}
|
|
2347
|
-
|
|
2267
|
+
return this.#handBack(refusal, generation, context);
|
|
2268
|
+
}
|
|
2269
|
+
const compiled = result.modules;
|
|
2270
|
+
const vendorJoined = compiled.some((module) => module.vendor && !graph.modules[module.id]);
|
|
2271
|
+
const code = await this.#code(state);
|
|
2272
|
+
for (const id of [...orphans, ...this.#caseTwins(graph, compiled)]) {
|
|
2273
|
+
delete graph.modules[id];
|
|
2274
|
+
resolver.forget(paths.fileOf(id));
|
|
2275
|
+
code?.modules.delete(id);
|
|
2276
|
+
forgotten.push(id);
|
|
2277
|
+
}
|
|
2278
|
+
graph.pending = graph.pending.filter((id) => !orphans.includes(id));
|
|
2279
|
+
this.#bundler.merge(graph, resolver, compiled);
|
|
2280
|
+
if (!this.#resident)
|
|
2281
|
+
delete graph.failed;
|
|
2282
|
+
for (const module of compiled)
|
|
2283
|
+
this.#delegated.delete(module.id);
|
|
2284
|
+
graph.entries = entryIds;
|
|
2285
|
+
for (const module of compiled) {
|
|
2286
|
+
if (module.vendor || !code)
|
|
2287
|
+
continue;
|
|
2288
|
+
code.modules.set(module.id, module.factory);
|
|
2289
|
+
if (module.helpers)
|
|
2290
|
+
code.helpers.set(module.helpers.hash, module.helpers.definition);
|
|
2291
|
+
}
|
|
2292
|
+
const changedIds = compiled.filter((module) => !module.vendor).map((module) => module.id);
|
|
2293
|
+
const constantId = changedIds.find((id) => id.endsWith(".constant.ts"));
|
|
2294
|
+
const reason = vendorJoined ? "an npm module joined the graph" : routesMoved ? "the route table changed" : constantId ? `${path8.basename(constantId)} changed` : undefined;
|
|
2295
|
+
if (compiled.length === 0 && !reason) {
|
|
2296
|
+
await writer.writeJson("graph.json", graph);
|
|
2297
|
+
await writer.forgetModules(forgotten);
|
|
2298
|
+
if (carried.length === 0)
|
|
2299
|
+
return { kind: "unchanged" };
|
|
2300
|
+
return await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
|
|
2301
|
+
}
|
|
2302
|
+
const previous = state.manifest;
|
|
2303
|
+
const next = { ...previous, generation, entries: entryIds };
|
|
2304
|
+
const update = (patchUrl) => ({
|
|
2305
|
+
generation,
|
|
2306
|
+
reload: !!reason,
|
|
2307
|
+
reason,
|
|
2308
|
+
patchUrl,
|
|
2309
|
+
changedIds,
|
|
2310
|
+
moduleCount: Object.keys(graph.modules).length,
|
|
2311
|
+
epoch: previous.epoch
|
|
2312
|
+
});
|
|
2313
|
+
let outcome;
|
|
2314
|
+
if (reason) {
|
|
2315
|
+
await writer.forgetPatch(generation);
|
|
2316
|
+
await writer.writeModules(compiled);
|
|
2317
|
+
const vendorFile = vendorJoined ? await writer.writeVendor(graph) : previous.vendorFile;
|
|
2318
|
+
await writer.writeApp(graph, { generation, vendorFile, epoch: previous.epoch }, code);
|
|
2319
|
+
state.manifest = { ...next, vendorFile, appGeneration: generation };
|
|
2320
|
+
await writer.writeState(graph, state.manifest);
|
|
2321
|
+
await writer.prune(vendorFile, generation);
|
|
2322
|
+
const reloaded = update();
|
|
2323
|
+
announce?.(reloaded);
|
|
2324
|
+
outcome = { kind: "update", update: reloaded };
|
|
2325
|
+
} else {
|
|
2326
|
+
const patched = update(await writer.writePatch(generation, compiled.filter((module) => !module.vendor)));
|
|
2327
|
+
await writer.commitPatch(graph, { ...next, appGeneration: appGenerationOf2(previous) }, () => announce?.(patched), { modules: compiled, code });
|
|
2328
|
+
state.manifest = { ...next, appGeneration: generation };
|
|
2329
|
+
await writer.prune(previous.vendorFile, generation);
|
|
2330
|
+
outcome = { kind: "update", update: patched };
|
|
2348
2331
|
}
|
|
2349
|
-
|
|
2350
|
-
if (
|
|
2351
|
-
|
|
2352
|
-
|
|
2332
|
+
await writer.forgetModules(forgotten);
|
|
2333
|
+
if (carried.length === 0)
|
|
2334
|
+
return outcome;
|
|
2335
|
+
const retried = await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
|
|
2336
|
+
return retried.kind === "delegate" ? retried : outcome;
|
|
2353
2337
|
}
|
|
2354
|
-
async#
|
|
2355
|
-
const
|
|
2356
|
-
const
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
if (single.tangled.length > 0)
|
|
2366
|
-
throw new Error(`[csr-dev] ${this.#paths.idOf(file)} kept inlining another module`);
|
|
2367
|
-
modules.push(...single.modules);
|
|
2368
|
-
misses.push(...single.misses);
|
|
2338
|
+
async#retryCarried(context, state, carried, { announce, allowWholeAppBuilds }) {
|
|
2339
|
+
const { paths, writer } = this.#bundler;
|
|
2340
|
+
const retried = await this.#patch(context, state, [], state.manifest.generation + 1, {
|
|
2341
|
+
announce,
|
|
2342
|
+
roots: carried.map((id) => paths.fileOf(id)),
|
|
2343
|
+
onlyRoots: true,
|
|
2344
|
+
allowWholeAppBuilds
|
|
2345
|
+
});
|
|
2346
|
+
if (retried.kind === "delegate") {
|
|
2347
|
+
state.graph.pending = [...new Set([...state.graph.pending, ...carried])];
|
|
2348
|
+
await writer.writeJson("graph.json", state.graph);
|
|
2369
2349
|
}
|
|
2370
|
-
return
|
|
2350
|
+
return retried;
|
|
2371
2351
|
}
|
|
2372
|
-
async#
|
|
2373
|
-
const
|
|
2374
|
-
const hashes = new Map(await Promise.all(files.map(async (file) => [CsrDevPaths.realpath(file), vendor ? "" : await CsrDevPaths.hashOf(file)])));
|
|
2375
|
-
const misses = [];
|
|
2376
|
-
const macroOutputs = vendor ? new Map : await this.#evaluateMacros(files);
|
|
2377
|
-
let result;
|
|
2352
|
+
async#compileLeavingOrphans(compile, graph, changed) {
|
|
2353
|
+
const { paths } = this.#bundler;
|
|
2378
2354
|
try {
|
|
2379
|
-
result
|
|
2380
|
-
entrypoints: files,
|
|
2381
|
-
root: this.#paths.root,
|
|
2382
|
-
target: "browser",
|
|
2383
|
-
format: "cjs",
|
|
2384
|
-
reactFastRefresh: !vendor,
|
|
2385
|
-
sourcemap: vendor ? "none" : "external",
|
|
2386
|
-
naming: { entry: "[dir]/[name].[ext]", asset: "assets/[name]-[hash].[ext]" },
|
|
2387
|
-
publicPath: this.#routePrefix,
|
|
2388
|
-
metafile: true,
|
|
2389
|
-
define: { ...this.#context.define, "import.meta": "__akanMeta" },
|
|
2390
|
-
optimizeImports: this.#context.optimizeImports,
|
|
2391
|
-
plugins: [this.#registryPlugin(vendor, misses, macroOutputs)]
|
|
2392
|
-
});
|
|
2355
|
+
return { result: await compile([...changed]), orphans: [] };
|
|
2393
2356
|
} catch (error) {
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
for (const artifact of result.outputs) {
|
|
2403
|
-
if (artifact.kind === "asset") {
|
|
2404
|
-
const assetPath = path9.join(this.#outDir, artifact.path);
|
|
2405
|
-
await Bun.write(assetPath, artifact);
|
|
2406
|
-
this.#assets.add(assetPath);
|
|
2407
|
-
continue;
|
|
2408
|
-
}
|
|
2409
|
-
if (artifact.kind !== "entry-point")
|
|
2410
|
-
continue;
|
|
2411
|
-
const meta = outputs[artifact.path];
|
|
2412
|
-
if (!meta?.entryPoint)
|
|
2413
|
-
throw new Error(`[csr-dev] no entry point recorded for ${artifact.path}`);
|
|
2414
|
-
const file = CsrDevPaths.realpath(path9.resolve(meta.entryPoint));
|
|
2415
|
-
const inputs = Object.keys(meta.inputs).map((input) => CsrDevPaths.realpath(path9.resolve(input)));
|
|
2416
|
-
if (inputs.some((input) => input !== file && this.#resolver.isRegistryModule(input))) {
|
|
2417
|
-
tangled.push(file);
|
|
2418
|
-
continue;
|
|
2419
|
-
}
|
|
2420
|
-
const code = await artifact.text();
|
|
2421
|
-
const id = this.#paths.idOf(file);
|
|
2422
|
-
const built = CsrDevModuleCompiler.#factory(id, code);
|
|
2423
|
-
const problem = CsrDevFactoryCheck.problemOf(built.factory, built.helpers?.definition);
|
|
2424
|
-
if (problem && !vendor)
|
|
2425
|
-
throw new Error(`[csr-dev] ${id}: its factory does not parse as a classic script: ${problem}`);
|
|
2426
|
-
if (problem) {
|
|
2427
|
-
modules.push(this.#leftOut(file, problem));
|
|
2428
|
-
continue;
|
|
2429
|
-
}
|
|
2430
|
-
const sourceMap = sourceMaps.get(`${artifact.path}.map`);
|
|
2431
|
-
modules.push({
|
|
2432
|
-
id,
|
|
2433
|
-
file,
|
|
2434
|
-
vendor,
|
|
2435
|
-
...built,
|
|
2436
|
-
sourceMap: sourceMap ? this.#rebaseSourceMap(await sourceMap.text()) : undefined,
|
|
2437
|
-
deps: this.#emittedDeps(code),
|
|
2438
|
-
mtimeMs: mtimes.get(file) ?? CsrDevPaths.mtimeOf(file),
|
|
2439
|
-
hash: hashes.get(file) ?? ""
|
|
2357
|
+
const failing = new Set(CsrDevPatcher.#failingFiles(error).map((file) => paths.idOf(file)));
|
|
2358
|
+
const broken = [...changed].filter((id) => failing.has(id) && graph.modules[id]);
|
|
2359
|
+
if (broken.length === 0)
|
|
2360
|
+
throw error;
|
|
2361
|
+
const rest = [...changed].filter((id) => !broken.includes(id));
|
|
2362
|
+
const empty = { modules: [], refusedVendors: [], unresolved: [] };
|
|
2363
|
+
const result = rest.length === 0 ? empty : await compile(rest).catch(() => {
|
|
2364
|
+
throw error;
|
|
2440
2365
|
});
|
|
2366
|
+
const modules = { ...graph.modules };
|
|
2367
|
+
for (const module of result.modules)
|
|
2368
|
+
modules[module.id] = {
|
|
2369
|
+
vendor: module.vendor,
|
|
2370
|
+
mtimeMs: module.mtimeMs,
|
|
2371
|
+
hash: module.hash,
|
|
2372
|
+
deps: module.deps.map((dep) => CsrDevPaths.isStub(dep) ? dep : paths.idOf(dep))
|
|
2373
|
+
};
|
|
2374
|
+
const reached = await this.#bundler.reachable({ ...graph, modules });
|
|
2375
|
+
if (broken.some((id) => reached.has(id)))
|
|
2376
|
+
throw error;
|
|
2377
|
+
const orphans = Object.keys(modules).filter((id) => !reached.has(id) && !modules[id]?.vendor);
|
|
2378
|
+
return { result, orphans };
|
|
2441
2379
|
}
|
|
2442
|
-
return { modules, misses, tangled };
|
|
2443
|
-
}
|
|
2444
|
-
async#withoutFailed(files, error) {
|
|
2445
|
-
const reasons = new Map;
|
|
2446
|
-
for (const message of error.errors) {
|
|
2447
|
-
const file = message.position?.file;
|
|
2448
|
-
if (typeof file !== "string" || !path9.isAbsolute(file))
|
|
2449
|
-
continue;
|
|
2450
|
-
const real = CsrDevPaths.realpath(file);
|
|
2451
|
-
const at = message.position?.line ? `:${message.position.line}` : "";
|
|
2452
|
-
reasons.set(real, [...reasons.get(real) ?? [], `${String(message.message)} (${this.#paths.idOf(real)}${at})`]);
|
|
2453
|
-
}
|
|
2454
|
-
const failed = new Set(files.filter((file) => reasons.has(CsrDevPaths.realpath(file))));
|
|
2455
|
-
if (failed.size === 0)
|
|
2456
|
-
throw error;
|
|
2457
|
-
const rest = files.filter((file) => !failed.has(file));
|
|
2458
|
-
const round = rest.length > 0 ? await this.#bunBuild(rest, true) : { modules: [], misses: [], tangled: [] };
|
|
2459
|
-
for (const file of failed) {
|
|
2460
|
-
const text = (reasons.get(CsrDevPaths.realpath(file)) ?? []).join("; ");
|
|
2461
|
-
const awaits = text.includes('"await" can only be used');
|
|
2462
|
-
round.modules.push(this.#leftOut(file, awaits ? `a top-level await, which a CommonJS factory cannot wait on: ${text}` : text));
|
|
2463
|
-
}
|
|
2464
|
-
return round;
|
|
2465
|
-
}
|
|
2466
|
-
#leftOut(file, problem) {
|
|
2467
|
-
const real = CsrDevPaths.realpath(file);
|
|
2468
|
-
const id = this.#paths.idOf(real);
|
|
2469
|
-
this.#app.logger.error(`[csr-dev] ${id} is left out of the dev registry: ${problem}`);
|
|
2470
|
-
return {
|
|
2471
|
-
id,
|
|
2472
|
-
file: real,
|
|
2473
|
-
vendor: true,
|
|
2474
|
-
factory: CsrDevFactoryCheck.thrower(id, problem),
|
|
2475
|
-
helpers: null,
|
|
2476
|
-
deps: [],
|
|
2477
|
-
mtimeMs: CsrDevPaths.mtimeOf(real),
|
|
2478
|
-
hash: ""
|
|
2479
|
-
};
|
|
2480
2380
|
}
|
|
2481
|
-
|
|
2482
|
-
const
|
|
2483
|
-
const
|
|
2484
|
-
const
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
target: "browser",
|
|
2488
|
-
format: "esm",
|
|
2489
|
-
plugins: [
|
|
2490
|
-
{
|
|
2491
|
-
name: "akan-csr-macro-pass",
|
|
2492
|
-
setup: (build) => void build.onResolve({ filter: /.*/ }, (args) => args.importer ? { path: args.path, external: true } : undefined)
|
|
2493
|
-
}
|
|
2494
|
-
]
|
|
2495
|
-
});
|
|
2496
|
-
const [output] = result.outputs;
|
|
2497
|
-
if (!output)
|
|
2498
|
-
throw new Error(`[csr-dev] ${this.#paths.idOf(file)}: its macro pass wrote no output`);
|
|
2499
|
-
const text = await output.text();
|
|
2500
|
-
return [
|
|
2501
|
-
[file, text],
|
|
2502
|
-
[CsrDevPaths.realpath(file), text]
|
|
2503
|
-
];
|
|
2504
|
-
}));
|
|
2505
|
-
return new Map(outputs.flat());
|
|
2381
|
+
#caseTwins(graph, compiled) {
|
|
2382
|
+
const { paths } = this.#bundler;
|
|
2383
|
+
const idsByFold = new Map;
|
|
2384
|
+
for (const id of Object.keys(graph.modules))
|
|
2385
|
+
idsByFold.set(id.toLowerCase(), [...idsByFold.get(id.toLowerCase()) ?? [], id]);
|
|
2386
|
+
return compiled.flatMap((module) => (idsByFold.get(module.id.toLowerCase()) ?? []).filter((id) => id !== module.id && !graph.modules[id]?.vendor && !CsrDevPaths.isNamed(paths.fileOf(id))));
|
|
2506
2387
|
}
|
|
2507
|
-
#
|
|
2388
|
+
async#withTwinImporters(compile, graph, result) {
|
|
2389
|
+
const twins = new Set(this.#caseTwins(graph, result.modules));
|
|
2390
|
+
if (twins.size === 0)
|
|
2391
|
+
return result;
|
|
2392
|
+
const compiledIds = new Set(result.modules.map((module) => module.id));
|
|
2393
|
+
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);
|
|
2394
|
+
if (importers.length === 0)
|
|
2395
|
+
return result;
|
|
2396
|
+
const more = await compile(importers);
|
|
2508
2397
|
return {
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
if (macroOutputs.size > 0) {
|
|
2513
|
-
const exact = [...macroOutputs.keys()].map((file) => file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
2514
|
-
build.onLoad({ filter: new RegExp(`^(?:${exact.join("|")})$`) }, (args) => {
|
|
2515
|
-
const contents = macroOutputs.get(args.path);
|
|
2516
|
-
return contents === undefined ? undefined : { contents, loader: "js" };
|
|
2517
|
-
});
|
|
2518
|
-
}
|
|
2519
|
-
if (!vendor)
|
|
2520
|
-
build.onLoad({ filter: /\.tsx$/ }, async (args) => {
|
|
2521
|
-
if (CsrDevPaths.isVendorFile(args.path))
|
|
2522
|
-
return;
|
|
2523
|
-
const normalized = RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh(await Bun.file(args.path).text(), { path: args.path });
|
|
2524
|
-
return normalized ? { contents: normalized, loader: "tsx" } : undefined;
|
|
2525
|
-
});
|
|
2526
|
-
build.onResolve({ filter: /.*/ }, (args) => {
|
|
2527
|
-
if (args.kind === "entry-point-build" || !args.importer)
|
|
2528
|
-
return;
|
|
2529
|
-
if (this.#context.externals?.includes(args.path))
|
|
2530
|
-
return { path: `${CsrDevPaths.vendorPrefix}${args.path}`, external: true };
|
|
2531
|
-
const importer = CsrDevPaths.realpath(args.importer);
|
|
2532
|
-
const target = this.#resolver.resolve(importer, args.path, args.kind === "require-call" || args.kind === "require-resolve" ? "require" : "import");
|
|
2533
|
-
if (target === CsrDevResolver.inline)
|
|
2534
|
-
return;
|
|
2535
|
-
if (target === null) {
|
|
2536
|
-
misses.push(`${args.path} from ${this.#paths.idOf(importer)}`);
|
|
2537
|
-
return { path: `akan-miss:${args.path}`, external: true };
|
|
2538
|
-
}
|
|
2539
|
-
const external = CsrDevPaths.isStub(target) ? target : `${CsrDevPaths.modulePrefix}${this.#paths.idOf(target)}`;
|
|
2540
|
-
return { path: external, external: true };
|
|
2541
|
-
});
|
|
2542
|
-
}
|
|
2398
|
+
modules: [...result.modules, ...more.modules],
|
|
2399
|
+
refusedVendors: [...result.refusedVendors, ...more.refusedVendors],
|
|
2400
|
+
unresolved: [...result.unresolved, ...more.unresolved]
|
|
2543
2401
|
};
|
|
2544
2402
|
}
|
|
2545
|
-
#
|
|
2546
|
-
const
|
|
2547
|
-
const
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2403
|
+
static #implicatedFiles(error) {
|
|
2404
|
+
const failing = CsrDevPatcher.#failingFiles(error);
|
|
2405
|
+
const messages = error instanceof AggregateError ? error.errors : [];
|
|
2406
|
+
const named = messages.flatMap((message) => {
|
|
2407
|
+
const text = String(message.message ?? "");
|
|
2408
|
+
const bases = [process.cwd(), ...failing.map((file) => path8.dirname(file))];
|
|
2409
|
+
return [...text.matchAll(/"([^"\n]+)"/g)].flatMap(([, quoted = ""]) => {
|
|
2410
|
+
const found = bases.map((base) => path8.resolve(base, quoted)).find((file) => CsrDevPaths.isScript(file) && fs5.existsSync(file));
|
|
2411
|
+
return found ? [CsrDevPaths.realpath(found)] : [];
|
|
2412
|
+
});
|
|
2413
|
+
});
|
|
2414
|
+
return [...new Set([...failing, ...named])];
|
|
2551
2415
|
}
|
|
2552
|
-
#
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2555
|
-
for (const [, reference] of code.matchAll(references)) {
|
|
2556
|
-
if (!reference)
|
|
2557
|
-
continue;
|
|
2558
|
-
deps.add(CsrDevPaths.isStub(reference) ? reference : this.#paths.fileOf(reference.slice(CsrDevPaths.modulePrefix.length)));
|
|
2559
|
-
}
|
|
2560
|
-
return [...deps];
|
|
2416
|
+
static #failingFiles(error) {
|
|
2417
|
+
const messages = error instanceof AggregateError ? error.errors : [];
|
|
2418
|
+
return messages.map((message) => message.position?.file).filter((file) => typeof file === "string" && path8.isAbsolute(file)).map((file) => CsrDevPaths.realpath(file));
|
|
2561
2419
|
}
|
|
2562
|
-
|
|
2563
|
-
const
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2420
|
+
async#carriedRoots(graph, changed, present) {
|
|
2421
|
+
const { paths } = this.#bundler;
|
|
2422
|
+
const outside = graph.pending.filter((id) => !graph.modules[id] && !changed.has(id) && present(paths.fileOf(id)));
|
|
2423
|
+
if (outside.length === 0)
|
|
2424
|
+
return [];
|
|
2425
|
+
const wanted = await this.#bundler.wantedRoots(outside.map((id) => paths.fileOf(id)), graph);
|
|
2426
|
+
const kept = outside.filter((id) => wanted.has(paths.fileOf(id)));
|
|
2427
|
+
graph.pending = graph.pending.filter((id) => graph.modules[id] || kept.includes(id));
|
|
2428
|
+
const carried = [];
|
|
2429
|
+
for (const id of kept)
|
|
2430
|
+
if (!await this.#parked(graph, id))
|
|
2431
|
+
carried.push(id);
|
|
2432
|
+
return carried;
|
|
2433
|
+
}
|
|
2434
|
+
async#parked(graph, id) {
|
|
2435
|
+
const { paths } = this.#bundler;
|
|
2436
|
+
const handedOver = this.#delegated.get(id);
|
|
2437
|
+
if (!handedOver || handedOver.hash !== await CsrDevPaths.hashOf(paths.fileOf(id)))
|
|
2438
|
+
return false;
|
|
2439
|
+
if (!handedOver.failed) {
|
|
2440
|
+
if (JSON.stringify(graph.failed ?? null) === handedOver.before)
|
|
2441
|
+
return true;
|
|
2442
|
+
if (graph.failed?.roots.includes(id))
|
|
2443
|
+
handedOver.failed = graph.failed.files;
|
|
2573
2444
|
}
|
|
2574
|
-
if (
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
return
|
|
2580
|
-
factory: `function (require, module, exports, $RefreshReg$, $RefreshSig$, __akanImport) {${meta}
|
|
2581
|
-
${shared?.code ?? patched}
|
|
2582
|
-
}`,
|
|
2583
|
-
helpers: shared?.helpers ?? null
|
|
2584
|
-
};
|
|
2445
|
+
if (!handedOver.failed)
|
|
2446
|
+
return false;
|
|
2447
|
+
for (const [failedId, hash] of Object.entries(handedOver.failed))
|
|
2448
|
+
if (await CsrDevPaths.hashOf(paths.fileOf(failedId)) !== hash)
|
|
2449
|
+
return false;
|
|
2450
|
+
return true;
|
|
2585
2451
|
}
|
|
2586
|
-
static #
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
const
|
|
2591
|
-
const
|
|
2592
|
-
|
|
2452
|
+
static async#hashesOf(paths, files) {
|
|
2453
|
+
return Object.fromEntries(await Promise.all(files.map(async (file) => [paths.idOf(file), await CsrDevPaths.hashOf(file)])));
|
|
2454
|
+
}
|
|
2455
|
+
#withoutFactory({ graph, code }, present) {
|
|
2456
|
+
const { paths, writer } = this.#bundler;
|
|
2457
|
+
const hasFactory = (id) => code ? code.modules.has(id) : writer.hasModule(id);
|
|
2458
|
+
return Object.entries(graph.modules).filter(([id, module]) => !module.vendor && !hasFactory(id) && present(paths.fileOf(id))).map(([id]) => id);
|
|
2459
|
+
}
|
|
2460
|
+
static #presence(changedFiles) {
|
|
2461
|
+
const caseMoved = changedFiles.some((file) => fs5.existsSync(file) && !CsrDevPaths.isNamed(file));
|
|
2462
|
+
return caseMoved ? (file) => CsrDevPaths.isNamed(file) : (file) => fs5.existsSync(file);
|
|
2463
|
+
}
|
|
2464
|
+
#handBack(reason, generation, context, { first = false } = {}) {
|
|
2465
|
+
this.#state = null;
|
|
2466
|
+
return { kind: "delegate", reason, generation, context, ...first ? { first } : {} };
|
|
2467
|
+
}
|
|
2468
|
+
async#load() {
|
|
2469
|
+
const manifest = await this.#bundler.writer.readJson(CSR_DEV_MANIFEST_FILE2);
|
|
2470
|
+
const graph = await this.#bundler.writer.readJson("graph.json");
|
|
2471
|
+
if (!manifest || !graph || graph.version !== 1 || await this.#bundler.writer.isBuilding())
|
|
2593
2472
|
return null;
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2473
|
+
return { graph, manifest, code: null };
|
|
2474
|
+
}
|
|
2475
|
+
async#code(state) {
|
|
2476
|
+
if (!this.#resident)
|
|
2477
|
+
return;
|
|
2478
|
+
state.code ??= await this.#bundler.writer.readCode(state.graph);
|
|
2479
|
+
return state.code;
|
|
2480
|
+
}
|
|
2481
|
+
async#changedModules(graph, changedFiles, present) {
|
|
2482
|
+
const { paths } = this.#bundler;
|
|
2483
|
+
const changed = new Set(graph.pending.filter((id) => graph.modules[id]));
|
|
2484
|
+
for (const file of changedFiles) {
|
|
2485
|
+
const id = paths.idOf(file);
|
|
2486
|
+
if (graph.modules[id])
|
|
2487
|
+
changed.add(id);
|
|
2488
|
+
}
|
|
2489
|
+
for (const id of this.#shadowedImporters(graph, changedFiles))
|
|
2490
|
+
changed.add(id);
|
|
2491
|
+
for (const [id, module] of Object.entries(graph.modules)) {
|
|
2492
|
+
if (module.vendor || changed.has(id))
|
|
2493
|
+
continue;
|
|
2494
|
+
const file = paths.fileOf(id);
|
|
2495
|
+
if (CsrDevPaths.mtimeOf(file) === module.mtimeMs)
|
|
2496
|
+
continue;
|
|
2497
|
+
if (await CsrDevPaths.hashOf(file) !== module.hash)
|
|
2498
|
+
changed.add(id);
|
|
2499
|
+
}
|
|
2500
|
+
for (const [id, module] of Object.entries(graph.modules)) {
|
|
2501
|
+
if (changed.has(id))
|
|
2502
|
+
continue;
|
|
2503
|
+
for (const dep of module.deps) {
|
|
2504
|
+
if (graph.modules[dep] || CsrDevPaths.isStub(dep))
|
|
2505
|
+
continue;
|
|
2506
|
+
const file = paths.fileOf(dep);
|
|
2507
|
+
if (present(file) && CsrDevPaths.isNamed(file))
|
|
2508
|
+
changed.add(dep);
|
|
2509
|
+
else if (!module.vendor)
|
|
2510
|
+
changed.add(id);
|
|
2605
2511
|
}
|
|
2606
|
-
}
|
|
2512
|
+
}
|
|
2513
|
+
return changed;
|
|
2514
|
+
}
|
|
2515
|
+
#shadowedImporters(graph, changedFiles) {
|
|
2516
|
+
const { paths } = this.#bundler;
|
|
2517
|
+
const bases = new Set(changedFiles.map((file) => CsrDevPaths.realpath(file)).filter((file) => !graph.modules[paths.idOf(file)] && CsrDevPaths.isScript(file) && fs5.existsSync(file)).flatMap((file) => {
|
|
2518
|
+
const stem = file.slice(0, file.length - path8.extname(file).length);
|
|
2519
|
+
return path8.basename(stem) === "index" ? [stem, path8.dirname(stem)] : [stem];
|
|
2520
|
+
}));
|
|
2521
|
+
if (bases.size === 0)
|
|
2522
|
+
return [];
|
|
2523
|
+
return Object.entries(graph.resolution).filter(([importer, bySpecifier]) => {
|
|
2524
|
+
if (!graph.modules[importer] || graph.modules[importer]?.vendor)
|
|
2525
|
+
return false;
|
|
2526
|
+
const dir = path8.dirname(paths.fileOf(importer));
|
|
2527
|
+
return Object.keys(bySpecifier).some((specifier) => specifier.startsWith(".") && bases.has(path8.resolve(dir, specifier)));
|
|
2528
|
+
}).map(([importer]) => importer);
|
|
2529
|
+
}
|
|
2530
|
+
#forgetDeletedModules({ graph, code }, present) {
|
|
2531
|
+
const { paths } = this.#bundler;
|
|
2532
|
+
const deleted = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && !present(paths.fileOf(id)));
|
|
2533
|
+
for (const id of deleted) {
|
|
2534
|
+
delete graph.modules[id];
|
|
2535
|
+
code?.modules.delete(id);
|
|
2536
|
+
}
|
|
2537
|
+
return deleted;
|
|
2607
2538
|
}
|
|
2608
2539
|
}
|
|
2609
2540
|
|
|
2610
2541
|
// pkgs/@akanjs/devkit/frontendBuild/registryMetadataFingerprint.ts
|
|
2611
2542
|
import fs6 from "fs";
|
|
2612
|
-
import
|
|
2543
|
+
import path9 from "path";
|
|
2613
2544
|
|
|
2614
2545
|
class RegistryMetadataFingerprint {
|
|
2615
2546
|
static #sources = new Bun.Glob("**/*.{signal,dictionary}.ts");
|
|
@@ -2617,12 +2548,12 @@ class RegistryMetadataFingerprint {
|
|
|
2617
2548
|
static async of(roots, isMetadataFile) {
|
|
2618
2549
|
const stamps = [];
|
|
2619
2550
|
for (const root of new Set(roots)) {
|
|
2620
|
-
const libDir =
|
|
2551
|
+
const libDir = path9.join(root, "lib");
|
|
2621
2552
|
if (!fs6.existsSync(libDir))
|
|
2622
2553
|
continue;
|
|
2623
|
-
const files = RegistryMetadataFingerprint.#libFiles.map((name) =>
|
|
2554
|
+
const files = RegistryMetadataFingerprint.#libFiles.map((name) => path9.join(libDir, name));
|
|
2624
2555
|
for await (const relative of RegistryMetadataFingerprint.#sources.scan({ cwd: libDir, onlyFiles: true }))
|
|
2625
|
-
files.push(
|
|
2556
|
+
files.push(path9.join(libDir, relative));
|
|
2626
2557
|
for (const file of files) {
|
|
2627
2558
|
if (!isMetadataFile(file))
|
|
2628
2559
|
continue;
|
|
@@ -2648,8 +2579,8 @@ class DevRegistryBundler {
|
|
|
2648
2579
|
this.app = app;
|
|
2649
2580
|
this.routePrefix = routePrefix;
|
|
2650
2581
|
this.paths = new CsrDevPaths(app.workspace.workspaceRoot);
|
|
2651
|
-
this.outDir =
|
|
2652
|
-
this.entryDir =
|
|
2582
|
+
this.outDir = path10.join(app.cwdPath, ".akan/artifact", dirName);
|
|
2583
|
+
this.entryDir = path10.join(app.cwdPath, ".akan/generated", dirName);
|
|
2653
2584
|
this.writer = new CsrDevArtifactWriter(this.outDir, { routePrefix, library });
|
|
2654
2585
|
}
|
|
2655
2586
|
async update(changedFiles = [], options = {}) {
|
|
@@ -2687,10 +2618,10 @@ class DevRegistryBundler {
|
|
|
2687
2618
|
return isAkanRuntimeMetadataFile(file);
|
|
2688
2619
|
}
|
|
2689
2620
|
async metadataFingerprint() {
|
|
2690
|
-
const libsContainer =
|
|
2621
|
+
const libsContainer = path10.resolve(this.app.workspace.workspaceRoot, "libs");
|
|
2691
2622
|
const roots = await new WatchRootResolver(this.app).resolve();
|
|
2692
|
-
const libRoots = roots.includes(libsContainer) ? fs7.readdirSync(libsContainer).map((name) =>
|
|
2693
|
-
return await RegistryMetadataFingerprint.of([
|
|
2623
|
+
const libRoots = roots.includes(libsContainer) ? fs7.readdirSync(libsContainer).map((name) => path10.join(libsContainer, name)) : roots.filter((root) => path10.dirname(root) === libsContainer);
|
|
2624
|
+
return await RegistryMetadataFingerprint.of([path10.resolve(this.app.cwdPath), ...libRoots], (file) => this.isMetadataFile(file));
|
|
2694
2625
|
}
|
|
2695
2626
|
async fullBuild(context, generation, reason) {
|
|
2696
2627
|
const metadata = await this.metadataFingerprint();
|
|
@@ -2783,7 +2714,7 @@ class CsrDevBundler extends DevRegistryBundler {
|
|
|
2783
2714
|
const files = {};
|
|
2784
2715
|
const changed = [];
|
|
2785
2716
|
for (const basePath of context.htmlBasePaths) {
|
|
2786
|
-
const file =
|
|
2717
|
+
const file = path11.join(this.entryDir, CsrEntryFiles.entryFilename(basePath));
|
|
2787
2718
|
const entryPages = CsrEntryFiles.pageEntriesForBasePath(context.pageEntries, basePath, context.basePaths);
|
|
2788
2719
|
const generator = new PagesEntrySourceGenerator(entryPages, {
|
|
2789
2720
|
isAsyncDefault: (moduleAbsPath) => this.#isAsyncDefault(moduleAbsPath)
|
|
@@ -2822,7 +2753,7 @@ ${hot}`;
|
|
|
2822
2753
|
}
|
|
2823
2754
|
#resolveRefreshRuntime() {
|
|
2824
2755
|
const akanWebkit = CsrDevPaths.tryResolve("akanjs/webkit", this.app.cwdPath);
|
|
2825
|
-
const bases = [this.app.cwdPath, this.paths.root, ...akanWebkit ? [
|
|
2756
|
+
const bases = [this.app.cwdPath, this.paths.root, ...akanWebkit ? [path11.dirname(path11.dirname(akanWebkit))] : []];
|
|
2826
2757
|
for (const base of bases) {
|
|
2827
2758
|
const resolved = CsrDevPaths.tryResolve("react-refresh/runtime", base);
|
|
2828
2759
|
if (resolved)
|
|
@@ -2833,7 +2764,7 @@ ${hot}`;
|
|
|
2833
2764
|
}
|
|
2834
2765
|
|
|
2835
2766
|
// pkgs/@akanjs/devkit/frontendBuild/devChangePlanner.ts
|
|
2836
|
-
import
|
|
2767
|
+
import path12 from "path";
|
|
2837
2768
|
var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
2838
2769
|
var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
2839
2770
|
var CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
|
|
@@ -2844,11 +2775,11 @@ var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "
|
|
|
2844
2775
|
class DevChangePlanner {
|
|
2845
2776
|
#workspaceRoot;
|
|
2846
2777
|
constructor({ workspaceRoot }) {
|
|
2847
|
-
this.#workspaceRoot =
|
|
2778
|
+
this.#workspaceRoot = path12.resolve(workspaceRoot);
|
|
2848
2779
|
}
|
|
2849
2780
|
plan({ generation, files, kinds, generatedFiles = [] }) {
|
|
2850
2781
|
const fileList = uniqueResolved([...files, ...generatedFiles]);
|
|
2851
|
-
const generatedSet = new Set(generatedFiles.map((file) =>
|
|
2782
|
+
const generatedSet = new Set(generatedFiles.map((file) => path12.resolve(file)));
|
|
2852
2783
|
const kindSet = new Set(kinds);
|
|
2853
2784
|
const roles = new Set;
|
|
2854
2785
|
const actions = new Set;
|
|
@@ -2865,13 +2796,13 @@ class DevChangePlanner {
|
|
|
2865
2796
|
}
|
|
2866
2797
|
for (const file of fileList) {
|
|
2867
2798
|
const reasons = new Set;
|
|
2868
|
-
const fileRoles = this.#rolesForFile(file, { isGenerated: generatedSet.has(
|
|
2799
|
+
const fileRoles = this.#rolesForFile(file, { isGenerated: generatedSet.has(path12.resolve(file)), reasons });
|
|
2869
2800
|
for (const role of fileRoles)
|
|
2870
2801
|
roles.add(role);
|
|
2871
2802
|
if (reasons.has("runtime-metadata"))
|
|
2872
2803
|
actions.add("restart-builder");
|
|
2873
2804
|
if (reasons.size > 0)
|
|
2874
|
-
reasonByFile[
|
|
2805
|
+
reasonByFile[path12.resolve(file)] = [...reasons].sort();
|
|
2875
2806
|
}
|
|
2876
2807
|
if (roles.has("barrel"))
|
|
2877
2808
|
actions.add("sync-generated");
|
|
@@ -2892,12 +2823,12 @@ class DevChangePlanner {
|
|
|
2892
2823
|
}
|
|
2893
2824
|
#rolesForFile(file, { isGenerated, reasons }) {
|
|
2894
2825
|
const roles = new Set;
|
|
2895
|
-
const abs =
|
|
2896
|
-
const base =
|
|
2897
|
-
const ext =
|
|
2826
|
+
const abs = path12.resolve(file);
|
|
2827
|
+
const base = path12.basename(abs);
|
|
2828
|
+
const ext = path12.extname(abs).toLowerCase();
|
|
2898
2829
|
const isSource = SOURCE_EXTS.has(ext);
|
|
2899
|
-
const rel =
|
|
2900
|
-
const parts = rel.split(
|
|
2830
|
+
const rel = path12.relative(this.#workspaceRoot, abs);
|
|
2831
|
+
const parts = rel.split(path12.sep).filter(Boolean);
|
|
2901
2832
|
if (CONFIG_BASENAMES.has(base)) {
|
|
2902
2833
|
roles.add("config");
|
|
2903
2834
|
reasons.add("config-file");
|
|
@@ -2968,17 +2899,17 @@ class DevChangePlanner {
|
|
|
2968
2899
|
return true;
|
|
2969
2900
|
}
|
|
2970
2901
|
#isWorkspaceSource(rel) {
|
|
2971
|
-
if (!rel || rel.startsWith("..") ||
|
|
2902
|
+
if (!rel || rel.startsWith("..") || path12.isAbsolute(rel))
|
|
2972
2903
|
return false;
|
|
2973
|
-
const [scope] = rel.split(
|
|
2904
|
+
const [scope] = rel.split(path12.sep);
|
|
2974
2905
|
return scope === "apps" || scope === "libs" || scope === "pkgs";
|
|
2975
2906
|
}
|
|
2976
2907
|
}
|
|
2977
|
-
var uniqueResolved = (files) => [...new Set(files.map((file) =>
|
|
2908
|
+
var uniqueResolved = (files) => [...new Set(files.map((file) => path12.resolve(file)))].sort();
|
|
2978
2909
|
|
|
2979
2910
|
// pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
|
|
2980
2911
|
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile2, rm as rm2, stat as stat2 } from "fs/promises";
|
|
2981
|
-
import
|
|
2912
|
+
import path13 from "path";
|
|
2982
2913
|
import { capitalize } from "akanjs/common";
|
|
2983
2914
|
var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
2984
2915
|
var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
|
|
@@ -2992,7 +2923,7 @@ var SCALAR_UI_TYPES = ["Template", "Unit"];
|
|
|
2992
2923
|
class DevGeneratedIndexSync {
|
|
2993
2924
|
#workspaceRoot;
|
|
2994
2925
|
constructor({ workspaceRoot }) {
|
|
2995
|
-
this.#workspaceRoot =
|
|
2926
|
+
this.#workspaceRoot = path13.resolve(workspaceRoot);
|
|
2996
2927
|
}
|
|
2997
2928
|
async syncForBatch(files) {
|
|
2998
2929
|
const indexPaths = new Set;
|
|
@@ -3021,11 +2952,11 @@ class DevGeneratedIndexSync {
|
|
|
3021
2952
|
return { changedFiles, errors };
|
|
3022
2953
|
}
|
|
3023
2954
|
#facetIndexFor(file) {
|
|
3024
|
-
const abs =
|
|
3025
|
-
const rel =
|
|
3026
|
-
if (rel.startsWith("..") ||
|
|
2955
|
+
const abs = path13.resolve(file);
|
|
2956
|
+
const rel = path13.relative(this.#workspaceRoot, abs);
|
|
2957
|
+
if (rel.startsWith("..") || path13.isAbsolute(rel))
|
|
3027
2958
|
return null;
|
|
3028
|
-
const parts = rel.split(
|
|
2959
|
+
const parts = rel.split(path13.sep).filter(Boolean);
|
|
3029
2960
|
if (parts.length < 4)
|
|
3030
2961
|
return null;
|
|
3031
2962
|
const [scope, project, facet, child] = parts;
|
|
@@ -3033,32 +2964,32 @@ class DevGeneratedIndexSync {
|
|
|
3033
2964
|
return null;
|
|
3034
2965
|
if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
|
|
3035
2966
|
return null;
|
|
3036
|
-
return
|
|
2967
|
+
return path13.join(this.#workspaceRoot, scope, project, facet, "index.ts");
|
|
3037
2968
|
}
|
|
3038
2969
|
async#moduleIndexForDirectoryEvent(file) {
|
|
3039
|
-
const abs =
|
|
3040
|
-
const rel =
|
|
3041
|
-
if (rel.startsWith("..") ||
|
|
2970
|
+
const abs = path13.resolve(file);
|
|
2971
|
+
const rel = path13.relative(this.#workspaceRoot, abs);
|
|
2972
|
+
if (rel.startsWith("..") || path13.isAbsolute(rel))
|
|
3042
2973
|
return null;
|
|
3043
|
-
const parts = rel.split(
|
|
2974
|
+
const parts = rel.split(path13.sep).filter(Boolean);
|
|
3044
2975
|
if (parts.length !== 4 && parts.length !== 5)
|
|
3045
2976
|
return null;
|
|
3046
2977
|
const [scope, project, libSegment, moduleSegment, scalarSegment] = parts;
|
|
3047
2978
|
if (scope !== "apps" && scope !== "libs" || !project || libSegment !== "lib")
|
|
3048
2979
|
return null;
|
|
3049
2980
|
const isExistingDirectory = await stat2(abs).then((s) => s.isDirectory()).catch(() => false);
|
|
3050
|
-
const looksLikeDeletedDirectory = !
|
|
2981
|
+
const looksLikeDeletedDirectory = !path13.extname(abs);
|
|
3051
2982
|
if (!isExistingDirectory && !looksLikeDeletedDirectory)
|
|
3052
2983
|
return null;
|
|
3053
2984
|
if (moduleSegment === "__scalar" && scalarSegment) {
|
|
3054
|
-
return
|
|
2985
|
+
return path13.join(this.#workspaceRoot, scope, project, "lib", "__scalar", scalarSegment, "index.ts");
|
|
3055
2986
|
}
|
|
3056
2987
|
if (!moduleSegment || moduleSegment === "__scalar")
|
|
3057
2988
|
return null;
|
|
3058
|
-
return
|
|
2989
|
+
return path13.join(this.#workspaceRoot, scope, project, "lib", moduleSegment, "index.ts");
|
|
3059
2990
|
}
|
|
3060
2991
|
async#syncIndex(indexPath) {
|
|
3061
|
-
const dir =
|
|
2992
|
+
const dir = path13.dirname(indexPath);
|
|
3062
2993
|
const content = await this.#contentForIndex(indexPath);
|
|
3063
2994
|
if (content === null) {
|
|
3064
2995
|
if (!await exists(indexPath))
|
|
@@ -3074,14 +3005,14 @@ class DevGeneratedIndexSync {
|
|
|
3074
3005
|
return true;
|
|
3075
3006
|
}
|
|
3076
3007
|
async#contentForIndex(indexPath) {
|
|
3077
|
-
const parts =
|
|
3008
|
+
const parts = path13.relative(this.#workspaceRoot, indexPath).split(path13.sep).filter(Boolean);
|
|
3078
3009
|
const facet = parts.at(-2);
|
|
3079
3010
|
if (facet && BARREL_FACETS2.has(facet))
|
|
3080
|
-
return this.#facetContent(
|
|
3081
|
-
return this.#moduleContent(
|
|
3011
|
+
return this.#facetContent(path13.dirname(indexPath));
|
|
3012
|
+
return this.#moduleContent(path13.dirname(indexPath));
|
|
3082
3013
|
}
|
|
3083
3014
|
async#facetContent(dir) {
|
|
3084
|
-
const nameCasePattern =
|
|
3015
|
+
const nameCasePattern = path13.basename(dir) === "ui" ? FACET_PASCAL_CASE_RE : FACET_CAMEL_CASE_RE;
|
|
3085
3016
|
const entries = await readdir3(dir, { withFileTypes: true }).catch(() => null);
|
|
3086
3017
|
if (!entries)
|
|
3087
3018
|
return null;
|
|
@@ -3106,8 +3037,8 @@ class DevGeneratedIndexSync {
|
|
|
3106
3037
|
`;
|
|
3107
3038
|
}
|
|
3108
3039
|
async#moduleContent(dir) {
|
|
3109
|
-
const rel =
|
|
3110
|
-
const parts = rel.split(
|
|
3040
|
+
const rel = path13.relative(this.#workspaceRoot, dir);
|
|
3041
|
+
const parts = rel.split(path13.sep).filter(Boolean);
|
|
3111
3042
|
const moduleSegment = parts.at(-1);
|
|
3112
3043
|
if (!moduleSegment)
|
|
3113
3044
|
return null;
|
|
@@ -3119,7 +3050,7 @@ class DevGeneratedIndexSync {
|
|
|
3119
3050
|
const allowedTypes = isScalar ? SCALAR_UI_TYPES : moduleSegment.startsWith("_") ? SERVICE_UI_TYPES : MODULE_UI_TYPES;
|
|
3120
3051
|
const fileTypes = [];
|
|
3121
3052
|
for (const type of allowedTypes) {
|
|
3122
|
-
if (await exists(
|
|
3053
|
+
if (await exists(path13.join(dir, `${modelName}.${type}.tsx`)))
|
|
3123
3054
|
fileTypes.push(type);
|
|
3124
3055
|
}
|
|
3125
3056
|
if (fileTypes.length === 0)
|
|
@@ -3156,6 +3087,75 @@ var formatError2 = (err) => err instanceof Error ? err.message : String(err);
|
|
|
3156
3087
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
3157
3088
|
import path15 from "path";
|
|
3158
3089
|
import { SSR_DEV_DIRNAME, SSR_DEV_ROUTE_PREFIX } from "akanjs/server/hmr/csrDevManifest";
|
|
3090
|
+
|
|
3091
|
+
// pkgs/@akanjs/devkit/artifact/routeSeedIndex.ts
|
|
3092
|
+
import path14 from "path";
|
|
3093
|
+
import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
|
|
3094
|
+
import {
|
|
3095
|
+
ROUTE_SEED_INDEX_JSON
|
|
3096
|
+
} from "akanjs/server/artifact/routeSeedIndexStore";
|
|
3097
|
+
function computeRouteSeedIndex(pageEntries) {
|
|
3098
|
+
const layoutsByPrefix = new Map;
|
|
3099
|
+
const pagesBySegments = [];
|
|
3100
|
+
for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
|
|
3101
|
+
const parsed = parseRouteModuleKey(key);
|
|
3102
|
+
const files = [path14.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path14.resolve(seed))];
|
|
3103
|
+
if (parsed.kind === "layout" || parsed.kind === "overrides") {
|
|
3104
|
+
layoutsByPrefix.getOrInsert(parsed.routeSegments.join("/"), []).push(...files);
|
|
3105
|
+
} else if (parsed.kind === "page") {
|
|
3106
|
+
pagesBySegments.push({
|
|
3107
|
+
key,
|
|
3108
|
+
pattern: parsed.pattern,
|
|
3109
|
+
segments: parsed.routeSegments,
|
|
3110
|
+
files,
|
|
3111
|
+
includeOwnLayout: parsed.leaf === "_index"
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
assertUniqueRoutePatterns(pagesBySegments);
|
|
3116
|
+
const globalLayoutFiles = layoutsByPrefix.get("") ?? [];
|
|
3117
|
+
const seedEntries = [];
|
|
3118
|
+
for (const { pattern, segments, files, includeOwnLayout } of pagesBySegments) {
|
|
3119
|
+
const layouts = [];
|
|
3120
|
+
const maxPrefixLength = includeOwnLayout ? segments.length : Math.max(segments.length - 1, 0);
|
|
3121
|
+
for (let i = 0;i <= maxPrefixLength; i++) {
|
|
3122
|
+
const prefix = segments.slice(0, i).join("/");
|
|
3123
|
+
const layoutFiles = layoutsByPrefix.get(prefix);
|
|
3124
|
+
if (layoutFiles)
|
|
3125
|
+
layouts.push(...layoutFiles);
|
|
3126
|
+
}
|
|
3127
|
+
const seeds = Array.from(new Set([...layouts, ...files]));
|
|
3128
|
+
const routeId = pattern || "/";
|
|
3129
|
+
seedEntries.push({ routeId, pattern: routeId, seeds });
|
|
3130
|
+
}
|
|
3131
|
+
seedEntries.sort((a, b) => compareRouteSpecificity(a.pattern, b.pattern));
|
|
3132
|
+
return { entries: seedEntries, globalLayoutFiles };
|
|
3133
|
+
}
|
|
3134
|
+
function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
|
|
3135
|
+
const normalizedArtifactDir = path14.resolve(artifactDir);
|
|
3136
|
+
if (options.production)
|
|
3137
|
+
return { entries: index.entries.map((entry) => ({ routeId: entry.routeId })) };
|
|
3138
|
+
return {
|
|
3139
|
+
entries: index.entries.map((entry) => ({
|
|
3140
|
+
...entry,
|
|
3141
|
+
seeds: entry.seeds.map((seed) => serializeArtifactPath(seed, normalizedArtifactDir))
|
|
3142
|
+
})),
|
|
3143
|
+
globalLayoutFiles: index.globalLayoutFiles.map((file) => serializeArtifactPath(file, normalizedArtifactDir))
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
async function saveRouteSeedIndex(artifactDir, index, options = {}) {
|
|
3147
|
+
const absPath = path14.join(path14.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
|
|
3148
|
+
await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
|
|
3149
|
+
`);
|
|
3150
|
+
return absPath;
|
|
3151
|
+
}
|
|
3152
|
+
function serializeArtifactPath(artifactPath, artifactDir) {
|
|
3153
|
+
if (!path14.isAbsolute(artifactPath))
|
|
3154
|
+
return artifactPath;
|
|
3155
|
+
return path14.relative(artifactDir, artifactPath).split(path14.sep).join("/");
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// pkgs/@akanjs/devkit/frontendBuild/ssrDevBundler.ts
|
|
3159
3159
|
class SsrDevBundler extends DevRegistryBundler {
|
|
3160
3160
|
static #formatVersion = 2;
|
|
3161
3161
|
static bootstrapKey = "";
|