auto-server-split 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4847 @@
1
+ import {
2
+ BUILTIN_CLIENT_ONLY_PACKAGES,
3
+ BUILTIN_SERVER_SAFE_PACKAGES,
4
+ CSS_IN_JS_PACKAGES,
5
+ matchesPackageList,
6
+ packageNameOf
7
+ } from "./chunk-YCSBOPV3.js";
8
+
9
+ // src/analysis/skip.ts
10
+ var SKIP_CODES = [
11
+ "imported-by-client",
12
+ "exports-non-components",
13
+ "state-spans-component",
14
+ "context-provider",
15
+ "css-in-js-runtime",
16
+ "non-serializable-prop",
17
+ "untyped-boundary",
18
+ "spread-props",
19
+ "conditional-hooks-or-returns",
20
+ "class-component",
21
+ "dynamic-import",
22
+ "render-prop",
23
+ "hoc",
24
+ "server-env-difference",
25
+ "no-islands",
26
+ "fully-interactive",
27
+ "below-threshold",
28
+ "measure-failed",
29
+ "output-exists",
30
+ "would-create-route",
31
+ "unsupported-pattern",
32
+ "self-check-failed",
33
+ "parse-error",
34
+ "internal-error"
35
+ ];
36
+ var SKIP_TITLES = {
37
+ "imported-by-client": "imported by a client module",
38
+ "exports-non-components": "exports something other than components and types",
39
+ "state-spans-component": "client state spans the whole component",
40
+ "context-provider": "creates or provides a React Context",
41
+ "css-in-js-runtime": "uses runtime CSS-in-JS",
42
+ "non-serializable-prop": "a value crossing the boundary is not serializable",
43
+ "untyped-boundary": "untyped file: boundary values can't be proven serializable",
44
+ "spread-props": "spread props can't be proven serializable",
45
+ "conditional-hooks-or-returns": "conditional hooks or client-dependent early returns",
46
+ "class-component": "contains a class component",
47
+ "dynamic-import": "uses next/dynamic or React.lazy",
48
+ "render-prop": "uses a render prop or function-as-child",
49
+ hoc: "wrapped in memo/forwardRef or another higher-order component",
50
+ "server-env-difference": "reads an env var that differs between server and client",
51
+ "no-islands": "no client-only code found (the directive may be unnecessary)",
52
+ "fully-interactive": "nothing would stay on the server",
53
+ "below-threshold": "saving is below the configured threshold",
54
+ "measure-failed": "the saving couldn't be measured, so minBytesSaved can't be checked",
55
+ "output-exists": "the generated file already exists",
56
+ "would-create-route": "the generated file could become a route",
57
+ "unsupported-pattern": "uses a pattern this version does not handle",
58
+ "self-check-failed": "the generated code failed the self-check",
59
+ "parse-error": "the file could not be parsed",
60
+ "internal-error": "internal error (please report)"
61
+ };
62
+ function ok(value) {
63
+ return { ok: true, value };
64
+ }
65
+ function skip(code, message, location) {
66
+ return { ok: false, skip: location ? { code, message, location } : { code, message } };
67
+ }
68
+
69
+ // src/config/config.ts
70
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
71
+ import { createRequire } from "module";
72
+ import path from "path";
73
+ import { pathToFileURL } from "url";
74
+ import { buildSync } from "esbuild";
75
+ var DEFAULT_CONFIG = {
76
+ include: ["app/**", "components/**", "src/**"],
77
+ exclude: ["**/*.test.*", "**/*.spec.*", "**/*.stories.*"],
78
+ clientOnlyPackages: [],
79
+ serverSafeHooks: [],
80
+ clientFileSuffix: ".client",
81
+ islandsDir: void 0,
82
+ minBytesSaved: 2048,
83
+ minStaticRatio: 0.3,
84
+ prettier: "auto",
85
+ mongoose: { autoSerialize: false }
86
+ };
87
+ var CONFIG_FILES = [
88
+ "auto-split.config.ts",
89
+ "auto-split.config.mts",
90
+ "auto-split.config.mjs",
91
+ "auto-split.config.js",
92
+ "auto-split.config.json"
93
+ ];
94
+ var ConfigError = class extends Error {
95
+ };
96
+ var KEYS = {
97
+ include: stringArray,
98
+ exclude: stringArray,
99
+ clientOnlyPackages: stringArray,
100
+ serverSafeHooks: stringArray,
101
+ clientFileSuffix: (v) => typeof v === "string" && /^\.[\w.-]+$/.test(v) ? void 0 : 'must be a string starting with "." (e.g. ".client")',
102
+ islandsDir: (v) => typeof v === "string" && v.length > 0 && !path.isAbsolute(v) && !v.includes("..") ? void 0 : "must be a relative directory name",
103
+ minBytesSaved: (v) => typeof v === "number" && v >= 0 && Number.isFinite(v) ? void 0 : "must be a non-negative number",
104
+ minStaticRatio: (v) => typeof v === "number" && v >= 0 && v <= 1 ? void 0 : "must be a number between 0 and 1",
105
+ prettier: (v) => v === "auto" || v === false ? void 0 : 'must be "auto" or false',
106
+ mongoose: (v) => {
107
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return "must be an object";
108
+ for (const [k, val] of Object.entries(v)) {
109
+ if (k !== "autoSerialize") return `has an unknown key "${k}" (allowed: autoSerialize)`;
110
+ if (typeof val !== "boolean") return "autoSerialize must be a boolean";
111
+ }
112
+ return void 0;
113
+ }
114
+ };
115
+ function stringArray(v) {
116
+ return Array.isArray(v) && v.every((x) => typeof x === "string") ? void 0 : "must be an array of strings";
117
+ }
118
+ function validateConfig(value, source) {
119
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
120
+ throw new ConfigError(`${source}: the config must be an object`);
121
+ }
122
+ const problems = [];
123
+ for (const [key, v] of Object.entries(value)) {
124
+ const check = KEYS[key];
125
+ if (!check) {
126
+ problems.push(`unknown key "${key}" (allowed: ${Object.keys(KEYS).join(", ")})`);
127
+ continue;
128
+ }
129
+ if (v === void 0) continue;
130
+ const problem = check(v);
131
+ if (problem) problems.push(`"${key}" ${problem}`);
132
+ }
133
+ if (problems.length > 0) throw new ConfigError(`${source}:
134
+ - ${problems.join("\n - ")}`);
135
+ return value;
136
+ }
137
+ function resolveConfig(...layers) {
138
+ const out = { ...DEFAULT_CONFIG, mongoose: { ...DEFAULT_CONFIG.mongoose } };
139
+ for (const layer of layers) {
140
+ if (!layer) continue;
141
+ for (const [key, value] of Object.entries(layer)) {
142
+ if (value === void 0) continue;
143
+ if (key === "mongoose")
144
+ out.mongoose = { ...out.mongoose, ...value };
145
+ else out[key] = value;
146
+ }
147
+ }
148
+ return out;
149
+ }
150
+ function findConfigFile(root) {
151
+ return CONFIG_FILES.map((f) => path.join(root, f)).find((f) => existsSync(f));
152
+ }
153
+ function readJsonConfig(file) {
154
+ try {
155
+ return JSON.parse(readFileSync(file, "utf8"));
156
+ } catch (error) {
157
+ throw new ConfigError(`${path.basename(file)}: invalid JSON (${error.message})`);
158
+ }
159
+ }
160
+ async function loadConfigFile(file) {
161
+ const rel2 = path.basename(file);
162
+ let raw;
163
+ if (file.endsWith(".json")) {
164
+ raw = readJsonConfig(file);
165
+ } else if (file.endsWith(".ts") || file.endsWith(".mts")) {
166
+ const esbuild = await import("esbuild");
167
+ const result = await esbuild.build({
168
+ entryPoints: [file],
169
+ bundle: true,
170
+ write: false,
171
+ format: "esm",
172
+ platform: "node",
173
+ packages: "external",
174
+ logLevel: "silent",
175
+ plugins: [
176
+ {
177
+ name: "auto-split-shim",
178
+ setup(build) {
179
+ build.onResolve({ filter: /^auto-server-split$/ }, () => ({
180
+ path: "shim",
181
+ namespace: "auto-split-shim"
182
+ }));
183
+ build.onLoad({ filter: /.*/, namespace: "auto-split-shim" }, () => ({
184
+ contents: "export const defineConfig = (c) => c;",
185
+ loader: "js"
186
+ }));
187
+ }
188
+ }
189
+ ]
190
+ });
191
+ const code = result.outputFiles[0]?.text ?? "";
192
+ const temp = path.join(
193
+ path.dirname(file),
194
+ `.auto-split.config.${process.pid}.${Date.now()}.mjs`
195
+ );
196
+ writeFileSync(temp, code);
197
+ try {
198
+ raw = (await import(pathToFileURL(temp).href)).default;
199
+ } finally {
200
+ rmSync(temp, { force: true });
201
+ }
202
+ } else {
203
+ raw = (await import(pathToFileURL(file).href)).default;
204
+ }
205
+ return validateConfig(raw, rel2);
206
+ }
207
+ function loadConfigFileSync(file) {
208
+ const rel2 = path.basename(file);
209
+ if (file.endsWith(".json")) return validateConfig(readJsonConfig(file), rel2);
210
+ let code;
211
+ try {
212
+ code = buildSync({
213
+ entryPoints: [file],
214
+ bundle: true,
215
+ write: false,
216
+ format: "cjs",
217
+ platform: "node",
218
+ packages: "external",
219
+ logLevel: "silent",
220
+ // CommonJS has no import.meta; give the config the values a native import() would.
221
+ define: {
222
+ "import.meta.url": JSON.stringify(pathToFileURL(file).href),
223
+ "import.meta.dirname": JSON.stringify(path.dirname(file)),
224
+ "import.meta.filename": JSON.stringify(file)
225
+ }
226
+ }).outputFiles[0]?.text ?? "";
227
+ } catch (error) {
228
+ const first = error.errors?.[0]?.text;
229
+ throw new ConfigError(
230
+ `${rel2}: couldn't be loaded synchronously for ESLint (${first ?? String(error)})`
231
+ );
232
+ }
233
+ const requireFromConfig = createRequire(file);
234
+ const module = { exports: {} };
235
+ const evaluate = new Function("module", "exports", "require", "__filename", "__dirname", code);
236
+ evaluate(
237
+ module,
238
+ module.exports,
239
+ (id) => id === "auto-server-split" ? { defineConfig: (c) => c } : requireFromConfig(id),
240
+ file,
241
+ path.dirname(file)
242
+ );
243
+ const exported = module.exports;
244
+ return validateConfig(exported.__esModule ? exported.default : exported, rel2);
245
+ }
246
+
247
+ // src/split.ts
248
+ import { existsSync as existsSync5, mkdirSync, realpathSync, writeFileSync as writeFileSync2 } from "fs";
249
+ import path9 from "path";
250
+ import { createTwoFilesPatch } from "diff";
251
+ import fg3 from "fast-glob";
252
+ import { ts as ts18 } from "ts-morph";
253
+
254
+ // src/analysis/components.ts
255
+ import { ts as ts2 } from "ts-morph";
256
+
257
+ // src/analysis/context.ts
258
+ function lineCol(sf, pos) {
259
+ const lc = sf.getLineAndCharacterOfPosition(pos);
260
+ return { line: lc.line + 1, column: lc.character + 1 };
261
+ }
262
+ function locOf(ctx, node) {
263
+ return lineCol(ctx.sf, node.getStart(ctx.sf));
264
+ }
265
+
266
+ // src/analysis/refs.ts
267
+ import { ts } from "ts-morph";
268
+ function isJsxElementLike(node) {
269
+ return ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node);
270
+ }
271
+ function isIntrinsicTagName(name) {
272
+ if (ts.isIdentifier(name)) return /^[a-z]/.test(name.text) || name.text.includes("-");
273
+ return ts.isJsxNamespacedName(name);
274
+ }
275
+ function tagNameText(sf, node) {
276
+ if (ts.isJsxFragment(node)) return "Fragment";
277
+ const tag = ts.isJsxElement(node) ? node.openingElement.tagName : node.tagName;
278
+ return tag.getText(sf);
279
+ }
280
+ function isValueReference(id) {
281
+ const parent = id.parent;
282
+ if (!parent) return false;
283
+ if (ts.isPropertyAccessExpression(parent) && parent.name === id) return false;
284
+ if (ts.isQualifiedName(parent) && parent.right === id) return false;
285
+ if ((ts.isPropertyAssignment(parent) || ts.isPropertyDeclaration(parent) || ts.isMethodDeclaration(parent) || ts.isGetAccessorDeclaration(parent) || ts.isSetAccessorDeclaration(parent) || ts.isPropertySignature(parent) || ts.isMethodSignature(parent) || ts.isEnumMember(parent)) && parent.name === id) {
286
+ return false;
287
+ }
288
+ if (ts.isBindingElement(parent) && (parent.propertyName === id || parent.name === id))
289
+ return false;
290
+ if ((ts.isVariableDeclaration(parent) || ts.isParameter(parent) || ts.isFunctionDeclaration(parent) || ts.isFunctionExpression(parent) || ts.isClassDeclaration(parent) || ts.isClassExpression(parent) || ts.isEnumDeclaration(parent) || ts.isTypeAliasDeclaration(parent) || ts.isInterfaceDeclaration(parent) || ts.isTypeParameterDeclaration(parent)) && parent.name === id) {
291
+ return false;
292
+ }
293
+ if (ts.isJsxAttribute(parent) && parent.name === id) return false;
294
+ if (ts.isJsxClosingElement(parent)) return false;
295
+ if ((ts.isJsxOpeningElement(parent) || ts.isJsxSelfClosingElement(parent)) && parent.tagName === id) {
296
+ return !isIntrinsicTagName(id);
297
+ }
298
+ if (ts.isImportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent) || ts.isExportSpecifier(parent) || ts.isImportEqualsDeclaration(parent)) {
299
+ return false;
300
+ }
301
+ if ((ts.isLabeledStatement(parent) || ts.isBreakStatement(parent) || ts.isContinueStatement(parent)) && parent.label === id) {
302
+ return false;
303
+ }
304
+ if (ts.isMetaProperty(parent) || ts.isJsxNamespacedName(parent)) return false;
305
+ return true;
306
+ }
307
+ function isTypeSyntax(node) {
308
+ return ts.isTypeNode(node) && !ts.isExpressionWithTypeArguments(node);
309
+ }
310
+ function forEachValueRef(node, cb, stop) {
311
+ const visit = (n) => {
312
+ if (n !== node && stop?.(n)) return;
313
+ if (isTypeSyntax(n)) return;
314
+ if (ts.isTypeAliasDeclaration(n) || ts.isInterfaceDeclaration(n)) return;
315
+ if (ts.isIdentifier(n)) {
316
+ if (isValueReference(n)) cb(n);
317
+ return;
318
+ }
319
+ if (ts.isJsxElement(n)) {
320
+ visit(n.openingElement);
321
+ n.children.forEach(visit);
322
+ return;
323
+ }
324
+ ts.forEachChild(n, visit);
325
+ };
326
+ visit(node);
327
+ }
328
+ function forEachIdentifier(node, cb) {
329
+ const visit = (n) => {
330
+ if (ts.isIdentifier(n)) cb(n);
331
+ ts.forEachChild(n, visit);
332
+ };
333
+ visit(node);
334
+ }
335
+ function symbolOfRef(checker, id) {
336
+ if (ts.isShorthandPropertyAssignment(id.parent) && id.parent.name === id) {
337
+ return checker.getShorthandAssignmentValueSymbol(id.parent);
338
+ }
339
+ return checker.getSymbolAtLocation(id);
340
+ }
341
+ function bindingIdentifiers(name) {
342
+ if (ts.isIdentifier(name)) return [name];
343
+ const out = [];
344
+ for (const element of name.elements) {
345
+ if (ts.isOmittedExpression(element)) continue;
346
+ out.push(...bindingIdentifiers(element.name));
347
+ }
348
+ return out;
349
+ }
350
+ function containsRange(outer, inner) {
351
+ return inner.pos >= outer.pos && inner.end <= outer.end;
352
+ }
353
+ function mapCallOf(fn) {
354
+ const parent = fn.parent;
355
+ if (parent && ts.isCallExpression(parent) && parent.arguments[0] === fn && ts.isPropertyAccessExpression(parent.expression) && parent.expression.name.text === "map") {
356
+ return parent;
357
+ }
358
+ return void 0;
359
+ }
360
+ function stripParens(expr) {
361
+ let e = expr;
362
+ while (ts.isParenthesizedExpression(e)) e = e.expression;
363
+ return e;
364
+ }
365
+ var HOOK_NAME_RE = /^use[A-Z]/;
366
+ function calleeName(call) {
367
+ const callee = call.expression;
368
+ if (ts.isIdentifier(callee)) return callee.text;
369
+ if (ts.isPropertyAccessExpression(callee)) return callee.name.text;
370
+ return void 0;
371
+ }
372
+
373
+ // src/analysis/components.ts
374
+ function componentDeclarationNode(component) {
375
+ return ts2.isFunctionDeclaration(component.fn) ? component.fn : component.fn.parent;
376
+ }
377
+ function isCapitalized(name) {
378
+ return /^[A-Z]/.test(name);
379
+ }
380
+ function hasExportModifier(node) {
381
+ return ts2.canHaveModifiers(node) && (ts2.getModifiers(node)?.some((m) => m.kind === ts2.SyntaxKind.ExportKeyword) ?? false);
382
+ }
383
+ function hasDefaultModifier(node) {
384
+ return ts2.canHaveModifiers(node) && (ts2.getModifiers(node)?.some((m) => m.kind === ts2.SyntaxKind.DefaultKeyword) ?? false);
385
+ }
386
+ function returnsJsx(fn) {
387
+ if (!fn.body) return false;
388
+ if (!ts2.isBlock(fn.body)) return containsJsx(fn.body);
389
+ let found = false;
390
+ const visit = (n) => {
391
+ if (found) return;
392
+ if (ts2.isFunctionLike(n) && n !== fn) return;
393
+ if (ts2.isReturnStatement(n) && n.expression && containsJsx(n.expression)) found = true;
394
+ ts2.forEachChild(n, visit);
395
+ };
396
+ visit(fn.body);
397
+ return found;
398
+ }
399
+ function containsJsx(node) {
400
+ if (isJsxElementLike(node)) return true;
401
+ return ts2.forEachChild(node, (c) => containsJsx(c) ? true : void 0) ?? false;
402
+ }
403
+ function hocCallName(expr, sf) {
404
+ if (ts2.isCallExpression(expr)) return expr.expression.getText(sf);
405
+ return void 0;
406
+ }
407
+ function analyzeFileShape(ctx, facts) {
408
+ const { sf } = ctx;
409
+ const components = [];
410
+ const moduleDecls = [];
411
+ const exportedNames = /* @__PURE__ */ new Map();
412
+ let defaultExportName;
413
+ for (const statement of sf.statements) {
414
+ if (ts2.isImportDeclaration(statement)) continue;
415
+ if (ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression)) continue;
416
+ if (ts2.isTypeAliasDeclaration(statement) || ts2.isInterfaceDeclaration(statement)) {
417
+ moduleDecls.push({
418
+ statement,
419
+ names: [statement.name.text],
420
+ kind: "type",
421
+ exported: hasExportModifier(statement)
422
+ });
423
+ continue;
424
+ }
425
+ if (ts2.isFunctionDeclaration(statement)) {
426
+ const name = statement.name?.text ?? (hasDefaultModifier(statement) ? "default" : "");
427
+ const exported = hasExportModifier(statement);
428
+ if ((isCapitalized(name) || name === "default") && returnsJsx(statement)) {
429
+ if (statement.asteriskToken || statement.modifiers?.some((m) => m.kind === ts2.SyntaxKind.AsyncKeyword)) {
430
+ return skip(
431
+ "unsupported-pattern",
432
+ `${name} is an async or generator function`,
433
+ locOf(ctx, statement)
434
+ );
435
+ }
436
+ components.push({
437
+ name: name === "default" ? "Component" : name,
438
+ fn: statement,
439
+ statement,
440
+ exported: exported ? hasDefaultModifier(statement) ? "default" : "named" : "none"
441
+ });
442
+ } else {
443
+ if (exported) {
444
+ const why = isCapitalized(name) ? "doesn't return JSX, so it isn't treated as a component" : "is not a component";
445
+ return skip(
446
+ "exports-non-components",
447
+ `exports ${name || "a function"}, which ${why}`,
448
+ locOf(ctx, statement)
449
+ );
450
+ }
451
+ moduleDecls.push({
452
+ statement,
453
+ names: name ? [name] : [],
454
+ kind: "function",
455
+ exported: false
456
+ });
457
+ }
458
+ continue;
459
+ }
460
+ if (ts2.isVariableStatement(statement)) {
461
+ const exported = hasExportModifier(statement);
462
+ const isConst = (statement.declarationList.flags & ts2.NodeFlags.Const) !== 0;
463
+ let isComponentStatement = false;
464
+ for (const decl of statement.declarationList.declarations) {
465
+ const name = ts2.isIdentifier(decl.name) ? decl.name.text : void 0;
466
+ const init = decl.initializer;
467
+ const fn = init && (ts2.isArrowFunction(init) || ts2.isFunctionExpression(init)) ? init : void 0;
468
+ if (name && isCapitalized(name) && fn && returnsJsx(fn)) {
469
+ if (statement.declarationList.declarations.length > 1) {
470
+ return skip(
471
+ "unsupported-pattern",
472
+ `${name} is declared together with other variables`,
473
+ locOf(ctx, decl)
474
+ );
475
+ }
476
+ if (fn.modifiers?.some((m) => m.kind === ts2.SyntaxKind.AsyncKeyword)) {
477
+ return skip("unsupported-pattern", `${name} is an async function`, locOf(ctx, decl));
478
+ }
479
+ components.push({ name, fn, statement, exported: exported ? "named" : "none" });
480
+ isComponentStatement = true;
481
+ continue;
482
+ }
483
+ if (name && init && isCapitalized(name)) {
484
+ const hoc = hocCallName(init, sf);
485
+ if (hoc && /(^|\.)(memo|forwardRef)$/.test(hoc)) {
486
+ return skip(
487
+ "hoc",
488
+ `${name} is wrapped in ${hoc}(), which this version doesn't split`,
489
+ locOf(ctx, decl)
490
+ );
491
+ }
492
+ }
493
+ if (exported) {
494
+ return skip(
495
+ "exports-non-components",
496
+ `exports ${name ?? "a value"}, which is not a component`,
497
+ locOf(ctx, decl)
498
+ );
499
+ }
500
+ }
501
+ if (isComponentStatement) continue;
502
+ if (!isConst) {
503
+ return skip(
504
+ "unsupported-pattern",
505
+ "declares module-level `let`/`var` state, which would be shared across requests on the server",
506
+ locOf(ctx, statement)
507
+ );
508
+ }
509
+ const names = statement.declarationList.declarations.flatMap(
510
+ (d) => ts2.isIdentifier(d.name) ? [d.name.text] : []
511
+ );
512
+ if (statement.declarationList.declarations.some((d) => !ts2.isIdentifier(d.name))) {
513
+ return skip(
514
+ "unsupported-pattern",
515
+ "uses a destructuring declaration at module level",
516
+ locOf(ctx, statement)
517
+ );
518
+ }
519
+ moduleDecls.push({ statement, names, kind: "const", exported: false });
520
+ continue;
521
+ }
522
+ if (ts2.isClassDeclaration(statement)) {
523
+ const extendsComponent = statement.heritageClauses?.some(
524
+ (h) => h.types.some((t) => /(^|\.)(Component|PureComponent)$/.test(t.expression.getText(sf)))
525
+ );
526
+ if (extendsComponent)
527
+ return skip(
528
+ "class-component",
529
+ `${statement.name?.text ?? "a class"} is a class component`,
530
+ locOf(ctx, statement)
531
+ );
532
+ if (hasExportModifier(statement)) {
533
+ return skip(
534
+ "exports-non-components",
535
+ `exports the class ${statement.name?.text ?? ""}`.trim(),
536
+ locOf(ctx, statement)
537
+ );
538
+ }
539
+ moduleDecls.push({
540
+ statement,
541
+ names: statement.name ? [statement.name.text] : [],
542
+ kind: "class",
543
+ exported: false
544
+ });
545
+ continue;
546
+ }
547
+ if (ts2.isEnumDeclaration(statement)) {
548
+ if (hasExportModifier(statement)) {
549
+ return skip(
550
+ "exports-non-components",
551
+ `exports the enum ${statement.name.text}`,
552
+ locOf(ctx, statement)
553
+ );
554
+ }
555
+ moduleDecls.push({ statement, names: [statement.name.text], kind: "enum", exported: false });
556
+ continue;
557
+ }
558
+ if (ts2.isExportAssignment(statement)) {
559
+ const expr = statement.expression;
560
+ if (ts2.isIdentifier(expr)) {
561
+ defaultExportName = expr.text;
562
+ exportedNames.set(expr.text, statement);
563
+ continue;
564
+ }
565
+ const hoc = hocCallName(expr, sf);
566
+ if (hoc)
567
+ return skip(
568
+ "hoc",
569
+ `the default export is wrapped in ${hoc}(), which this version doesn't split`,
570
+ locOf(ctx, statement)
571
+ );
572
+ return skip(
573
+ "exports-non-components",
574
+ "the default export is not a component",
575
+ locOf(ctx, statement)
576
+ );
577
+ }
578
+ if (ts2.isExportDeclaration(statement)) {
579
+ if (statement.moduleSpecifier) {
580
+ if (statement.isTypeOnly) continue;
581
+ return skip(
582
+ "exports-non-components",
583
+ "re-exports another module; its exports can't be verified",
584
+ locOf(ctx, statement)
585
+ );
586
+ }
587
+ const clause = statement.exportClause;
588
+ if (clause && ts2.isNamedExports(clause)) {
589
+ for (const element of clause.elements) {
590
+ if (statement.isTypeOnly || element.isTypeOnly) continue;
591
+ const local = (element.propertyName ?? element.name).text;
592
+ exportedNames.set(local, element);
593
+ if (element.name.text === "default") defaultExportName = local;
594
+ }
595
+ }
596
+ continue;
597
+ }
598
+ return skip(
599
+ "unsupported-pattern",
600
+ "has a module-level statement that runs on import",
601
+ locOf(ctx, statement)
602
+ );
603
+ }
604
+ for (const [name, node] of exportedNames) {
605
+ const component = components.find((c) => c.name === name);
606
+ if (component) {
607
+ if (component.exported === "none")
608
+ component.exported = name === defaultExportName ? "default" : "named";
609
+ continue;
610
+ }
611
+ const decl = moduleDecls.find((d) => d.names.includes(name));
612
+ if (decl?.kind === "type") continue;
613
+ return skip(
614
+ "exports-non-components",
615
+ `exports ${name}, which is not a component`,
616
+ locOf(ctx, node)
617
+ );
618
+ }
619
+ for (const decl of moduleDecls) {
620
+ if (decl.kind !== "const" && decl.kind !== "class") continue;
621
+ const markers = facts.scanMarkers(
622
+ decl.statement,
623
+ (n) => ts2.isFunctionLike(n) || ts2.isClassLike(n)
624
+ );
625
+ const browser = markers.find(
626
+ (m) => m.kind === "browser-global" || m.kind === "hook" || m.kind === "nondeterministic"
627
+ );
628
+ if (browser) {
629
+ return skip(
630
+ "unsupported-pattern",
631
+ `module-level code uses ${browser.detail}; it would run on the server after the split`,
632
+ locOf(ctx, browser.node)
633
+ );
634
+ }
635
+ }
636
+ if (components.length === 0) {
637
+ return skip("unsupported-pattern", "no function component found in this file", void 0);
638
+ }
639
+ const componentDecls = /* @__PURE__ */ new Map();
640
+ for (const component of components) {
641
+ componentDecls.set(component, {
642
+ statement: component.statement,
643
+ names: [component.name],
644
+ kind: "function",
645
+ exported: component.exported !== "none",
646
+ component
647
+ });
648
+ }
649
+ return ok({ components, moduleDecls, componentDecls });
650
+ }
651
+
652
+ // src/analysis/importTable.ts
653
+ import path2 from "path";
654
+ import { ts as ts3 } from "ts-morph";
655
+ var REACT_NEUTRAL = /* @__PURE__ */ new Set([
656
+ "default",
657
+ "*",
658
+ "Fragment",
659
+ "Suspense",
660
+ "StrictMode",
661
+ "Profiler",
662
+ "Children",
663
+ "cloneElement",
664
+ "isValidElement",
665
+ "createElement",
666
+ "cache",
667
+ "version"
668
+ ]);
669
+ var REACT_SPECIAL = {
670
+ createContext: "context",
671
+ Component: "class",
672
+ PureComponent: "class",
673
+ memo: "hoc",
674
+ forwardRef: "hoc",
675
+ lazy: "lazy"
676
+ };
677
+ var REACT_DOM_NEUTRAL = /* @__PURE__ */ new Set([
678
+ "preload",
679
+ "preinit",
680
+ "preconnect",
681
+ "prefetchDNS",
682
+ "preloadModule",
683
+ "preinitModule"
684
+ ]);
685
+ function buildImportTable(sf, checker, graph) {
686
+ const file = path2.resolve(sf.fileName);
687
+ const table = {
688
+ bindings: [],
689
+ bySymbol: /* @__PURE__ */ new Map(),
690
+ declarations: [],
691
+ sideEffectImports: []
692
+ };
693
+ for (const statement of sf.statements) {
694
+ if (!ts3.isImportDeclaration(statement) || !ts3.isStringLiteral(statement.moduleSpecifier))
695
+ continue;
696
+ table.declarations.push(statement);
697
+ const specifier = statement.moduleSpecifier.text;
698
+ const clause = statement.importClause;
699
+ if (!clause) {
700
+ table.sideEffectImports.push(statement);
701
+ continue;
702
+ }
703
+ const edge = graph.edgeFor(file, specifier);
704
+ const module = edge?.to ? { kind: "project", file: edge.to, directive: graph.nodes.get(edge.to)?.directive } : {
705
+ kind: "external",
706
+ info: edge?.external ?? {
707
+ specifier,
708
+ packageName: specifier,
709
+ kind: "unresolved",
710
+ detail: "not in module graph"
711
+ }
712
+ };
713
+ const add = (node, nameNode, imported, typeOnly) => {
714
+ const symbol = checker.getSymbolAtLocation(nameNode);
715
+ if (!symbol) return;
716
+ const binding = {
717
+ local: nameNode.text,
718
+ imported,
719
+ symbol,
720
+ declaration: statement,
721
+ node,
722
+ typeOnly: typeOnly || clause.isTypeOnly,
723
+ module,
724
+ specifier
725
+ };
726
+ table.bindings.push(binding);
727
+ table.bySymbol.set(symbol, binding);
728
+ };
729
+ if (clause.name) add(clause, clause.name, "default", false);
730
+ const bindings = clause.namedBindings;
731
+ if (bindings && ts3.isNamespaceImport(bindings)) add(bindings, bindings.name, "*", false);
732
+ if (bindings && ts3.isNamedImports(bindings)) {
733
+ for (const element of bindings.elements) {
734
+ add(element, element.name, (element.propertyName ?? element.name).text, element.isTypeOnly);
735
+ }
736
+ }
737
+ }
738
+ return table;
739
+ }
740
+ function classifyImportUsage(binding, member, asJsxTag, framework, projectSafety) {
741
+ const name = binding.imported === "default" || binding.imported === "*" ? member ?? binding.imported : binding.imported;
742
+ const from = binding.specifier;
743
+ const mod = binding.module;
744
+ if (mod.kind === "project") {
745
+ if (mod.directive === "use client") {
746
+ return asJsxTag ? { cls: "client-reference", reason: `${binding.local} is a Client Component from ${from}` } : {
747
+ cls: "client-only",
748
+ reason: `${binding.local} comes from the "use client" module ${from}`
749
+ };
750
+ }
751
+ if (mod.directive === "use server")
752
+ return { cls: "server-action", reason: `${binding.local} is a Server Action` };
753
+ const safety = projectSafety(mod.file);
754
+ return safety.safe ? { cls: "neutral", reason: `${from} is server-safe` } : {
755
+ cls: "client-only",
756
+ reason: `${binding.local} from ${from} is client-only (${safety.reason ?? "uses client APIs"})`
757
+ };
758
+ }
759
+ const info = mod.info;
760
+ switch (info.kind) {
761
+ case "react": {
762
+ const special = REACT_SPECIAL[name];
763
+ if (special) return { cls: "client-only", special, reason: `React.${name}` };
764
+ if (REACT_NEUTRAL.has(name)) return { cls: "neutral", reason: `React.${name}` };
765
+ return { cls: "client-only", reason: `React.${name} only works in Client Components` };
766
+ }
767
+ case "react-dom":
768
+ return REACT_DOM_NEUTRAL.has(name) ? { cls: "neutral", reason: `ReactDOM.${name}` } : { cls: "client-only", reason: `ReactDOM.${name} only works in the browser` };
769
+ case "framework": {
770
+ const rule = framework.classifyModule(from);
771
+ if (!rule) return { cls: "client-only", reason: `${from} is not understood` };
772
+ if (rule.kind === "dynamic-import")
773
+ return { cls: "client-only", special: "dynamic", reason: `${from}` };
774
+ if (rule.kind === "css-in-js")
775
+ return { cls: "client-only", special: "css-in-js", reason: `${from}` };
776
+ const cls = rule.classify(name);
777
+ return { cls, reason: `${name} from ${from}` };
778
+ }
779
+ case "css-in-js":
780
+ return {
781
+ cls: "client-only",
782
+ special: "css-in-js",
783
+ reason: `${from} is a runtime CSS-in-JS library`
784
+ };
785
+ case "use-client-entry":
786
+ return asJsxTag ? { cls: "client-reference", reason: `${binding.local} is a Client Component from ${from}` } : {
787
+ cls: "client-only",
788
+ reason: `${binding.local} comes from the "use client" package ${from}`
789
+ };
790
+ case "client-list":
791
+ return {
792
+ cls: "client-only",
793
+ reason: `${from} is a client-only package${info.detail ? ` (${info.detail})` : ""}`
794
+ };
795
+ case "client-only-marker":
796
+ return { cls: "client-only", reason: `${from} is marked client-only` };
797
+ case "react-unknown":
798
+ return {
799
+ cls: "client-only",
800
+ reason: info.detail ?? `${from} depends on React and isn't known to be server-safe`
801
+ };
802
+ case "unresolved":
803
+ return {
804
+ cls: "client-only",
805
+ reason: `${from} could not be resolved${info.detail ? ` (${info.detail})` : ""}`
806
+ };
807
+ case "builtin":
808
+ case "server-only-marker":
809
+ return { cls: "server-only", reason: `${from} only works on the server` };
810
+ case "server-safe-list":
811
+ case "neutral":
812
+ case "json":
813
+ case "asset":
814
+ case "css-module":
815
+ case "stylesheet":
816
+ return { cls: "neutral", reason: `${from} is server-safe` };
817
+ }
818
+ }
819
+
820
+ // src/analysis/importers.ts
821
+ import { ts as ts5 } from "ts-morph";
822
+
823
+ // src/analysis/directives.ts
824
+ import { ts as ts4 } from "ts-morph";
825
+ function getModuleDirective(sf) {
826
+ for (const statement of sf.statements) {
827
+ if (!ts4.isExpressionStatement(statement) || !ts4.isStringLiteral(statement.expression)) break;
828
+ const text = statement.expression.text;
829
+ if (text === "use client" || text === "use server") return { directive: text, statement };
830
+ }
831
+ return { directive: void 0, statement: void 0 };
832
+ }
833
+ function hasUseServerBody(fn) {
834
+ const body = fn.body;
835
+ if (!body || !ts4.isBlock(body)) return false;
836
+ for (const statement of body.statements) {
837
+ if (!ts4.isExpressionStatement(statement) || !ts4.isStringLiteral(statement.expression)) break;
838
+ if (statement.expression.text === "use server") return true;
839
+ }
840
+ return false;
841
+ }
842
+
843
+ // src/analysis/importers.ts
844
+ function isInTypePosition(node) {
845
+ let current = node;
846
+ while (current.parent) {
847
+ const parent = current.parent;
848
+ if (ts5.isTypeNode(parent) && !ts5.isExpressionWithTypeArguments(parent)) return true;
849
+ if (ts5.isTypeQueryNode(parent)) return true;
850
+ if (ts5.isStatement(parent) || ts5.isSourceFile(parent)) return false;
851
+ current = parent;
852
+ }
853
+ return false;
854
+ }
855
+ function isJsxTagPosition(id) {
856
+ let node = id;
857
+ if (ts5.isPropertyAccessExpression(node.parent) && node.parent.name === node) node = node.parent;
858
+ const parent = node.parent;
859
+ return (ts5.isJsxOpeningElement(parent) || ts5.isJsxSelfClosingElement(parent) || ts5.isJsxClosingElement(parent)) && parent.tagName === node;
860
+ }
861
+ function checkImporters(program, graph, file, components) {
862
+ const checker = program.getTypeChecker();
863
+ const actionUsage = /* @__PURE__ */ new Map();
864
+ let sawSpread = false;
865
+ const componentDecls = new Set(components.map(componentDeclarationNode));
866
+ const importers = /* @__PURE__ */ new Set();
867
+ const queue = [file];
868
+ const visited = /* @__PURE__ */ new Set([file]);
869
+ while (queue.length > 0) {
870
+ const target = queue.shift();
871
+ const edges = graph.importersOf(target).filter((e) => e.kind !== "type").sort((a, b) => a.from.localeCompare(b.from) || a.line - b.line);
872
+ for (const edge of edges) {
873
+ const importer = edge.from;
874
+ const rel2 = graph.rel(importer);
875
+ const node = graph.nodes.get(importer);
876
+ if (graph.isClient(importer)) {
877
+ const root = graph.clientRootOf(importer);
878
+ const via = root && root !== importer ? `, reached from ${graph.rel(root)}` : "";
879
+ return skip("imported-by-client", `imported by ${rel2} (client${via})`);
880
+ }
881
+ if (!node?.inProgram) {
882
+ return skip(
883
+ "imported-by-client",
884
+ `imported by ${rel2}, which is outside the TypeScript project, so its usage can't be verified`
885
+ );
886
+ }
887
+ if (edge.kind === "dynamic") {
888
+ return skip("dynamic-import", `loaded with import() by ${rel2}`);
889
+ }
890
+ importers.add(importer);
891
+ if (edge.kind === "reexport" && !visited.has(importer)) {
892
+ visited.add(importer);
893
+ queue.push(importer);
894
+ }
895
+ const sf = program.getSourceFile(importer);
896
+ if (!sf) {
897
+ return skip(
898
+ "imported-by-client",
899
+ `imported by ${rel2}, which couldn't be loaded to verify how it uses the component`
900
+ );
901
+ }
902
+ let misuse;
903
+ const recordActionProps = (tag) => {
904
+ let tagNode = tag;
905
+ if (ts5.isPropertyAccessExpression(tagNode.parent)) tagNode = tagNode.parent;
906
+ const element = tagNode.parent;
907
+ if (!ts5.isJsxOpeningElement(element) && !ts5.isJsxSelfClosingElement(element)) return;
908
+ for (const attr of element.attributes.properties) {
909
+ if (ts5.isJsxSpreadAttribute(attr)) {
910
+ sawSpread = true;
911
+ continue;
912
+ }
913
+ const name = attr.name.getText(sf);
914
+ const init = attr.initializer;
915
+ const expr = init && ts5.isJsxExpression(init) ? init.expression : void 0;
916
+ const isAction = expr !== void 0 && isServerActionReference(expr, checker);
917
+ actionUsage.set(name, (actionUsage.get(name) ?? true) && isAction);
918
+ }
919
+ };
920
+ const visit = (n) => {
921
+ if (misuse) return;
922
+ if (ts5.isIdentifier(n)) {
923
+ const symbol = checker.getSymbolAtLocation(n);
924
+ const resolved = symbol && symbol.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
925
+ if (resolved?.declarations?.some((d) => componentDecls.has(d))) {
926
+ const p = n.parent;
927
+ const benign = ts5.isImportSpecifier(p) || ts5.isImportClause(p) || ts5.isExportSpecifier(p) || ts5.isNamespaceImport(p) || isJsxTagPosition(n) || isInTypePosition(n);
928
+ if (!benign) misuse = n;
929
+ if (isJsxTagPosition(n)) recordActionProps(n);
930
+ }
931
+ return;
932
+ }
933
+ ts5.forEachChild(n, visit);
934
+ };
935
+ visit(sf);
936
+ if (misuse) {
937
+ const { line } = lineCol(sf, misuse.getStart(sf));
938
+ return skip(
939
+ "unsupported-pattern",
940
+ `${misuse.text} is used as a value in ${rel2}:${line}; only <${misuse.text} /> usage can be verified`
941
+ );
942
+ }
943
+ }
944
+ }
945
+ const serverActionProps = /* @__PURE__ */ new Set();
946
+ if (!sawSpread) {
947
+ for (const [name, all] of actionUsage) if (all) serverActionProps.add(name);
948
+ }
949
+ return ok({ importers: [...importers], serverActionProps });
950
+ }
951
+ function isServerActionReference(expr, checker) {
952
+ let e = expr;
953
+ while (ts5.isParenthesizedExpression(e)) e = e.expression;
954
+ if (!ts5.isIdentifier(e) && !ts5.isPropertyAccessExpression(e)) return false;
955
+ const symbol = checker.getSymbolAtLocation(e);
956
+ const resolved = symbol && symbol.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
957
+ const decl = resolved?.valueDeclaration ?? resolved?.declarations?.[0];
958
+ if (!decl) return false;
959
+ const sf = decl.getSourceFile();
960
+ if (sf.isDeclarationFile) return false;
961
+ if (getModuleDirective(sf).directive === "use server") return true;
962
+ if (ts5.isFunctionDeclaration(decl) || ts5.isFunctionExpression(decl) || ts5.isArrowFunction(decl)) {
963
+ return hasUseServerBody(decl);
964
+ }
965
+ if (ts5.isVariableDeclaration(decl) && decl.initializer) {
966
+ const init = decl.initializer;
967
+ if (ts5.isArrowFunction(init) || ts5.isFunctionExpression(init)) return hasUseServerBody(init);
968
+ }
969
+ return false;
970
+ }
971
+
972
+ // src/analysis/islands.ts
973
+ import { ts as ts8 } from "ts-morph";
974
+
975
+ // src/analysis/mongoose.ts
976
+ import { ts as ts7 } from "ts-morph";
977
+
978
+ // src/analysis/serializable.ts
979
+ import { ts as ts6 } from "ts-morph";
980
+ var OK = { ok: true };
981
+ var fail = (reason) => ({ ok: false, reason });
982
+ var BUILTIN_SERIALIZABLE = /* @__PURE__ */ new Set([
983
+ "Date",
984
+ "ArrayBuffer",
985
+ "Int8Array",
986
+ "Uint8Array",
987
+ "Uint8ClampedArray",
988
+ "Int16Array",
989
+ "Uint16Array",
990
+ "Int32Array",
991
+ "Uint32Array",
992
+ "Float16Array",
993
+ "Float32Array",
994
+ "Float64Array",
995
+ "BigInt64Array",
996
+ "BigUint64Array",
997
+ "FormData"
998
+ ]);
999
+ var BUILTIN_CONTAINERS = /* @__PURE__ */ new Set([
1000
+ "Map",
1001
+ "ReadonlyMap",
1002
+ "Set",
1003
+ "ReadonlySet",
1004
+ "Promise",
1005
+ "PromiseLike",
1006
+ "Array",
1007
+ "ReadonlyArray",
1008
+ "Iterable"
1009
+ ]);
1010
+ var REACT_NODE_TYPES = /* @__PURE__ */ new Set([
1011
+ "ReactElement",
1012
+ "ReactPortal",
1013
+ "Element",
1014
+ "ReactNode",
1015
+ "AwaitedReactNode",
1016
+ "JSXElementConstructor"
1017
+ ]);
1018
+ function isFromDeclarationFile(symbol, test) {
1019
+ const decls = symbol?.declarations ?? [];
1020
+ return decls.some((d) => test(d.getSourceFile().fileName)) && decls.every((d) => d.getSourceFile().isDeclarationFile);
1021
+ }
1022
+ var isLibFile = (f) => /\/lib\.[a-z0-9.]*\.d\.ts$/.test(f) || /\/@types\/node\//.test(f) || /\/undici-types\//.test(f);
1023
+ var isReactTypes = (f) => /\/@types\/react\//.test(f) || /\/react\/[^/]*\.d\.ts$/.test(f);
1024
+ function checkSerializable(checker, type, options = {}) {
1025
+ const seen = /* @__PURE__ */ new Set();
1026
+ const visit = (t, depth) => {
1027
+ if (depth > 40) return fail("is too deeply nested to verify");
1028
+ const flags = t.flags;
1029
+ const text = () => checker.typeToString(t);
1030
+ if (flags & (ts6.TypeFlags.Any | ts6.TypeFlags.Unknown)) {
1031
+ const unresolved = "intrinsicName" in t && t.intrinsicName === "error";
1032
+ return fail(
1033
+ unresolved ? `has the type \`${text()}\`, which couldn't be resolved (missing import or generated types?)` : `is \`${text()}\`, which can't be proven serializable`
1034
+ );
1035
+ }
1036
+ if (flags & ts6.TypeFlags.Never) return fail("is `never`");
1037
+ if (flags & (ts6.TypeFlags.StringLike | ts6.TypeFlags.NumberLike | ts6.TypeFlags.BooleanLike | ts6.TypeFlags.BigIntLike | ts6.TypeFlags.Null | ts6.TypeFlags.Undefined | ts6.TypeFlags.Void | ts6.TypeFlags.EnumLike)) {
1038
+ return OK;
1039
+ }
1040
+ if (options.primitivesOnly)
1041
+ return fail(`has the non-primitive type \`${text()}\` in an untyped file`);
1042
+ if (t.aliasSymbol && REACT_NODE_TYPES.has(t.aliasSymbol.getName()) && isFromDeclarationFile(t.aliasSymbol, isReactTypes)) {
1043
+ return OK;
1044
+ }
1045
+ if (flags & ts6.TypeFlags.UniqueESSymbol && isSymbolForDeclaration(t.getSymbol())) return OK;
1046
+ if (flags & ts6.TypeFlags.ESSymbolLike)
1047
+ return fail("is a symbol (only Symbol.for symbols are serializable)");
1048
+ if (t.isUnion()) {
1049
+ for (const member of t.types) {
1050
+ const v = visit(member, depth + 1);
1051
+ if (!v.ok) return v;
1052
+ }
1053
+ return OK;
1054
+ }
1055
+ if (t.isIntersection()) {
1056
+ for (const member of t.types) {
1057
+ const v = visit(member, depth + 1);
1058
+ if (!v.ok) return v;
1059
+ }
1060
+ return OK;
1061
+ }
1062
+ if (flags & ts6.TypeFlags.TypeParameter) {
1063
+ const constraint = checker.getBaseConstraintOfType(t);
1064
+ return constraint && constraint !== t ? visit(constraint, depth + 1) : fail(`is the unconstrained type parameter \`${text()}\``);
1065
+ }
1066
+ if (!(flags & ts6.TypeFlags.Object) && !(flags & ts6.TypeFlags.NonPrimitive))
1067
+ return fail(`has the type \`${text()}\``);
1068
+ if (flags & ts6.TypeFlags.NonPrimitive)
1069
+ return fail("is `object`, which can't be proven serializable");
1070
+ if (seen.has(t)) return OK;
1071
+ seen.add(t);
1072
+ const symbol = t.getSymbol();
1073
+ const aliasSymbol = t.aliasSymbol;
1074
+ const name = symbol?.getName();
1075
+ if (aliasSymbol && REACT_NODE_TYPES.has(aliasSymbol.getName()) && isFromDeclarationFile(aliasSymbol, isReactTypes) || name && REACT_NODE_TYPES.has(name) && isFromDeclarationFile(symbol, isReactTypes)) {
1076
+ return OK;
1077
+ }
1078
+ if (name && BUILTIN_SERIALIZABLE.has(name) && isFromDeclarationFile(symbol, isLibFile))
1079
+ return OK;
1080
+ if (checker.isArrayType(t) || checker.isTupleType(t)) {
1081
+ for (const arg of checker.getTypeArguments(t)) {
1082
+ const v = visit(arg, depth + 1);
1083
+ if (!v.ok) return fail(`contains an element that ${v.reason}`);
1084
+ }
1085
+ return OK;
1086
+ }
1087
+ if (name && BUILTIN_CONTAINERS.has(name) && isFromDeclarationFile(symbol, isLibFile)) {
1088
+ const args = t.target ? checker.getTypeArguments(t) : [];
1089
+ for (const arg of args) {
1090
+ const v = visit(arg, depth + 1);
1091
+ if (!v.ok) return fail(`contains a ${name} value that ${v.reason}`);
1092
+ }
1093
+ return OK;
1094
+ }
1095
+ if (t.getCallSignatures().length > 0 || t.getConstructSignatures().length > 0) {
1096
+ return fail(`is a function (\`${text()}\`)`);
1097
+ }
1098
+ if (symbol && symbol.flags & ts6.SymbolFlags.Class)
1099
+ return fail(`is an instance of the class ${name ?? text()}`);
1100
+ if (symbol && isFromDeclarationFile(symbol, isLibFile) && !(symbol.flags & ts6.SymbolFlags.TypeLiteral) && name !== "__object") {
1101
+ return fail(`is a built-in \`${name}\` object, which React can't serialize`);
1102
+ }
1103
+ if (symbol && symbol.declarations?.some((d) => ts6.isClassLike(d)))
1104
+ return fail(`is an instance of the class ${name}`);
1105
+ for (const prop of checker.getPropertiesOfType(t)) {
1106
+ if (prop.flags & ts6.SymbolFlags.Method) return fail(`has the method \`${prop.getName()}\``);
1107
+ const decl = prop.valueDeclaration ?? prop.declarations?.[0];
1108
+ const propType = decl ? checker.getTypeOfSymbolAtLocation(prop, decl) : checker.getTypeOfSymbol(prop);
1109
+ const v = visit(propType, depth + 1);
1110
+ if (!v.ok) return fail(`has a property \`${prop.getName()}\` that ${v.reason}`);
1111
+ }
1112
+ for (const info of checker.getIndexInfosOfType(t)) {
1113
+ const v = visit(info.type, depth + 1);
1114
+ if (!v.ok) return fail(`has index values that ${v.reason}`);
1115
+ }
1116
+ return OK;
1117
+ };
1118
+ return visit(type, 0);
1119
+ }
1120
+ function isSymbolForDeclaration(symbol) {
1121
+ const decl = symbol?.valueDeclaration;
1122
+ if (!decl || !ts6.isVariableDeclaration(decl) || !decl.initializer) return false;
1123
+ const init = decl.initializer;
1124
+ return ts6.isCallExpression(init) && ts6.isPropertyAccessExpression(init.expression) && ts6.isIdentifier(init.expression.expression) && init.expression.expression.text === "Symbol" && init.expression.name.text === "for";
1125
+ }
1126
+ function isCallableType(type) {
1127
+ if (type.isUnion()) return type.types.some(isCallableType);
1128
+ return type.getCallSignatures().length > 0;
1129
+ }
1130
+
1131
+ // src/analysis/mongoose.ts
1132
+ var MONGO_TYPES_RE = /\/node_modules\/(?:\.pnpm\/[^/]+\/node_modules\/)?(bson|mongodb|mongoose)\//;
1133
+ function declaredInMongoPackage(symbol) {
1134
+ return (symbol?.declarations ?? []).some((d) => MONGO_TYPES_RE.test(d.getSourceFile().fileName));
1135
+ }
1136
+ function isObjectIdType(type) {
1137
+ const symbol = type.getSymbol() ?? type.aliasSymbol;
1138
+ return symbol?.getName() === "ObjectId" && declaredInMongoPackage(symbol);
1139
+ }
1140
+ function isMongooseLike(checker, type) {
1141
+ const seen = /* @__PURE__ */ new Set();
1142
+ const visit = (t, depth) => {
1143
+ if (depth > 6 || seen.has(t)) return false;
1144
+ seen.add(t);
1145
+ if (isObjectIdType(t)) return true;
1146
+ if (t.isUnionOrIntersection()) return t.types.some((m) => visit(m, depth + 1));
1147
+ if (!(t.flags & ts7.TypeFlags.Object)) return false;
1148
+ const isDoc = t.getProperty("$isNew") !== void 0 && declaredInMongoPackage(t.getProperty("$isNew"));
1149
+ if (isDoc) return true;
1150
+ if (checker.isArrayType(t))
1151
+ return checker.getTypeArguments(t).some((a) => visit(a, depth + 1));
1152
+ for (const prop of checker.getPropertiesOfType(t).slice(0, 200)) {
1153
+ const decl = prop.valueDeclaration ?? prop.declarations?.[0];
1154
+ if (!decl || MONGO_TYPES_RE.test(decl.getSourceFile().fileName)) continue;
1155
+ if (visit(checker.getTypeOfSymbolAtLocation(prop, decl), depth + 1)) return true;
1156
+ }
1157
+ return false;
1158
+ };
1159
+ return visit(type, 0);
1160
+ }
1161
+ function stripNullish(type) {
1162
+ if (!type.isUnion()) return { type, nullable: false };
1163
+ const rest = type.types.filter((t) => !(t.flags & (ts7.TypeFlags.Null | ts7.TypeFlags.Undefined)));
1164
+ if (rest.length === type.types.length || rest.length !== 1 || !rest[0])
1165
+ return { type, nullable: false };
1166
+ return { type: rest[0], nullable: true };
1167
+ }
1168
+ function pickMongooseFields(checker, name, refs, printType) {
1169
+ const root = { kind: "branch", fields: /* @__PURE__ */ new Map() };
1170
+ for (const ref of refs) {
1171
+ let node = ref;
1172
+ const path10 = [];
1173
+ let leafType;
1174
+ for (; ; ) {
1175
+ const parent = node.parent;
1176
+ if (!ts7.isPropertyAccessExpression(parent) || parent.expression !== node) break;
1177
+ if (parent.questionDotToken)
1178
+ return { ok: false, reason: `uses optional chaining on \`${name}\`` };
1179
+ path10.push(parent.name.text);
1180
+ node = parent;
1181
+ const type = checker.getTypeAtLocation(node);
1182
+ const { type: inner2 } = stripNullish(type);
1183
+ if (isObjectIdType(inner2) || checkSerializable(checker, type).ok) {
1184
+ leafType = type;
1185
+ break;
1186
+ }
1187
+ }
1188
+ if (path10.length === 0) {
1189
+ return {
1190
+ ok: false,
1191
+ reason: `uses \`${name}\` as a whole (spread, passed on, or iterated), so its fields can't be picked`
1192
+ };
1193
+ }
1194
+ if (!leafType) {
1195
+ return {
1196
+ ok: false,
1197
+ reason: `reads \`${name}.${path10.join(".")}\`, which isn't serializable even after picking fields`
1198
+ };
1199
+ }
1200
+ const { type: inner, nullable } = stripNullish(leafType);
1201
+ const objectId = isObjectIdType(inner);
1202
+ if (objectId) {
1203
+ const after = node.parent;
1204
+ if (ts7.isPropertyAccessExpression(after) && after.expression === node && after.name.text !== "toString") {
1205
+ return {
1206
+ ok: false,
1207
+ reason: `calls ObjectId.${after.name.text}() on \`${name}.${path10.join(".")}\`, which a string doesn't have`
1208
+ };
1209
+ }
1210
+ }
1211
+ let branch = root;
1212
+ for (const [i, key] of path10.entries()) {
1213
+ const last = i === path10.length - 1;
1214
+ const existing = branch.fields.get(key);
1215
+ if (last) {
1216
+ if (existing?.kind === "branch")
1217
+ return {
1218
+ ok: false,
1219
+ reason: `reads \`${name}.${path10.join(".")}\` both as a value and as an object`
1220
+ };
1221
+ if (!existing)
1222
+ branch.fields.set(key, {
1223
+ kind: "leaf",
1224
+ source: `${name}.${path10.join(".")}`,
1225
+ type: leafType,
1226
+ objectId,
1227
+ nullable
1228
+ });
1229
+ } else {
1230
+ if (existing?.kind === "leaf")
1231
+ return {
1232
+ ok: false,
1233
+ reason: `reads \`${name}.${path10.slice(0, i + 1).join(".")}\` both as a value and as an object`
1234
+ };
1235
+ if (!existing) {
1236
+ const next = { kind: "branch", fields: /* @__PURE__ */ new Map() };
1237
+ branch.fields.set(key, next);
1238
+ branch = next;
1239
+ } else {
1240
+ branch = existing;
1241
+ }
1242
+ }
1243
+ }
1244
+ }
1245
+ const render = (node) => {
1246
+ const exprs = [];
1247
+ const types = [];
1248
+ for (const [key, child] of node.fields) {
1249
+ const prop = /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key);
1250
+ if (child.kind === "branch") {
1251
+ const inner = render(child);
1252
+ exprs.push(`${prop}: ${inner.expr}`);
1253
+ types.push(`${prop}: ${inner.type}`);
1254
+ } else if (child.objectId) {
1255
+ exprs.push(
1256
+ child.nullable ? `${prop}: ${child.source} == null ? ${child.source} : String(${child.source})` : `${prop}: String(${child.source})`
1257
+ );
1258
+ types.push(
1259
+ `${prop}: ${child.nullable ? `string | ${printNullish(child.type)}` : "string"}`
1260
+ );
1261
+ } else {
1262
+ exprs.push(`${prop}: ${child.source}`);
1263
+ types.push(`${prop}: ${printType(child.type)}`);
1264
+ }
1265
+ }
1266
+ return { expr: `{ ${exprs.join(", ")} }`, type: `{ ${types.join("; ")} }` };
1267
+ };
1268
+ const rendered = render(root);
1269
+ return { ok: true, value: { serverExpr: rendered.expr, typeText: rendered.type } };
1270
+ }
1271
+ function printNullish(type) {
1272
+ if (!type.isUnion()) return "null";
1273
+ const parts = [];
1274
+ if (type.types.some((t) => t.flags & ts7.TypeFlags.Null)) parts.push("null");
1275
+ if (type.types.some((t) => t.flags & ts7.TypeFlags.Undefined)) parts.push("undefined");
1276
+ return parts.join(" | ") || "null";
1277
+ }
1278
+
1279
+ // src/analysis/islands.ts
1280
+ var REACT_SHARED_CONTEXT_HOOKS = ["useContext", "use"];
1281
+ var UnionFind = class {
1282
+ parent = /* @__PURE__ */ new Map();
1283
+ find(x) {
1284
+ let p = this.parent.get(x) ?? x;
1285
+ if (p !== x) {
1286
+ p = this.find(p);
1287
+ this.parent.set(x, p);
1288
+ }
1289
+ return p;
1290
+ }
1291
+ union(a, b) {
1292
+ const ra = this.find(a);
1293
+ const rb = this.find(b);
1294
+ if (ra !== rb) this.parent.set(rb, ra);
1295
+ }
1296
+ };
1297
+ function lowestCommonAncestor(nodes) {
1298
+ const [first, ...rest] = nodes;
1299
+ if (!first) return void 0;
1300
+ let lca = first;
1301
+ for (const node of rest) {
1302
+ if (!lca) return void 0;
1303
+ if (node.rootIndex !== lca.rootIndex) return void 0;
1304
+ let a = lca;
1305
+ let b = node;
1306
+ while (a && b && a.depth > b.depth) a = a.parent;
1307
+ while (a && b && b.depth > a.depth) b = b.parent;
1308
+ while (a && b && a !== b) {
1309
+ a = a.parent;
1310
+ b = b.parent;
1311
+ }
1312
+ lca = a;
1313
+ }
1314
+ return lca;
1315
+ }
1316
+ function isAncestorOrSelf(ancestor, node) {
1317
+ let current = node;
1318
+ while (current) {
1319
+ if (current === ancestor) return true;
1320
+ current = current.parent;
1321
+ }
1322
+ return false;
1323
+ }
1324
+ function pascal(text) {
1325
+ return text.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join("");
1326
+ }
1327
+ function islandBaseName(componentName, rootTag) {
1328
+ return `${componentName}${pascal(rootTag) || "Island"}`;
1329
+ }
1330
+ function planComponent(ctx, facts, shape, component, options = {}) {
1331
+ const { checker, sf } = ctx;
1332
+ const fn = component.fn;
1333
+ const body = fn.body;
1334
+ if (!body) return skip("unsupported-pattern", `${component.name} has no body`, locOf(ctx, fn));
1335
+ if (fn.parameters.length > 1) {
1336
+ return skip(
1337
+ "unsupported-pattern",
1338
+ `${component.name} takes more than one parameter`,
1339
+ locOf(ctx, fn)
1340
+ );
1341
+ }
1342
+ const paramSymbols = /* @__PURE__ */ new Set();
1343
+ const serverActionParams = /* @__PURE__ */ new Set();
1344
+ for (const param of fn.parameters) {
1345
+ for (const id of bindingIdentifiers(param.name)) {
1346
+ const symbol = checker.getSymbolAtLocation(id);
1347
+ if (symbol) paramSymbols.add(symbol);
1348
+ }
1349
+ if (ts8.isObjectBindingPattern(param.name)) {
1350
+ for (const element of param.name.elements) {
1351
+ const propName = element.propertyName ?? element.name;
1352
+ if (!ts8.isIdentifier(propName) || !ts8.isIdentifier(element.name)) continue;
1353
+ if (!options.serverActionProps?.has(propName.text)) continue;
1354
+ const symbol = checker.getSymbolAtLocation(element.name);
1355
+ if (symbol) serverActionParams.add(symbol);
1356
+ }
1357
+ }
1358
+ }
1359
+ const units = [];
1360
+ const renderRoots = [];
1361
+ const earlyReturnConditions = [];
1362
+ const symbolToUnit = /* @__PURE__ */ new Map();
1363
+ const newUnit = (node, kind) => {
1364
+ const unit = {
1365
+ index: units.length,
1366
+ node,
1367
+ kind,
1368
+ declares: /* @__PURE__ */ new Set(),
1369
+ localDeps: /* @__PURE__ */ new Set(),
1370
+ moduleDeps: /* @__PURE__ */ new Set(),
1371
+ markers: [],
1372
+ client: false
1373
+ };
1374
+ units.push(unit);
1375
+ return unit;
1376
+ };
1377
+ if (!ts8.isBlock(body)) {
1378
+ renderRoots.push(stripParens(body));
1379
+ } else {
1380
+ const statements = body.statements;
1381
+ let sawReturn = false;
1382
+ for (let i = 0; i < statements.length; i++) {
1383
+ const statement = statements[i];
1384
+ if (ts8.isEmptyStatement(statement)) continue;
1385
+ if (sawReturn)
1386
+ return skip(
1387
+ "unsupported-pattern",
1388
+ "has statements after the final return",
1389
+ locOf(ctx, statement)
1390
+ );
1391
+ if (ts8.isReturnStatement(statement)) {
1392
+ if (!statement.expression)
1393
+ return skip("unsupported-pattern", "returns nothing", locOf(ctx, statement));
1394
+ renderRoots.push(stripParens(statement.expression));
1395
+ sawReturn = true;
1396
+ continue;
1397
+ }
1398
+ if (ts8.isIfStatement(statement)) {
1399
+ const then = statement.thenStatement;
1400
+ const ret = ts8.isReturnStatement(then) ? then : ts8.isBlock(then) && then.statements.length === 1 && ts8.isReturnStatement(then.statements[0]) ? then.statements[0] : void 0;
1401
+ if (!ret || statement.elseStatement || !ret.expression) {
1402
+ return skip(
1403
+ "unsupported-pattern",
1404
+ "has an `if` statement other than a simple early return",
1405
+ locOf(ctx, statement)
1406
+ );
1407
+ }
1408
+ const unit = newUnit(statement, "early-return");
1409
+ unit.returned = stripParens(ret.expression);
1410
+ renderRoots.push(unit.returned);
1411
+ earlyReturnConditions.push(statement.expression);
1412
+ continue;
1413
+ }
1414
+ if (ts8.isVariableStatement(statement)) {
1415
+ if (!(statement.declarationList.flags & ts8.NodeFlags.Const)) {
1416
+ return skip(
1417
+ "unsupported-pattern",
1418
+ "declares a `let`/`var` in the component body",
1419
+ locOf(ctx, statement)
1420
+ );
1421
+ }
1422
+ const unit = newUnit(statement, "var");
1423
+ for (const decl of statement.declarationList.declarations) {
1424
+ for (const id of bindingIdentifiers(decl.name)) {
1425
+ const symbol = checker.getSymbolAtLocation(id);
1426
+ if (symbol) {
1427
+ unit.declares.add(symbol);
1428
+ symbolToUnit.set(symbol, unit);
1429
+ }
1430
+ }
1431
+ }
1432
+ continue;
1433
+ }
1434
+ if (ts8.isFunctionDeclaration(statement) && statement.name) {
1435
+ const unit = newUnit(statement, "function");
1436
+ const symbol = checker.getSymbolAtLocation(statement.name);
1437
+ if (symbol) {
1438
+ unit.declares.add(symbol);
1439
+ symbolToUnit.set(symbol, unit);
1440
+ }
1441
+ continue;
1442
+ }
1443
+ if (ts8.isExpressionStatement(statement)) {
1444
+ const expr = stripParens(statement.expression);
1445
+ if (ts8.isCallExpression(expr) && facts.isHookCall(expr)) {
1446
+ newUnit(statement, "hook-call");
1447
+ continue;
1448
+ }
1449
+ return skip(
1450
+ "unsupported-pattern",
1451
+ "has a statement with side effects in the component body (only declarations, hook calls and early returns are supported)",
1452
+ locOf(ctx, statement)
1453
+ );
1454
+ }
1455
+ return skip(
1456
+ "unsupported-pattern",
1457
+ `has a \`${ts8.SyntaxKind[statement.kind]}\` in the component body`,
1458
+ locOf(ctx, statement)
1459
+ );
1460
+ }
1461
+ if (renderRoots.length === 0)
1462
+ return skip("unsupported-pattern", `${component.name} never returns JSX`, locOf(ctx, fn));
1463
+ }
1464
+ const moduleDeclByStatement = /* @__PURE__ */ new Map();
1465
+ for (const decl of shape.moduleDecls) moduleDeclByStatement.set(decl.statement, decl);
1466
+ const componentStatements = new Set(shape.components.map((c) => c.statement));
1467
+ const topLevelStatement = (node) => {
1468
+ let current = node;
1469
+ while (current && current.parent !== sf) current = current.parent;
1470
+ return current;
1471
+ };
1472
+ const originOf = (id) => {
1473
+ const info = facts.classifyRef(id);
1474
+ switch (info.kind) {
1475
+ case "import":
1476
+ return { kind: "import", binding: info.binding, usage: info.usage };
1477
+ case "browser-global":
1478
+ return { kind: "browser-global", name: info.name };
1479
+ case "global":
1480
+ return { kind: "global" };
1481
+ case "unknown":
1482
+ return { kind: "unknown" };
1483
+ case "file": {
1484
+ const { symbol, declaration } = info;
1485
+ if (paramSymbols.has(symbol)) return { kind: "param", symbol };
1486
+ const unit = symbolToUnit.get(symbol);
1487
+ if (unit) return { kind: "local", symbol, unit };
1488
+ if (containsRange(fn, declaration)) return { kind: "inner", symbol, declaration };
1489
+ const top = topLevelStatement(declaration);
1490
+ if (top && componentStatements.has(top)) {
1491
+ const target = shape.components.find((c) => c.statement === top);
1492
+ if (target && componentDeclarationNode(target) === declaration)
1493
+ return { kind: "component", symbol, component: target };
1494
+ return { kind: "inner", symbol, declaration };
1495
+ }
1496
+ const decl = top ? moduleDeclByStatement.get(top) : void 0;
1497
+ if (decl) return { kind: "module", symbol, decl };
1498
+ return { kind: "unknown" };
1499
+ }
1500
+ }
1501
+ };
1502
+ const clientModuleDecls = /* @__PURE__ */ new Set();
1503
+ const moduleDeclDeps = /* @__PURE__ */ new Map();
1504
+ for (const decl of shape.moduleDecls) {
1505
+ if (decl.kind === "type") continue;
1506
+ const deps = /* @__PURE__ */ new Set();
1507
+ forEachValueRef(decl.statement, (id) => {
1508
+ const origin = originOf(id);
1509
+ if (origin.kind === "module" && origin.decl !== decl) deps.add(origin.decl);
1510
+ });
1511
+ moduleDeclDeps.set(decl, deps);
1512
+ if (facts.scanMarkers(decl.statement).length > 0) clientModuleDecls.add(decl);
1513
+ }
1514
+ for (let changed = true; changed; ) {
1515
+ changed = false;
1516
+ for (const [decl, deps] of moduleDeclDeps) {
1517
+ if (clientModuleDecls.has(decl)) continue;
1518
+ if ([...deps].some((d) => clientModuleDecls.has(d))) {
1519
+ clientModuleDecls.add(decl);
1520
+ changed = true;
1521
+ }
1522
+ }
1523
+ }
1524
+ let seenEarlyReturn = false;
1525
+ for (const unit of units) {
1526
+ if (unit.kind === "early-return") seenEarlyReturn = true;
1527
+ const scanned = unit.kind === "early-return" ? unit.node.expression : unit.node;
1528
+ unit.markers = facts.scanMarkers(scanned);
1529
+ forEachValueRef(scanned, (id) => {
1530
+ const origin = originOf(id);
1531
+ if (origin.kind === "local" && origin.unit !== unit) unit.localDeps.add(origin.unit);
1532
+ if (origin.kind === "module") unit.moduleDeps.add(origin.decl);
1533
+ if (origin.kind === "component")
1534
+ unit.markers.push({
1535
+ kind: "client-import",
1536
+ node: id,
1537
+ detail: origin.component === component ? "recursive component reference" : `component ${id.text} used as a value`
1538
+ });
1539
+ });
1540
+ for (const marker of unit.markers) {
1541
+ if (marker.kind !== "hook") continue;
1542
+ if (unit.kind === "early-return") {
1543
+ return skip(
1544
+ "conditional-hooks-or-returns",
1545
+ `calls ${marker.detail} inside an early-return condition`,
1546
+ locOf(ctx, marker.node)
1547
+ );
1548
+ }
1549
+ if (seenEarlyReturn) {
1550
+ return skip(
1551
+ "conditional-hooks-or-returns",
1552
+ `calls ${marker.detail} after an early return`,
1553
+ locOf(ctx, marker.node)
1554
+ );
1555
+ }
1556
+ const conditional = conditionalAncestor(marker.node, unit.node);
1557
+ if (conditional) {
1558
+ return skip(
1559
+ "conditional-hooks-or-returns",
1560
+ `calls ${marker.detail} ${conditional}`,
1561
+ locOf(ctx, marker.node)
1562
+ );
1563
+ }
1564
+ }
1565
+ }
1566
+ for (let changed = true; changed; ) {
1567
+ changed = false;
1568
+ for (const unit of units) {
1569
+ if (unit.client) continue;
1570
+ if (unit.markers.length > 0 || [...unit.localDeps].some((d) => d.client) || [...unit.moduleDeps].some((d) => clientModuleDecls.has(d))) {
1571
+ unit.client = true;
1572
+ changed = true;
1573
+ }
1574
+ }
1575
+ }
1576
+ for (const unit of units) {
1577
+ if (unit.kind === "early-return" && unit.client) {
1578
+ return skip(
1579
+ "conditional-hooks-or-returns",
1580
+ "returns early based on client-only state, so the server can't decide what to render",
1581
+ locOf(ctx, unit.node)
1582
+ );
1583
+ }
1584
+ }
1585
+ const jnodes = [];
1586
+ const jnodeOf = /* @__PURE__ */ new Map();
1587
+ const topJNodes = renderRoots.map(() => []);
1588
+ const build = (node, parent, rootIndex, fns) => {
1589
+ if (isJsxElementLike(node)) {
1590
+ const j = {
1591
+ id: jnodes.length,
1592
+ node,
1593
+ parent,
1594
+ children: [],
1595
+ rootIndex,
1596
+ fnBetween: fns,
1597
+ depth: parent ? parent.depth + 1 : 0
1598
+ };
1599
+ jnodes.push(j);
1600
+ jnodeOf.set(node, j);
1601
+ if (parent) parent.children.push(j);
1602
+ else topJNodes[rootIndex]?.push(j);
1603
+ const inner = (n) => build(n, j, rootIndex, []);
1604
+ if (ts8.isJsxElement(node)) {
1605
+ ts8.forEachChild(node.openingElement, inner);
1606
+ node.children.forEach(inner);
1607
+ } else if (ts8.isJsxSelfClosingElement(node)) {
1608
+ ts8.forEachChild(node, inner);
1609
+ } else {
1610
+ node.children.forEach(inner);
1611
+ }
1612
+ return;
1613
+ }
1614
+ const nextFns = ts8.isFunctionLike(node) ? [...fns, node] : fns;
1615
+ ts8.forEachChild(node, (c) => build(c, parent, rootIndex, nextFns));
1616
+ };
1617
+ renderRoots.forEach((root, i) => build(root, void 0, i, []));
1618
+ const isNestedJsx = (self) => (n) => n !== self && isJsxElementLike(n);
1619
+ for (const root of renderRoots) {
1620
+ for (const marker of facts.scanMarkers(root)) {
1621
+ if (marker.kind === "hook") {
1622
+ return skip(
1623
+ "conditional-hooks-or-returns",
1624
+ `calls ${marker.detail} inside the returned JSX`,
1625
+ locOf(ctx, marker.node)
1626
+ );
1627
+ }
1628
+ }
1629
+ }
1630
+ for (const j of jnodes) {
1631
+ if (ts8.isJsxFragment(j.node)) continue;
1632
+ const tag = ts8.isJsxElement(j.node) ? j.node.openingElement.tagName : j.node.tagName;
1633
+ if (isIntrinsicTagName(tag)) continue;
1634
+ if (ts8.isPropertyAccessExpression(tag) && (tag.name.text === "Provider" || tag.name.text === "Consumer")) {
1635
+ return skip("context-provider", `renders <${tag.getText(sf)}>`, locOf(ctx, tag));
1636
+ }
1637
+ const tagType = checker.getTypeAtLocation(tag);
1638
+ if (tagType.getProperty("Provider") && tagType.getProperty("Consumer")) {
1639
+ return skip(
1640
+ "context-provider",
1641
+ `renders the context <${tag.getText(sf)}> as a provider`,
1642
+ locOf(ctx, tag)
1643
+ );
1644
+ }
1645
+ }
1646
+ const anchors = [];
1647
+ const isServerActionRef = (expr) => {
1648
+ const e = stripParens(expr);
1649
+ if (!ts8.isIdentifier(e)) return false;
1650
+ const origin = originOf(e);
1651
+ if (origin.kind === "import") return origin.usage.cls === "server-action";
1652
+ if (origin.kind === "param") return serverActionParams.has(origin.symbol);
1653
+ if (origin.kind === "module" && origin.decl.kind === "function") {
1654
+ return ts8.isFunctionDeclaration(origin.decl.statement) && hasUseServerBody(origin.decl.statement);
1655
+ }
1656
+ return false;
1657
+ };
1658
+ for (const j of jnodes) {
1659
+ const reasons = [];
1660
+ const refs = [];
1661
+ const stop = isNestedJsx(j.node);
1662
+ forEachValueRef(j.node, (id) => refs.push(id), stop);
1663
+ let tagClass = "fragment";
1664
+ let attributes;
1665
+ if (!ts8.isJsxFragment(j.node)) {
1666
+ const tag = ts8.isJsxElement(j.node) ? j.node.openingElement.tagName : j.node.tagName;
1667
+ attributes = ts8.isJsxElement(j.node) ? j.node.openingElement.attributes : j.node.attributes;
1668
+ if (isIntrinsicTagName(tag)) tagClass = "intrinsic";
1669
+ else {
1670
+ let head = tag;
1671
+ while (ts8.isPropertyAccessExpression(head)) head = head.expression;
1672
+ const origin = ts8.isIdentifier(head) ? originOf(head) : { kind: "unknown" };
1673
+ if (origin.kind === "import") {
1674
+ tagClass = origin.usage.cls === "client-reference" ? "client-reference" : origin.usage.cls === "neutral" ? "server" : "client";
1675
+ if (tagClass === "client") reasons.push(origin.usage.reason);
1676
+ } else if (origin.kind === "param" || origin.kind === "local" || origin.kind === "inner") {
1677
+ tagClass = "server";
1678
+ } else if (origin.kind === "module") {
1679
+ tagClass = clientModuleDecls.has(origin.decl) ? "client" : "server";
1680
+ if (tagClass === "client")
1681
+ reasons.push(`<${tag.getText(sf)}> is built from client-only code`);
1682
+ } else if (origin.kind === "component") {
1683
+ if (origin.component === component) {
1684
+ return skip(
1685
+ "unsupported-pattern",
1686
+ `${component.name} renders itself recursively`,
1687
+ locOf(ctx, tag)
1688
+ );
1689
+ }
1690
+ tagClass = options.wholesale?.has(origin.component) ? "client-reference" : "server";
1691
+ } else {
1692
+ tagClass = "client";
1693
+ reasons.push(`<${tag.getText(sf)}> can't be proven server-safe`);
1694
+ }
1695
+ }
1696
+ if (tagClass === "intrinsic" && ts8.isIdentifier(tag) && tag.text === "style" && attributes.properties.some((p) => ts8.isJsxAttribute(p) && p.name.getText(sf) === "jsx")) {
1697
+ return skip("css-in-js-runtime", "uses <style jsx> (styled-jsx)", locOf(ctx, tag));
1698
+ }
1699
+ for (const prop of attributes.properties) {
1700
+ if (ts8.isJsxSpreadAttribute(prop)) {
1701
+ const type = checker.getTypeAtLocation(prop.expression);
1702
+ const risky = checker.getPropertiesOfType(type).find(
1703
+ (p) => /^on[A-Z]/.test(p.getName()) || p.getName() === "ref" || isCallableType(checker.getTypeOfSymbolAtLocation(p, prop))
1704
+ );
1705
+ if (risky) reasons.push(`spreads a value that can carry \`${risky.getName()}\``);
1706
+ else if (tagClass === "client-reference" && !checkSerializable(checker, type, { primitivesOnly: ctx.isJs }).ok) {
1707
+ reasons.push("spreads non-serializable props into a Client Component");
1708
+ }
1709
+ continue;
1710
+ }
1711
+ const name = prop.name.getText(sf);
1712
+ if (/^on[A-Z]/.test(name) || name === "ref") {
1713
+ reasons.push(`has ${name}`);
1714
+ continue;
1715
+ }
1716
+ const init = prop.initializer;
1717
+ if (init && ts8.isJsxExpression(init) && init.expression) {
1718
+ const expr = init.expression;
1719
+ const type = checker.getTypeAtLocation(expr);
1720
+ if (isCallableType(type)) {
1721
+ const unwrapped = stripParens(expr);
1722
+ if ((ts8.isArrowFunction(unwrapped) || ts8.isFunctionExpression(unwrapped)) && containsJsxNode(unwrapped)) {
1723
+ return skip(
1724
+ "render-prop",
1725
+ `passes a render function to \`${name}\``,
1726
+ locOf(ctx, prop)
1727
+ );
1728
+ }
1729
+ const actionOk = isServerActionRef(expr) && (tagClass !== "intrinsic" || name === "action" || name === "formAction");
1730
+ if (!actionOk) reasons.push(`passes a function to \`${name}\``);
1731
+ } else if (tagClass === "client-reference") {
1732
+ const verdict = checkSerializable(checker, type, { primitivesOnly: ctx.isJs });
1733
+ if (!verdict.ok)
1734
+ reasons.push(`passes a non-serializable \`${name}\` to a Client Component`);
1735
+ }
1736
+ }
1737
+ }
1738
+ }
1739
+ const children = ts8.isJsxSelfClosingElement(j.node) ? [] : j.node.children;
1740
+ for (const child of children) {
1741
+ if (!ts8.isJsxExpression(child) || !child.expression) continue;
1742
+ const type = checker.getTypeAtLocation(child.expression);
1743
+ if (isCallableType(type))
1744
+ return skip("render-prop", "uses a function as a child (render prop)", locOf(ctx, child));
1745
+ if (tagClass === "client-reference") {
1746
+ const verdict = checkSerializable(checker, type, { primitivesOnly: ctx.isJs });
1747
+ if (!verdict.ok) reasons.push("passes non-serializable children to a Client Component");
1748
+ }
1749
+ }
1750
+ for (const id of refs) {
1751
+ const origin = originOf(id);
1752
+ if (origin.kind === "local" && origin.unit.client) reasons.push(`reads ${id.text}`);
1753
+ else if (origin.kind === "module" && clientModuleDecls.has(origin.decl))
1754
+ reasons.push(`uses ${id.text}`);
1755
+ else if (origin.kind === "import" && origin.usage.cls === "client-only" && !isTagRef(id))
1756
+ reasons.push(origin.usage.reason);
1757
+ else if (origin.kind === "browser-global") reasons.push(`uses ${origin.name}`);
1758
+ else if (origin.kind === "unknown" && !isTagRef(id))
1759
+ reasons.push(`uses ${id.text}, which can't be resolved`);
1760
+ }
1761
+ for (const marker of facts.scanMarkers(j.node, stop)) {
1762
+ if (marker.kind === "nondeterministic") reasons.push(`calls ${marker.detail}`);
1763
+ }
1764
+ if (reasons.length > 0) anchors.push({ j, reasons, refs });
1765
+ }
1766
+ for (const root of renderRoots) {
1767
+ if (isJsxElementLike(root)) continue;
1768
+ let problem;
1769
+ forEachValueRef(
1770
+ root,
1771
+ (id) => {
1772
+ if (problem) return;
1773
+ const origin = originOf(id);
1774
+ if (origin.kind === "local" && origin.unit.client || origin.kind === "browser-global")
1775
+ problem = id;
1776
+ },
1777
+ (n) => isJsxElementLike(n)
1778
+ );
1779
+ if (problem) {
1780
+ return skip(
1781
+ "state-spans-component",
1782
+ `the returned expression itself reads client-only \`${problem.text}\``,
1783
+ locOf(ctx, problem)
1784
+ );
1785
+ }
1786
+ }
1787
+ const closureOf = (refs) => {
1788
+ const out = /* @__PURE__ */ new Set();
1789
+ const stack = [];
1790
+ for (const id of refs) {
1791
+ const origin = originOf(id);
1792
+ if (origin.kind === "local" && !out.has(origin.unit)) {
1793
+ out.add(origin.unit);
1794
+ stack.push(origin.unit);
1795
+ }
1796
+ }
1797
+ while (stack.length > 0) {
1798
+ const unit = stack.pop();
1799
+ for (const dep of unit.localDeps) {
1800
+ if (!out.has(dep)) {
1801
+ out.add(dep);
1802
+ stack.push(dep);
1803
+ }
1804
+ }
1805
+ }
1806
+ return out;
1807
+ };
1808
+ const clientUnits = units.filter((u) => u.client);
1809
+ const totalJsx = jnodes.length;
1810
+ if (anchors.length === 0) {
1811
+ if (clientUnits.length === 0 && clientModuleDecls.size === 0) {
1812
+ return skip(
1813
+ "no-islands",
1814
+ `${component.name} uses no client-only features; removing "use client" may be enough (the tool never removes it automatically)`
1815
+ );
1816
+ }
1817
+ const first = clientUnits[0];
1818
+ return skip(
1819
+ "unsupported-pattern",
1820
+ first ? `client-only code at line ${locOf(ctx, first.node).line} isn't connected to any rendered element` : "client-only helpers aren't connected to any rendered element",
1821
+ first ? locOf(ctx, first.node) : void 0
1822
+ );
1823
+ }
1824
+ const uf = new UnionFind();
1825
+ const unitOwner = /* @__PURE__ */ new Map();
1826
+ anchors.forEach((anchor, i) => {
1827
+ for (const unit of closureOf(anchor.refs)) {
1828
+ if (!unit.client) continue;
1829
+ const owner = unitOwner.get(unit);
1830
+ if (owner === void 0) unitOwner.set(unit, i);
1831
+ else uf.union(owner, i);
1832
+ }
1833
+ });
1834
+ const groupsByRoot = /* @__PURE__ */ new Map();
1835
+ anchors.forEach((anchor, i) => {
1836
+ const key = uf.find(i);
1837
+ const list = groupsByRoot.get(key) ?? [];
1838
+ list.push(anchor);
1839
+ groupsByRoot.set(key, list);
1840
+ });
1841
+ const mappedRoot = (root, perItemSafe) => {
1842
+ let current = root;
1843
+ while (current) {
1844
+ const opaque = current.fnBetween.find((f) => !mapCallOf(f));
1845
+ if (opaque) {
1846
+ return skip(
1847
+ "render-prop",
1848
+ "an interactive element sits inside a callback other than .map()",
1849
+ locOf(ctx, current.node)
1850
+ );
1851
+ }
1852
+ if (current.fnBetween.some((f) => mapCallOf(f))) {
1853
+ if (perItemSafe) return ok(current);
1854
+ const container = current.parent;
1855
+ if (!container) {
1856
+ return skip(
1857
+ "state-spans-component",
1858
+ "state shared by every list item is used by the list itself, which is the returned element",
1859
+ locOf(ctx, current.node)
1860
+ );
1861
+ }
1862
+ root = container;
1863
+ }
1864
+ current = current.parent;
1865
+ }
1866
+ return ok(root);
1867
+ };
1868
+ const sharedContextHooks = /* @__PURE__ */ new Set([
1869
+ ...REACT_SHARED_CONTEXT_HOOKS,
1870
+ ...ctx.framework.sharedContextHooks
1871
+ ]);
1872
+ const hasComponentHooks = (groupAnchors) => [...closureOf(groupAnchors.flatMap((a) => a.refs))].some(
1873
+ (u) => u.markers.some(
1874
+ (m) => m.kind === "hook" && !sharedContextHooks.has(m.detail.replace(/\(\)$/, ""))
1875
+ )
1876
+ );
1877
+ let groups = [];
1878
+ for (const groupAnchors of groupsByRoot.values()) {
1879
+ const lca = lowestCommonAncestor(groupAnchors.map((a) => a.j));
1880
+ if (!lca) {
1881
+ return skip(
1882
+ "state-spans-component",
1883
+ "the same client state is used in different returned trees",
1884
+ locOf(ctx, groupAnchors[0]?.j.node ?? fn)
1885
+ );
1886
+ }
1887
+ const mapped = mappedRoot(lca, !hasComponentHooks(groupAnchors));
1888
+ if (!mapped.ok) return mapped;
1889
+ groups.push({ anchors: groupAnchors, root: mapped.value, extra: /* @__PURE__ */ new Set() });
1890
+ }
1891
+ const unitClosure = (start) => {
1892
+ const out = /* @__PURE__ */ new Set();
1893
+ const stack = [...start];
1894
+ while (stack.length > 0) {
1895
+ const unit = stack.pop();
1896
+ if (out.has(unit)) continue;
1897
+ out.add(unit);
1898
+ stack.push(...unit.localDeps);
1899
+ }
1900
+ return out;
1901
+ };
1902
+ const unitHasOwnHooks = (u) => u.markers.some(
1903
+ (m) => m.kind === "hook" && !sharedContextHooks.has(m.detail.replace(/\(\)$/, ""))
1904
+ );
1905
+ const groupHasHooks = (g) => hasComponentHooks(g.anchors) || [...unitClosure(g.extra)].some(unitHasOwnHooks);
1906
+ const unitsOfGroup = (group) => {
1907
+ const refs = [];
1908
+ forEachValueRef(group.root.node, (id) => refs.push(id));
1909
+ const units2 = closureOf(refs);
1910
+ for (const unit of unitClosure(group.extra)) units2.add(unit);
1911
+ return units2;
1912
+ };
1913
+ const attachUnreadClientUnits = () => {
1914
+ let changed = false;
1915
+ for (const unit of units) {
1916
+ if (!unit.client || unit.kind === "early-return") continue;
1917
+ const owned = groups.map((g) => unitsOfGroup(g));
1918
+ if (owned.some((set) => set.has(unit))) continue;
1919
+ const deps = unitClosure(unit.localDeps);
1920
+ const owners = groups.filter((_, i) => [...deps].some((d) => d.client && owned[i]?.has(d)));
1921
+ const [first, ...rest] = owners;
1922
+ if (!first) continue;
1923
+ if (rest.length > 0) {
1924
+ const lca = lowestCommonAncestor(owners.map((g) => g.root));
1925
+ if (!lca)
1926
+ return skip(
1927
+ "state-spans-component",
1928
+ "client state is shared across returned trees",
1929
+ locOf(ctx, unit.node)
1930
+ );
1931
+ const merged = [...owners.flatMap((g) => g.anchors)];
1932
+ const mapped = mappedRoot(lca, !owners.some(groupHasHooks));
1933
+ if (!mapped.ok) return mapped;
1934
+ first.anchors = merged;
1935
+ first.root = mapped.value;
1936
+ for (const other of rest) other.extra.forEach((u) => first.extra.add(u));
1937
+ groups = groups.filter((g) => !rest.includes(g));
1938
+ }
1939
+ first.extra.add(unit);
1940
+ if (unitHasOwnHooks(unit)) {
1941
+ const hoisted = mappedRoot(first.root, false);
1942
+ if (!hoisted.ok) return hoisted;
1943
+ first.root = hoisted.value;
1944
+ }
1945
+ changed = true;
1946
+ }
1947
+ return ok(changed);
1948
+ };
1949
+ for (let iteration = 0; iteration < 100; iteration++) {
1950
+ const attached = attachUnreadClientUnits();
1951
+ if (!attached.ok) return attached;
1952
+ let changed = attached.value;
1953
+ outer: for (const a of groups) {
1954
+ for (const b of groups) {
1955
+ if (a === b) continue;
1956
+ if (isAncestorOrSelf(a.root, b.root)) {
1957
+ a.anchors.push(...b.anchors);
1958
+ b.extra.forEach((u) => a.extra.add(u));
1959
+ groups = groups.filter((g) => g !== b);
1960
+ changed = true;
1961
+ break outer;
1962
+ }
1963
+ }
1964
+ }
1965
+ if (changed) continue;
1966
+ const owner = /* @__PURE__ */ new Map();
1967
+ for (const group of groups) {
1968
+ for (const unit of unitsOfGroup(group)) {
1969
+ if (!unit.client) continue;
1970
+ const other = owner.get(unit);
1971
+ if (other && other !== group) {
1972
+ const lca = lowestCommonAncestor([other.root, group.root]);
1973
+ if (!lca)
1974
+ return skip(
1975
+ "state-spans-component",
1976
+ "client state is shared across returned trees",
1977
+ locOf(ctx, unit.node)
1978
+ );
1979
+ const mapped = mappedRoot(lca, !(groupHasHooks(other) || groupHasHooks(group)));
1980
+ if (!mapped.ok) return mapped;
1981
+ other.anchors.push(...group.anchors);
1982
+ group.extra.forEach((u) => other.extra.add(u));
1983
+ other.root = mapped.value;
1984
+ groups = groups.filter((g) => g !== group);
1985
+ changed = true;
1986
+ break;
1987
+ }
1988
+ owner.set(unit, group);
1989
+ }
1990
+ if (changed) break;
1991
+ }
1992
+ if (changed) continue;
1993
+ for (const group of groups) {
1994
+ const islandUnits = unitsOfGroup(group);
1995
+ const clientDeclared = /* @__PURE__ */ new Set();
1996
+ for (const unit of islandUnits)
1997
+ if (unit.client) unit.declares.forEach((s) => clientDeclared.add(s));
1998
+ let escape;
1999
+ const check = (scope) => forEachValueRef(scope, (id) => {
2000
+ if (escape) return;
2001
+ const symbol = symbolOfRef(checker, id);
2002
+ if (!symbol || !clientDeclared.has(symbol)) return;
2003
+ if (containsRange(group.root.node, id)) return;
2004
+ escape = { node: id, j: enclosingJNode(id, jnodeOf) };
2005
+ });
2006
+ for (const root of renderRoots) check(root);
2007
+ for (const unit of units) if (!islandUnits.has(unit)) check(unit.node);
2008
+ if (escape) {
2009
+ if (!escape.j) {
2010
+ return skip(
2011
+ "state-spans-component",
2012
+ `client-only \`${escape.node.text}\` is read outside the interactive part`,
2013
+ locOf(ctx, escape.node)
2014
+ );
2015
+ }
2016
+ const lca = lowestCommonAncestor([group.root, escape.j]);
2017
+ if (!lca)
2018
+ return skip(
2019
+ "state-spans-component",
2020
+ `client-only \`${escape.node.text}\` is read in another returned tree`,
2021
+ locOf(ctx, escape.node)
2022
+ );
2023
+ const mapped = mappedRoot(lca, !groupHasHooks(group));
2024
+ if (!mapped.ok) return mapped;
2025
+ group.root = mapped.value;
2026
+ changed = true;
2027
+ break;
2028
+ }
2029
+ }
2030
+ if (!changed) break;
2031
+ }
2032
+ for (const group of groups) {
2033
+ if (!group.root.parent) {
2034
+ const single = group.anchors.length === 1 && group.anchors[0]?.j === group.root;
2035
+ if (single) {
2036
+ return skip(
2037
+ "fully-interactive",
2038
+ `the root <${tagNameText(sf, group.root.node)}> itself is interactive (${group.anchors[0]?.reasons[0] ?? "client-only"}), so nothing would stay on the server`,
2039
+ locOf(ctx, group.root.node)
2040
+ );
2041
+ }
2042
+ return skip(
2043
+ "state-spans-component",
2044
+ `client state is used across the whole returned tree (${describeAnchors(sf, group.anchors)})`,
2045
+ locOf(ctx, group.root.node)
2046
+ );
2047
+ }
2048
+ }
2049
+ const assigned = /* @__PURE__ */ new Map();
2050
+ for (const group of groups)
2051
+ for (const unit of unitsOfGroup(group)) if (unit.client) assigned.set(unit, group);
2052
+ for (const unit of clientUnits) {
2053
+ if (unit.kind === "early-return") continue;
2054
+ if (!assigned.has(unit)) {
2055
+ return skip(
2056
+ "unsupported-pattern",
2057
+ `client-only code at line ${locOf(ctx, unit.node).line} isn't connected to any rendered element; moving it would change when it runs`,
2058
+ locOf(ctx, unit.node)
2059
+ );
2060
+ }
2061
+ }
2062
+ groups.sort((a, b) => a.root.node.getStart(sf) - b.root.node.getStart(sf));
2063
+ const islands = [];
2064
+ const copiedUnits = /* @__PURE__ */ new Set();
2065
+ const anchorNodes = new Set(anchors.map((a) => a.j));
2066
+ const subtreeOf = (j) => jnodes.filter((other) => isAncestorOrSelf(j, other));
2067
+ const isStaticSubtree = (j) => {
2068
+ if (subtreeOf(j).some((n) => anchorNodes.has(n))) return false;
2069
+ if (facts.scanMarkers(j.node).length > 0) return false;
2070
+ let clientRead = false;
2071
+ forEachValueRef(j.node, (id) => {
2072
+ if (clientRead) return;
2073
+ const origin = originOf(id);
2074
+ if (origin.kind === "local" && origin.unit.client || origin.kind === "module" && clientModuleDecls.has(origin.decl) || origin.kind === "import" && origin.usage.cls !== "neutral" && !(origin.usage.cls === "client-reference" && isTagRef(id)) || origin.kind === "browser-global" || origin.kind === "unknown" || origin.kind === "component" && !isTagRef(id)) {
2075
+ clientRead = true;
2076
+ }
2077
+ });
2078
+ return !clientRead;
2079
+ };
2080
+ const isNonTrivial = (j) => {
2081
+ if (subtreeOf(j).length > 1) return true;
2082
+ let importedCall = false;
2083
+ const visit = (n) => {
2084
+ if (importedCall) return;
2085
+ if (ts8.isCallExpression(n)) {
2086
+ let callee = n.expression;
2087
+ while (ts8.isPropertyAccessExpression(callee)) callee = callee.expression;
2088
+ if (ts8.isIdentifier(callee) && originOf(callee).kind === "import") importedCall = true;
2089
+ }
2090
+ ts8.forEachChild(n, visit);
2091
+ };
2092
+ visit(j.node);
2093
+ return importedCall;
2094
+ };
2095
+ const findSlots = (root) => {
2096
+ const found = [];
2097
+ const visit = (j) => {
2098
+ for (const child of j.children) {
2099
+ const direct = child.fnBetween.length === 0 && child.node.parent === j.node;
2100
+ if (!direct) continue;
2101
+ if (isStaticSubtree(child)) {
2102
+ if (isNonTrivial(child)) found.push(child);
2103
+ continue;
2104
+ }
2105
+ visit(child);
2106
+ }
2107
+ };
2108
+ visit(root);
2109
+ return found;
2110
+ };
2111
+ for (const group of groups) {
2112
+ const rootNode = group.root.node;
2113
+ const rootTag = tagNameText(sf, rootNode);
2114
+ const islandUnits = new Set([...unitsOfGroup(group)].filter((u) => u.client));
2115
+ const props = /* @__PURE__ */ new Map();
2116
+ const valueImports = /* @__PURE__ */ new Set();
2117
+ const moduleDecls = /* @__PURE__ */ new Set();
2118
+ let keyAttr;
2119
+ if (!ts8.isJsxFragment(rootNode) && group.root.fnBetween.some((f) => mapCallOf(f))) {
2120
+ const attrs = ts8.isJsxElement(rootNode) ? rootNode.openingElement.attributes : rootNode.attributes;
2121
+ keyAttr = attrs.properties.find(
2122
+ (p) => ts8.isJsxAttribute(p) && p.name.getText(sf) === "key"
2123
+ );
2124
+ }
2125
+ const slotNodes = findSlots(group.root);
2126
+ const inSlot = (node) => slotNodes.some((slot) => containsRange(slot.node, node));
2127
+ const inIsland = (node) => containsRange(rootNode, node) && !(keyAttr && containsRange(keyAttr, node)) && !inSlot(node) || [...islandUnits].some((u) => containsRange(u.node, node));
2128
+ const pending = [rootNode, ...[...islandUnits].map((u) => u.node)];
2129
+ const visitedNodes = /* @__PURE__ */ new Set();
2130
+ const moduleLevelScopes = /* @__PURE__ */ new Set();
2131
+ const addProp = (id, symbol) => {
2132
+ const name = symbol.getName();
2133
+ const existing = props.get(name);
2134
+ if (existing) {
2135
+ if (existing.symbol !== symbol) {
2136
+ return {
2137
+ code: "unsupported-pattern",
2138
+ message: `two different values named \`${name}\` would cross the boundary`,
2139
+ location: locOf(ctx, id)
2140
+ };
2141
+ }
2142
+ if (containsRange(rootNode, id) && !containsRange(rootNode, existing.refNode))
2143
+ existing.refNode = id;
2144
+ return void 0;
2145
+ }
2146
+ if (name === "key" || name === "ref") {
2147
+ return {
2148
+ code: "unsupported-pattern",
2149
+ message: `a value named \`${name}\` can't be passed as a prop`,
2150
+ location: locOf(ctx, id)
2151
+ };
2152
+ }
2153
+ props.set(name, { name, symbol, type: checker.getTypeAtLocation(id), refNode: id });
2154
+ return void 0;
2155
+ };
2156
+ while (pending.length > 0) {
2157
+ const scope = pending.pop();
2158
+ if (visitedNodes.has(scope)) continue;
2159
+ visitedNodes.add(scope);
2160
+ let failure;
2161
+ forEachValueRef(scope, (id) => {
2162
+ if (failure) return;
2163
+ if (keyAttr && containsRange(keyAttr, id)) return;
2164
+ if (inSlot(id)) return;
2165
+ const origin = originOf(id);
2166
+ switch (origin.kind) {
2167
+ case "param":
2168
+ failure = addProp(id, origin.symbol);
2169
+ return;
2170
+ case "local": {
2171
+ const unit = origin.unit;
2172
+ if (islandUnits.has(unit)) return;
2173
+ if (unit.client) {
2174
+ failure = {
2175
+ code: "state-spans-component",
2176
+ message: `client-only \`${id.text}\` is shared between islands`,
2177
+ location: locOf(ctx, id)
2178
+ };
2179
+ return;
2180
+ }
2181
+ const type = checker.getTypeAtLocation(id);
2182
+ const needsCopy = unit.kind === "function" || isCallableType(type) || !checkSerializable(checker, type, { primitivesOnly: ctx.isJs }).ok;
2183
+ if (needsCopy) {
2184
+ islandUnits.add(unit);
2185
+ copiedUnits.add(unit);
2186
+ pending.push(unit.node);
2187
+ } else {
2188
+ failure = addProp(id, origin.symbol);
2189
+ }
2190
+ return;
2191
+ }
2192
+ case "inner":
2193
+ if (inIsland(origin.declaration)) return;
2194
+ if ([...moduleLevelScopes].some(
2195
+ (scopeNode) => containsRange(scopeNode, origin.declaration)
2196
+ ))
2197
+ return;
2198
+ failure = addProp(id, origin.symbol);
2199
+ return;
2200
+ case "module": {
2201
+ const decl = origin.decl;
2202
+ if (moduleDecls.has(decl)) return;
2203
+ const fromModuleLevel = moduleLevelScopes.has(scope);
2204
+ if (!fromModuleLevel && decl.kind === "const" && !clientModuleDecls.has(decl)) {
2205
+ const type = checker.getTypeAtLocation(id);
2206
+ if (checkSerializable(checker, type, { primitivesOnly: ctx.isJs }).ok) {
2207
+ failure = addProp(id, origin.symbol);
2208
+ return;
2209
+ }
2210
+ }
2211
+ moduleDecls.add(decl);
2212
+ moduleLevelScopes.add(decl.statement);
2213
+ pending.push(decl.statement);
2214
+ return;
2215
+ }
2216
+ case "import":
2217
+ if (origin.usage.cls === "server-only") {
2218
+ failure = {
2219
+ code: "unsupported-pattern",
2220
+ message: `${id.text} (${origin.binding.specifier}) is server-only but the interactive part uses it`,
2221
+ location: locOf(ctx, id)
2222
+ };
2223
+ return;
2224
+ }
2225
+ valueImports.add(origin.binding);
2226
+ return;
2227
+ case "component": {
2228
+ const target = origin.component;
2229
+ if (target === component) {
2230
+ failure = {
2231
+ code: "unsupported-pattern",
2232
+ message: "an island would reference the component itself",
2233
+ location: locOf(ctx, id)
2234
+ };
2235
+ return;
2236
+ }
2237
+ if (!isTagRef(id)) {
2238
+ failure = {
2239
+ code: "unsupported-pattern",
2240
+ message: `an island uses the component ${id.text} as a value`,
2241
+ location: locOf(ctx, id)
2242
+ };
2243
+ return;
2244
+ }
2245
+ if (options.wholesale?.has(target)) return;
2246
+ const decl = shape.componentDecls.get(target);
2247
+ if (!decl || moduleDecls.has(decl)) return;
2248
+ moduleDecls.add(decl);
2249
+ moduleLevelScopes.add(decl.statement);
2250
+ pending.push(decl.statement);
2251
+ return;
2252
+ }
2253
+ case "browser-global":
2254
+ case "global":
2255
+ return;
2256
+ case "unknown":
2257
+ failure = {
2258
+ code: "unsupported-pattern",
2259
+ message: `can't resolve \`${id.text}\``,
2260
+ location: locOf(ctx, id)
2261
+ };
2262
+ return;
2263
+ }
2264
+ });
2265
+ if (failure) return { ok: false, skip: failure };
2266
+ }
2267
+ for (const prop of props.values()) {
2268
+ if (serverActionParams.has(prop.symbol)) continue;
2269
+ const verdict = checkSerializable(checker, prop.type, { primitivesOnly: ctx.isJs });
2270
+ if (!verdict.ok && !ctx.isJs && ctx.config.mongoose.autoSerialize && isMongooseLike(checker, prop.type)) {
2271
+ const refs = [];
2272
+ for (const scope of [rootNode, ...[...islandUnits].map((u) => u.node)]) {
2273
+ forEachValueRef(scope, (id) => {
2274
+ if (keyAttr && containsRange(keyAttr, id) || inSlot(id)) return;
2275
+ if (symbolOfRef(checker, id) === prop.symbol) refs.push(id);
2276
+ });
2277
+ }
2278
+ const picked = pickMongooseFields(
2279
+ checker,
2280
+ prop.name,
2281
+ refs,
2282
+ (t) => checker.typeToString(
2283
+ t,
2284
+ rootNode,
2285
+ ts8.TypeFormatFlags.NoTruncation | ts8.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope
2286
+ )
2287
+ );
2288
+ if (picked.ok) {
2289
+ prop.picked = picked.value;
2290
+ continue;
2291
+ }
2292
+ return skip(
2293
+ "non-serializable-prop",
2294
+ `\`${prop.name}\` holds Mongoose/ObjectId data and the island ${picked.reason}`,
2295
+ locOf(ctx, prop.refNode)
2296
+ );
2297
+ }
2298
+ if (!verdict.ok) {
2299
+ const typeText = checker.typeToString(prop.type, rootNode, ts8.TypeFormatFlags.NoTruncation);
2300
+ const usedInSpread = isSpreadInside(rootNode, prop.symbol, checker);
2301
+ if (ctx.isJs) {
2302
+ return skip(
2303
+ "untyped-boundary",
2304
+ `\`${prop.name}\` would cross the server/client boundary, but its type in this untyped file (\`${typeText}\`) can't be proven serializable`,
2305
+ locOf(ctx, prop.refNode)
2306
+ );
2307
+ }
2308
+ return skip(
2309
+ usedInSpread ? "spread-props" : "non-serializable-prop",
2310
+ `\`${prop.name}\` (type \`${typeText}\`) would be passed to a Client Component, but it ${verdict.reason}`,
2311
+ locOf(ctx, prop.refNode)
2312
+ );
2313
+ }
2314
+ }
2315
+ if (keyAttr) {
2316
+ let clientKey;
2317
+ forEachValueRef(keyAttr, (id) => {
2318
+ const origin = originOf(id);
2319
+ if (origin.kind === "local" && origin.unit.client) clientKey = id;
2320
+ });
2321
+ if (clientKey)
2322
+ return skip(
2323
+ "unsupported-pattern",
2324
+ `the list key reads client-only \`${clientKey.text}\``,
2325
+ locOf(ctx, clientKey)
2326
+ );
2327
+ }
2328
+ islands.push({
2329
+ name: "",
2330
+ root: group.root,
2331
+ rootTag,
2332
+ keyAttr,
2333
+ units: [...islandUnits].sort((a, b) => a.index - b.index),
2334
+ props: [...props.values()].sort(
2335
+ (a, b) => declarationPos(a.symbol) - declarationPos(b.symbol) || a.name.localeCompare(b.name)
2336
+ ),
2337
+ valueImports,
2338
+ moduleDecls,
2339
+ slots: slotNodes.map((j, i) => ({
2340
+ j,
2341
+ name: slotNodes.length === 1 && !props.has("children") ? "children" : `slot${i + 1}`
2342
+ })),
2343
+ line: locOf(ctx, rootNode).line
2344
+ });
2345
+ }
2346
+ const removedUnits = new Set(clientUnits.filter((u) => u.kind !== "early-return"));
2347
+ const islandRanges = islands.map((i) => i.root.node);
2348
+ const slotRanges = islands.flatMap((i) => i.slots.map((slot) => slot.j.node));
2349
+ const inAnySlot = (node) => slotRanges.some((r) => containsRange(r, node));
2350
+ const serverReads = (unit) => {
2351
+ let read = false;
2352
+ const scan = (scope) => forEachValueRef(scope, (id) => {
2353
+ if (read) return;
2354
+ if (islandRanges.some((r) => containsRange(r, id)) && !inAnySlot(id)) return;
2355
+ const symbol = symbolOfRef(checker, id);
2356
+ if (symbol && unit.declares.has(symbol)) read = true;
2357
+ });
2358
+ for (const root of renderRoots) scan(root);
2359
+ for (const cond of earlyReturnConditions) scan(cond);
2360
+ for (const other of units) if (other !== unit && !removedUnits.has(other)) scan(other.node);
2361
+ for (const island of islands)
2362
+ for (const prop of island.props) if (unit.declares.has(prop.symbol)) read = true;
2363
+ return read;
2364
+ };
2365
+ for (let changed = true; changed; ) {
2366
+ changed = false;
2367
+ for (const unit of copiedUnits) {
2368
+ if (removedUnits.has(unit)) continue;
2369
+ if (!serverReads(unit)) {
2370
+ removedUnits.add(unit);
2371
+ changed = true;
2372
+ }
2373
+ }
2374
+ }
2375
+ const movedModuleDecls = /* @__PURE__ */ new Set();
2376
+ for (const decl of clientModuleDecls) movedModuleDecls.add(decl);
2377
+ const staticCount = jnodes.filter(
2378
+ (j) => !islands.some((i) => isAncestorOrSelf(i.root, j)) || islands.some((i) => i.slots.some((slot) => isAncestorOrSelf(slot.j, j)))
2379
+ ).length;
2380
+ return ok({
2381
+ component,
2382
+ islands,
2383
+ removedUnits,
2384
+ movedModuleDecls,
2385
+ staticRatio: totalJsx === 0 ? 0 : staticCount / totalJsx,
2386
+ jsxTotal: totalJsx
2387
+ });
2388
+ }
2389
+ function declarationPos(symbol) {
2390
+ return symbol.declarations?.[0]?.getStart() ?? Number.MAX_SAFE_INTEGER;
2391
+ }
2392
+ function containsJsxNode(node) {
2393
+ if (isJsxElementLike(node)) return true;
2394
+ return ts8.forEachChild(node, (c) => containsJsxNode(c) ? true : void 0) ?? false;
2395
+ }
2396
+ function isTagRef(id) {
2397
+ let node = id;
2398
+ while (ts8.isPropertyAccessExpression(node.parent) && node.parent.expression === node)
2399
+ node = node.parent;
2400
+ const parent = node.parent;
2401
+ return (ts8.isJsxOpeningElement(parent) || ts8.isJsxSelfClosingElement(parent)) && parent.tagName === node;
2402
+ }
2403
+ function enclosingJNode(node, jnodeOf) {
2404
+ let current = node.parent;
2405
+ while (current) {
2406
+ const j = jnodeOf.get(current);
2407
+ if (j) return j;
2408
+ current = current.parent;
2409
+ }
2410
+ return void 0;
2411
+ }
2412
+ function conditionalAncestor(node, stopAt) {
2413
+ let child = node;
2414
+ let current = node.parent;
2415
+ while (current && current !== stopAt) {
2416
+ if (ts8.isConditionalExpression(current) && child !== current.condition)
2417
+ return "inside a conditional expression";
2418
+ if (ts8.isBinaryExpression(current) && child === current.right && [
2419
+ ts8.SyntaxKind.AmpersandAmpersandToken,
2420
+ ts8.SyntaxKind.BarBarToken,
2421
+ ts8.SyntaxKind.QuestionQuestionToken
2422
+ ].includes(current.operatorToken.kind)) {
2423
+ return "inside a logical expression";
2424
+ }
2425
+ if (ts8.isIfStatement(current) || ts8.isSwitchStatement(current)) return "inside a condition";
2426
+ if (ts8.isIterationStatement(current, false)) return "inside a loop";
2427
+ if (ts8.isFunctionLike(current)) return "inside a nested function";
2428
+ child = current;
2429
+ current = current.parent;
2430
+ }
2431
+ return void 0;
2432
+ }
2433
+ function isSpreadInside(root, symbol, checker) {
2434
+ let found = false;
2435
+ const visit = (n) => {
2436
+ if (found) return;
2437
+ if ((ts8.isJsxSpreadAttribute(n) || ts8.isSpreadAssignment(n) || ts8.isSpreadElement(n)) && ts8.isIdentifier(n.expression)) {
2438
+ if (checker.getSymbolAtLocation(n.expression) === symbol) found = true;
2439
+ }
2440
+ ts8.forEachChild(n, visit);
2441
+ };
2442
+ visit(root);
2443
+ return found;
2444
+ }
2445
+ function describeAnchors(sf, anchors) {
2446
+ const tags = [...new Set(anchors.map((a) => `<${tagNameText(sf, a.j.node)}>`))];
2447
+ return tags.slice(0, 4).join(", ") + (tags.length > 4 ? ", ..." : "");
2448
+ }
2449
+
2450
+ // src/analysis/moduleSafety.ts
2451
+ import { ts as ts10 } from "ts-morph";
2452
+
2453
+ // src/analysis/taint.ts
2454
+ import { ts as ts9 } from "ts-morph";
2455
+ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
2456
+ "window",
2457
+ "document",
2458
+ "localStorage",
2459
+ "sessionStorage",
2460
+ "navigator",
2461
+ "location",
2462
+ "history",
2463
+ "matchMedia",
2464
+ "IntersectionObserver",
2465
+ "ResizeObserver",
2466
+ "MutationObserver",
2467
+ "PerformanceObserver",
2468
+ "requestAnimationFrame",
2469
+ "cancelAnimationFrame",
2470
+ "requestIdleCallback",
2471
+ "cancelIdleCallback",
2472
+ "alert",
2473
+ "confirm",
2474
+ "prompt",
2475
+ "self",
2476
+ "screen",
2477
+ "innerWidth",
2478
+ "innerHeight",
2479
+ "outerWidth",
2480
+ "outerHeight",
2481
+ "scrollX",
2482
+ "scrollY",
2483
+ "pageXOffset",
2484
+ "pageYOffset",
2485
+ "devicePixelRatio",
2486
+ "visualViewport",
2487
+ "getComputedStyle",
2488
+ "getSelection",
2489
+ "customElements",
2490
+ "indexedDB",
2491
+ "caches",
2492
+ "Notification",
2493
+ "FileReader",
2494
+ "Image",
2495
+ "Audio",
2496
+ "Worker",
2497
+ "XMLHttpRequest",
2498
+ "WebSocket",
2499
+ "EventSource",
2500
+ "DOMParser",
2501
+ "HTMLElement",
2502
+ "Element",
2503
+ "CustomEvent",
2504
+ "KeyboardEvent",
2505
+ "MouseEvent",
2506
+ "scrollTo",
2507
+ "scrollBy",
2508
+ "print",
2509
+ "open",
2510
+ "close",
2511
+ "postMessage",
2512
+ "frames",
2513
+ "parent",
2514
+ "top",
2515
+ "opener",
2516
+ "origin",
2517
+ "event",
2518
+ "setTimeout",
2519
+ "setInterval",
2520
+ "clearTimeout",
2521
+ "clearInterval"
2522
+ ]);
2523
+ function isAmbientDeclaration(decl) {
2524
+ return decl.getSourceFile().isDeclarationFile;
2525
+ }
2526
+ var FileFacts = class {
2527
+ constructor(sf, checker, imports, framework, projectSafety, serverSafeHooks) {
2528
+ this.sf = sf;
2529
+ this.checker = checker;
2530
+ this.imports = imports;
2531
+ this.framework = framework;
2532
+ this.projectSafety = projectSafety;
2533
+ this.serverSafeHooks = serverSafeHooks;
2534
+ }
2535
+ sf;
2536
+ checker;
2537
+ imports;
2538
+ framework;
2539
+ projectSafety;
2540
+ serverSafeHooks;
2541
+ classifyRef(id) {
2542
+ const symbol = symbolOfRef(this.checker, id);
2543
+ if (!symbol)
2544
+ return BROWSER_GLOBALS.has(id.text) ? { kind: "browser-global", name: id.text } : { kind: "unknown" };
2545
+ const binding = this.imports.bySymbol.get(symbol);
2546
+ if (binding) {
2547
+ const parent = id.parent;
2548
+ const member = parent && ts9.isPropertyAccessExpression(parent) && parent.expression === id ? parent.name.text : void 0;
2549
+ const asJsxTag = parent && (ts9.isJsxOpeningElement(parent) || ts9.isJsxSelfClosingElement(parent)) && parent.tagName === id || parent !== void 0 && ts9.isPropertyAccessExpression(parent) && parent.expression === id && (ts9.isJsxOpeningElement(parent.parent) || ts9.isJsxSelfClosingElement(parent.parent)) && parent.parent.tagName === parent;
2550
+ return {
2551
+ kind: "import",
2552
+ binding,
2553
+ usage: classifyImportUsage(binding, member, asJsxTag, this.framework, this.projectSafety)
2554
+ };
2555
+ }
2556
+ const decl = symbol.valueDeclaration ?? symbol.declarations?.[0];
2557
+ if (!decl) return { kind: "global" };
2558
+ if (decl.getSourceFile() === this.sf) return { kind: "file", symbol, declaration: decl };
2559
+ if (isAmbientDeclaration(decl)) {
2560
+ return BROWSER_GLOBALS.has(id.text) && !(symbol.flags & ts9.SymbolFlags.Alias) ? { kind: "browser-global", name: id.text } : { kind: "global" };
2561
+ }
2562
+ return { kind: "unknown" };
2563
+ }
2564
+ isHookCall(call) {
2565
+ const name = calleeName(call);
2566
+ if (!name) return false;
2567
+ if (this.serverSafeHooks.has(name)) return false;
2568
+ if (HOOK_NAME_RE.test(name)) return true;
2569
+ if (name === "use") {
2570
+ const [arg] = call.arguments;
2571
+ if (!arg) return false;
2572
+ const type = this.checker.getTypeAtLocation(arg);
2573
+ return type.getProperty("Provider") !== void 0 || type.getProperty("Consumer") !== void 0;
2574
+ }
2575
+ return false;
2576
+ }
2577
+ /**
2578
+ * Client-only markers under `node`. `stop` prunes subtrees (nested JSX elements when scanning
2579
+ * one element's own attributes and children).
2580
+ */
2581
+ scanMarkers(node, stop) {
2582
+ const markers = [];
2583
+ const visit = (n) => {
2584
+ if (n !== node && stop?.(n)) return;
2585
+ if (ts9.isCallExpression(n) && this.isHookCall(n)) {
2586
+ markers.push({ kind: "hook", node: n, detail: `${calleeName(n) ?? "hook"}()` });
2587
+ }
2588
+ const nondeterministic = nondeterministicCall(n);
2589
+ if (nondeterministic)
2590
+ markers.push({ kind: "nondeterministic", node: n, detail: nondeterministic });
2591
+ ts9.forEachChild(n, visit);
2592
+ };
2593
+ visit(node);
2594
+ forEachValueRef(
2595
+ node,
2596
+ (id) => {
2597
+ const info = this.classifyRef(id);
2598
+ if (info.kind === "browser-global")
2599
+ markers.push({ kind: "browser-global", node: id, detail: info.name });
2600
+ else if (info.kind === "import" && info.usage.cls === "client-only") {
2601
+ markers.push({
2602
+ kind: "client-import",
2603
+ node: id,
2604
+ detail: info.usage.reason,
2605
+ special: info.usage.special
2606
+ });
2607
+ } else if (info.kind === "import" && info.usage.special) {
2608
+ markers.push({
2609
+ kind: "client-import",
2610
+ node: id,
2611
+ detail: info.usage.reason,
2612
+ special: info.usage.special
2613
+ });
2614
+ }
2615
+ },
2616
+ stop
2617
+ );
2618
+ return markers;
2619
+ }
2620
+ };
2621
+ function nondeterministicCall(n) {
2622
+ if (ts9.isNewExpression(n) && ts9.isIdentifier(n.expression) && n.expression.text === "Date" && (n.arguments?.length ?? 0) === 0) {
2623
+ return "new Date()";
2624
+ }
2625
+ if (ts9.isCallExpression(n) && ts9.isPropertyAccessExpression(n.expression) && ts9.isIdentifier(n.expression.expression)) {
2626
+ const object = n.expression.expression.text;
2627
+ const method = n.expression.name.text;
2628
+ if (object === "Date" && method === "now" || object === "Math" && method === "random" || object === "performance" && method === "now" || object === "crypto" && (method === "randomUUID" || method === "getRandomValues")) {
2629
+ return `${object}.${method}()`;
2630
+ }
2631
+ }
2632
+ return void 0;
2633
+ }
2634
+ function findEnvProblem(node, checker, framework) {
2635
+ let problem;
2636
+ const visit = (n) => {
2637
+ if (problem) return;
2638
+ if (ts9.isIdentifier(n) && n.text === "process" && isGlobalProcess(n, checker)) {
2639
+ const parent = n.parent;
2640
+ if (ts9.isPropertyAccessExpression(parent) && parent.expression === n && parent.name.text === "env") {
2641
+ const outer = parent.parent;
2642
+ if (ts9.isPropertyAccessExpression(outer) && outer.expression === parent) {
2643
+ const name = outer.name.text;
2644
+ if (!framework.isClientVisibleEnv(name)) {
2645
+ problem = {
2646
+ node: outer,
2647
+ message: `reads process.env.${name}, which is defined on the server but not in the browser bundle`
2648
+ };
2649
+ }
2650
+ return;
2651
+ }
2652
+ if (ts9.isElementAccessExpression(outer) && outer.expression === parent && ts9.isStringLiteralLike(outer.argumentExpression)) {
2653
+ const name = outer.argumentExpression.text;
2654
+ if (!framework.isClientVisibleEnv(name)) {
2655
+ problem = {
2656
+ node: outer,
2657
+ message: `reads process.env["${name}"], which is defined on the server but not in the browser bundle`
2658
+ };
2659
+ }
2660
+ return;
2661
+ }
2662
+ problem = {
2663
+ node: parent,
2664
+ message: "reads process.env dynamically; its value differs between the server and the browser"
2665
+ };
2666
+ return;
2667
+ }
2668
+ if (!(ts9.isTypeOfExpression(parent) || ts9.isTypeQueryNode(parent))) {
2669
+ problem = {
2670
+ node: n,
2671
+ message: "reads `process`, which differs between the server and the browser"
2672
+ };
2673
+ }
2674
+ return;
2675
+ }
2676
+ ts9.forEachChild(n, visit);
2677
+ };
2678
+ visit(node);
2679
+ return problem;
2680
+ }
2681
+ function isGlobalProcess(id, checker) {
2682
+ if (ts9.isPropertyAccessExpression(id.parent) && id.parent.name === id) return false;
2683
+ const symbol = checker.getSymbolAtLocation(id);
2684
+ const decl = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
2685
+ return !decl || decl.getSourceFile().isDeclarationFile;
2686
+ }
2687
+
2688
+ // src/analysis/moduleSafety.ts
2689
+ var ModuleSafety = class {
2690
+ constructor(getProgram, graph, framework, serverSafeHooks) {
2691
+ this.getProgram = getProgram;
2692
+ this.graph = graph;
2693
+ this.framework = framework;
2694
+ this.serverSafeHooks = serverSafeHooks;
2695
+ }
2696
+ getProgram;
2697
+ graph;
2698
+ framework;
2699
+ serverSafeHooks;
2700
+ cache = /* @__PURE__ */ new Map();
2701
+ inProgress = /* @__PURE__ */ new Set();
2702
+ check = (file) => {
2703
+ const cached = this.cache.get(file);
2704
+ if (cached) return cached;
2705
+ if (this.inProgress.has(file)) return { safe: true };
2706
+ this.inProgress.add(file);
2707
+ try {
2708
+ const result = this.compute(file);
2709
+ this.cache.set(file, result);
2710
+ return result;
2711
+ } finally {
2712
+ this.inProgress.delete(file);
2713
+ }
2714
+ };
2715
+ compute(file) {
2716
+ const node = this.graph.nodes.get(file);
2717
+ const rel2 = this.graph.rel(file);
2718
+ if (!node || !node.inProgram)
2719
+ return { safe: false, reason: `${rel2} is outside the TypeScript project` };
2720
+ if (node.directive === "use client")
2721
+ return { safe: false, reason: `${rel2} is a "use client" module` };
2722
+ if (node.directive === "use server") return { safe: true };
2723
+ const program = this.getProgram();
2724
+ const sf = program.getSourceFile(file);
2725
+ if (!sf) return { safe: false, reason: `${rel2} is not in the program` };
2726
+ const checker = program.getTypeChecker();
2727
+ for (const statement of sf.statements) {
2728
+ if (ts10.isImportDeclaration(statement) && !statement.importClause && ts10.isStringLiteral(statement.moduleSpecifier)) {
2729
+ const spec = statement.moduleSpecifier.text;
2730
+ if (!/\.(css|scss|sass|less)$/.test(spec)) {
2731
+ return { safe: false, reason: `${rel2} has a side-effect import of ${spec}` };
2732
+ }
2733
+ }
2734
+ if (ts10.isExportDeclaration(statement) && statement.moduleSpecifier && !statement.isTypeOnly) {
2735
+ const edge = this.graph.edgeFor(file, statement.moduleSpecifier.text);
2736
+ if (edge?.to) {
2737
+ const target = this.graph.nodes.get(edge.to);
2738
+ if (target?.directive !== "use client" && target?.directive !== "use server") {
2739
+ const inner = this.check(edge.to);
2740
+ if (!inner.safe)
2741
+ return { safe: false, reason: inner.reason ?? `${rel2} re-exports client code` };
2742
+ }
2743
+ } else if (edge?.external && !["neutral", "server-safe-list", "json", "asset", "use-client-entry"].includes(
2744
+ edge.external.kind
2745
+ )) {
2746
+ return { safe: false, reason: `${rel2} re-exports ${edge.specifier}` };
2747
+ }
2748
+ }
2749
+ }
2750
+ const imports = buildImportTable(sf, checker, this.graph);
2751
+ const facts = new FileFacts(
2752
+ sf,
2753
+ checker,
2754
+ imports,
2755
+ this.framework,
2756
+ this.check,
2757
+ this.serverSafeHooks
2758
+ );
2759
+ const markers = facts.scanMarkers(sf);
2760
+ const first = markers[0];
2761
+ if (first) {
2762
+ const line = sf.getLineAndCharacterOfPosition(first.node.getStart(sf)).line + 1;
2763
+ return { safe: false, reason: `${rel2}:${line} uses ${first.detail}` };
2764
+ }
2765
+ const env = findEnvProblem(sf, checker, this.framework);
2766
+ if (env) return { safe: false, reason: `${rel2} ${env.message}` };
2767
+ let reason;
2768
+ const visit = (n) => {
2769
+ if (reason) return;
2770
+ if (ts10.isJsxAttribute(n)) {
2771
+ const name = n.name.getText(sf);
2772
+ if (/^on[A-Z]/.test(name) || name === "ref") reason = `${rel2} attaches ${name} handlers`;
2773
+ }
2774
+ if (ts10.isClassDeclaration(n) && n.heritageClauses?.some((h) => /Component/.test(h.getText(sf)))) {
2775
+ reason = `${rel2} declares a class component`;
2776
+ }
2777
+ ts10.forEachChild(n, visit);
2778
+ };
2779
+ visit(sf);
2780
+ if (reason) return { safe: false, reason };
2781
+ return { safe: true };
2782
+ }
2783
+ };
2784
+
2785
+ // src/frameworks/next.ts
2786
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
2787
+ import path3 from "path";
2788
+ import { ts as ts11 } from "ts-morph";
2789
+ var perName = (classify) => ({
2790
+ kind: "per-name",
2791
+ classify
2792
+ });
2793
+ var hooksClient = (name) => /^use[A-Z]/.test(name) ? "client-only" : "neutral";
2794
+ var NAVIGATION_SERVER_SAFE = /* @__PURE__ */ new Set([
2795
+ "redirect",
2796
+ "permanentRedirect",
2797
+ "notFound",
2798
+ "forbidden",
2799
+ "unauthorized",
2800
+ "RedirectType",
2801
+ "unstable_rethrow"
2802
+ ]);
2803
+ var nextAdapter = {
2804
+ name: "next",
2805
+ classifyModule(specifier) {
2806
+ if (specifier === "next/dynamic") return { kind: "dynamic-import" };
2807
+ if (specifier === "styled-jsx" || specifier.startsWith("styled-jsx/"))
2808
+ return { kind: "css-in-js" };
2809
+ if (["next/link", "next/image", "next/script", "next/form", "next/legacy/image"].includes(
2810
+ specifier
2811
+ )) {
2812
+ return perName(
2813
+ (name) => name === "default" || /^[A-Z]/.test(name) ? "client-reference" : "neutral"
2814
+ );
2815
+ }
2816
+ if (specifier === "next/navigation") {
2817
+ return perName((name) => NAVIGATION_SERVER_SAFE.has(name) ? "neutral" : "client-only");
2818
+ }
2819
+ if (specifier === "next/router" || specifier === "next/head" || specifier === "next/amp") {
2820
+ return perName(() => "client-only");
2821
+ }
2822
+ if (["next/headers", "next/server", "next/cache", "next/after", "next/og"].includes(specifier)) {
2823
+ return perName(() => "server-only");
2824
+ }
2825
+ if (specifier.startsWith("next/font/")) return perName(() => "neutral");
2826
+ if (specifier === "next" || specifier === "next/types") return perName(hooksClient);
2827
+ if (specifier.startsWith("next/")) return perName(() => "client-only");
2828
+ return void 0;
2829
+ },
2830
+ isClientVisibleEnv(name) {
2831
+ return name.startsWith("NEXT_PUBLIC_") || name === "NODE_ENV";
2832
+ },
2833
+ mustStayClientFiles: ["error", "global-error"],
2834
+ sharedContextHooks: [
2835
+ "usePathname",
2836
+ "useSearchParams",
2837
+ "useParams",
2838
+ "useRouter",
2839
+ "useSelectedLayoutSegment",
2840
+ "useSelectedLayoutSegments"
2841
+ ]
2842
+ };
2843
+ var NEXT_ROUTE_FILE_BASENAMES = ["page", "route"];
2844
+ var DEFAULT_PAGE_EXTENSIONS = ["tsx", "ts", "jsx", "js"];
2845
+ function readPageExtensions(root) {
2846
+ const candidates = [
2847
+ "next.config.ts",
2848
+ "next.config.mjs",
2849
+ "next.config.js",
2850
+ "next.config.cjs",
2851
+ "next.config.mts"
2852
+ ];
2853
+ const file = candidates.map((c) => path3.join(root, c)).find((f) => existsSync2(f));
2854
+ if (!file) return { ok: true, extensions: DEFAULT_PAGE_EXTENSIONS };
2855
+ const text = readFileSync2(file, "utf8");
2856
+ if (!text.includes("pageExtensions")) return { ok: true, extensions: DEFAULT_PAGE_EXTENSIONS };
2857
+ const sf = ts11.createSourceFile(file, text, ts11.ScriptTarget.Latest, true);
2858
+ let found;
2859
+ let unreadable = false;
2860
+ const visit = (node) => {
2861
+ if (ts11.isPropertyAssignment(node) && node.name.getText(sf) === "pageExtensions" || ts11.isShorthandPropertyAssignment(node) && node.name.text === "pageExtensions") {
2862
+ const init = ts11.isPropertyAssignment(node) ? node.initializer : void 0;
2863
+ if (init && ts11.isArrayLiteralExpression(init) && init.elements.every(ts11.isStringLiteralLike)) {
2864
+ found = init.elements.map((e) => e.text);
2865
+ } else {
2866
+ unreadable = true;
2867
+ }
2868
+ }
2869
+ ts11.forEachChild(node, visit);
2870
+ };
2871
+ visit(sf);
2872
+ if (unreadable || !found) {
2873
+ return {
2874
+ ok: false,
2875
+ reason: `${path3.basename(file)} sets pageExtensions in a way that can't be read statically`
2876
+ };
2877
+ }
2878
+ return { ok: true, extensions: found };
2879
+ }
2880
+ function couldBecomeRoute(root, absPath, pageExtensions) {
2881
+ const rel2 = path3.relative(root, absPath).split(path3.sep);
2882
+ if (rel2[0] === "pages" || rel2[0] === "src" && rel2[1] === "pages") return true;
2883
+ const base = path3.basename(absPath);
2884
+ return pageExtensions.some(
2885
+ (ext) => NEXT_ROUTE_FILE_BASENAMES.some((name) => base === `${name}.${ext}`)
2886
+ );
2887
+ }
2888
+
2889
+ // src/project/loadProject.ts
2890
+ import { existsSync as existsSync3 } from "fs";
2891
+ import path4 from "path";
2892
+ import fg from "fast-glob";
2893
+ import { Project, ts as ts12 } from "ts-morph";
2894
+ var IGNORED_DIRS = [
2895
+ "node_modules",
2896
+ ".next",
2897
+ ".git",
2898
+ "dist",
2899
+ "out",
2900
+ "build",
2901
+ "coverage",
2902
+ ".turbo",
2903
+ ".vercel"
2904
+ ];
2905
+ var IGNORE_GLOBS = IGNORED_DIRS.map((d) => `**/${d}/**`);
2906
+ var DEFAULT_COMPILER_OPTIONS = {
2907
+ target: ts12.ScriptTarget.ES2022,
2908
+ lib: ["lib.dom.d.ts", "lib.dom.iterable.d.ts", "lib.esnext.d.ts"],
2909
+ module: ts12.ModuleKind.ESNext,
2910
+ moduleResolution: ts12.ModuleResolutionKind.Bundler,
2911
+ jsx: ts12.JsxEmit.ReactJSX,
2912
+ strict: true,
2913
+ skipLibCheck: true,
2914
+ allowJs: true,
2915
+ esModuleInterop: true,
2916
+ isolatedModules: true,
2917
+ resolveJsonModule: true,
2918
+ noEmit: true
2919
+ };
2920
+ function loadProject(root, options = {}) {
2921
+ const configFile = options.tsconfig ?? ["tsconfig.json", "jsconfig.json"].map((f) => path4.join(root, f)).find((f) => existsSync3(f));
2922
+ if (configFile) {
2923
+ const isJsConfig = path4.basename(configFile) === "jsconfig.json";
2924
+ const project2 = new Project({
2925
+ tsConfigFilePath: configFile,
2926
+ skipAddingFilesFromTsConfig: options.lazy === true,
2927
+ ...isJsConfig ? { compilerOptions: { allowJs: true, checkJs: false } } : {}
2928
+ });
2929
+ const compilerOptions = project2.getCompilerOptions();
2930
+ if (!options.lazy) return { project: project2, root, configFile, compilerOptions };
2931
+ const parsed = ts12.parseJsonConfigFileContent(
2932
+ ts12.readConfigFile(configFile, ts12.sys.readFile).config,
2933
+ ts12.sys,
2934
+ path4.dirname(configFile),
2935
+ isJsConfig ? { allowJs: true } : void 0,
2936
+ configFile
2937
+ );
2938
+ const lazyFileNames = parsed.fileNames.map((f) => path4.resolve(f));
2939
+ for (const file of lazyFileNames) if (file.endsWith(".d.ts")) project2.addSourceFileAtPath(file);
2940
+ return { project: project2, root, configFile, compilerOptions, lazyFileNames };
2941
+ }
2942
+ const project = new Project({ compilerOptions: DEFAULT_COMPILER_OPTIONS });
2943
+ const files = fg.sync(["**/*.{ts,tsx,js,jsx,mts,cts}"], {
2944
+ cwd: root,
2945
+ ignore: IGNORE_GLOBS,
2946
+ absolute: true,
2947
+ dot: false
2948
+ });
2949
+ if (options.lazy)
2950
+ return {
2951
+ project,
2952
+ root,
2953
+ configFile: void 0,
2954
+ compilerOptions: DEFAULT_COMPILER_OPTIONS,
2955
+ lazyFileNames: files.map((f) => path4.resolve(f))
2956
+ };
2957
+ for (const file of files) project.addSourceFileAtPath(file);
2958
+ project.resolveSourceFileDependencies();
2959
+ return { project, root, configFile: void 0, compilerOptions: project.getCompilerOptions() };
2960
+ }
2961
+ function setSourceText(sf, text) {
2962
+ sf.applyTextChanges([{ span: { start: 0, length: sf.getFullText().length }, newText: text }]);
2963
+ }
2964
+ function ensureLoaded(loaded, files) {
2965
+ if (!loaded.lazyFileNames) return;
2966
+ let added = false;
2967
+ for (const file of files) {
2968
+ if (!loaded.project.getSourceFile(file) && existsSync3(file)) {
2969
+ loaded.project.addSourceFileAtPath(file);
2970
+ added = true;
2971
+ }
2972
+ }
2973
+ if (added) loaded.project.resolveSourceFileDependencies();
2974
+ }
2975
+ function projectSourceFiles(loaded) {
2976
+ return loaded.project.getSourceFiles().map((sf) => sf.compilerNode).filter((sf) => !sf.isDeclarationFile && !sf.fileName.includes("/node_modules/"));
2977
+ }
2978
+
2979
+ // src/project/moduleGraph.ts
2980
+ import { readFileSync as readFileSync3 } from "fs";
2981
+ import path5 from "path";
2982
+ import fg2 from "fast-glob";
2983
+ import { ts as ts13 } from "ts-morph";
2984
+ var SOURCE_EXT_RE = /\.(tsx?|jsx?|mts|cts|mjs|cjs)$/;
2985
+ var ModuleGraph = class {
2986
+ constructor(root) {
2987
+ this.root = root;
2988
+ }
2989
+ root;
2990
+ nodes = /* @__PURE__ */ new Map();
2991
+ importers = /* @__PURE__ */ new Map();
2992
+ /** Client-graph membership: file -> the module it was first reached from (null for "use client" roots). */
2993
+ clientParent = /* @__PURE__ */ new Map();
2994
+ addNode(node) {
2995
+ this.nodes.set(node.file, node);
2996
+ }
2997
+ finalize() {
2998
+ for (const node of this.nodes.values()) {
2999
+ for (const edge of node.edges) {
3000
+ if (!edge.to) continue;
3001
+ const list = this.importers.get(edge.to) ?? [];
3002
+ list.push(edge);
3003
+ this.importers.set(edge.to, list);
3004
+ }
3005
+ }
3006
+ const queue = [];
3007
+ for (const node of this.nodes.values()) {
3008
+ if (node.directive === "use client") {
3009
+ this.clientParent.set(node.file, null);
3010
+ queue.push(node.file);
3011
+ }
3012
+ }
3013
+ while (queue.length > 0) {
3014
+ const file = queue.shift();
3015
+ for (const edge of this.nodes.get(file)?.edges ?? []) {
3016
+ if (!edge.to || edge.kind === "type" || this.clientParent.has(edge.to)) continue;
3017
+ this.clientParent.set(edge.to, file);
3018
+ queue.push(edge.to);
3019
+ }
3020
+ }
3021
+ }
3022
+ importersOf(file) {
3023
+ return this.importers.get(file) ?? [];
3024
+ }
3025
+ isClient(file) {
3026
+ return this.clientParent.has(file);
3027
+ }
3028
+ /** The "use client" module that pulls `file` into the client graph, if it's not a root itself. */
3029
+ clientRootOf(file) {
3030
+ let current = file;
3031
+ let guard = 0;
3032
+ while (current && guard++ < 1e4) {
3033
+ const parent = this.clientParent.get(current);
3034
+ if (parent === null) return current;
3035
+ current = parent;
3036
+ }
3037
+ return void 0;
3038
+ }
3039
+ edgesFrom(file) {
3040
+ return this.nodes.get(file)?.edges ?? [];
3041
+ }
3042
+ edgeFor(file, specifier) {
3043
+ return this.edgesFrom(file).find((e) => e.specifier === specifier && e.kind !== "dynamic");
3044
+ }
3045
+ rel(file) {
3046
+ return path5.relative(this.root, file).split(path5.sep).join("/");
3047
+ }
3048
+ };
3049
+ function collectImports(sf) {
3050
+ const out = [];
3051
+ const lineOf = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
3052
+ for (const statement of sf.statements) {
3053
+ if (ts13.isImportDeclaration(statement) && ts13.isStringLiteral(statement.moduleSpecifier)) {
3054
+ const typeOnly = statement.importClause?.isTypeOnly === true;
3055
+ out.push({
3056
+ specifier: statement.moduleSpecifier.text,
3057
+ kind: typeOnly ? "type" : "static",
3058
+ line: lineOf(statement)
3059
+ });
3060
+ } else if (ts13.isExportDeclaration(statement) && statement.moduleSpecifier && ts13.isStringLiteral(statement.moduleSpecifier)) {
3061
+ out.push({
3062
+ specifier: statement.moduleSpecifier.text,
3063
+ kind: statement.isTypeOnly ? "type" : "reexport",
3064
+ line: lineOf(statement)
3065
+ });
3066
+ } else if (ts13.isImportEqualsDeclaration(statement) && ts13.isExternalModuleReference(statement.moduleReference)) {
3067
+ const expr = statement.moduleReference.expression;
3068
+ if (ts13.isStringLiteral(expr))
3069
+ out.push({ specifier: expr.text, kind: "static", line: lineOf(statement) });
3070
+ }
3071
+ }
3072
+ const visit = (node) => {
3073
+ if (ts13.isCallExpression(node)) {
3074
+ const [arg] = node.arguments;
3075
+ if (arg && ts13.isStringLiteralLike(arg)) {
3076
+ if (node.expression.kind === ts13.SyntaxKind.ImportKeyword)
3077
+ out.push({ specifier: arg.text, kind: "dynamic", line: lineOf(node) });
3078
+ else if (ts13.isIdentifier(node.expression) && node.expression.text === "require") {
3079
+ out.push({ specifier: arg.text, kind: "static", line: lineOf(node) });
3080
+ }
3081
+ }
3082
+ }
3083
+ if (ts13.isImportTypeNode(node)) return;
3084
+ ts13.forEachChild(node, visit);
3085
+ };
3086
+ visit(sf);
3087
+ return out;
3088
+ }
3089
+ var MDX_IMPORT_RE = /^\s*(?:import|export)\s+(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/gm;
3090
+ function buildModuleGraph(loaded, classifier) {
3091
+ const graph = new ModuleGraph(loaded.root);
3092
+ const options = { ...loaded.compilerOptions, allowJs: true };
3093
+ const host = ts13.sys;
3094
+ const cache2 = ts13.createModuleResolutionCache(loaded.root, (f) => f, options);
3095
+ const resolve = (specifier, from) => {
3096
+ const result = ts13.resolveModuleName(specifier, from, options, host, cache2).resolvedModule;
3097
+ if (!result || result.isExternalLibraryImport) return void 0;
3098
+ const resolved = path5.resolve(result.resolvedFileName);
3099
+ if (resolved.includes(`${path5.sep}node_modules${path5.sep}`) || resolved.endsWith(".d.ts"))
3100
+ return void 0;
3101
+ return resolved;
3102
+ };
3103
+ const addFile = (file, sf, inProgram, mdxText) => {
3104
+ const raw = sf ? collectImports(sf) : [...(mdxText ?? "").matchAll(MDX_IMPORT_RE)].map((m) => ({
3105
+ specifier: m[1] ?? "",
3106
+ kind: "static",
3107
+ line: (mdxText ?? "").slice(0, m.index).split("\n").length
3108
+ }));
3109
+ const edges = raw.map((imp) => {
3110
+ const to = resolve(imp.specifier, file);
3111
+ return {
3112
+ from: file,
3113
+ specifier: imp.specifier,
3114
+ kind: imp.kind,
3115
+ to,
3116
+ external: to ? void 0 : classifier.classify(imp.specifier, file),
3117
+ line: imp.line
3118
+ };
3119
+ });
3120
+ graph.addNode({
3121
+ file,
3122
+ directive: sf ? getModuleDirective(sf).directive : void 0,
3123
+ inProgram,
3124
+ edges
3125
+ });
3126
+ };
3127
+ if (loaded.lazyFileNames) {
3128
+ const allowJs = loaded.compilerOptions.allowJs === true;
3129
+ const queue = [...loaded.lazyFileNames];
3130
+ for (const file of queue) {
3131
+ if (graph.nodes.has(file) || !SOURCE_EXT_RE.test(file)) continue;
3132
+ if (file.endsWith(".d.ts") || file.includes(`${path5.sep}node_modules${path5.sep}`)) continue;
3133
+ let text;
3134
+ try {
3135
+ text = readFileSync3(file, "utf8");
3136
+ } catch {
3137
+ continue;
3138
+ }
3139
+ const kind = /\.(tsx|jsx)$/.test(file) ? ts13.ScriptKind.TSX : /\.(js|mjs|cjs)$/.test(file) ? ts13.ScriptKind.JS : ts13.ScriptKind.TS;
3140
+ addFile(file, ts13.createSourceFile(file, text, ts13.ScriptTarget.Latest, true, kind), true);
3141
+ for (const edge of graph.nodes.get(file)?.edges ?? []) {
3142
+ if (edge.to && (allowJs || !/\.(jsx?|mjs|cjs)$/.test(edge.to))) queue.push(edge.to);
3143
+ }
3144
+ }
3145
+ } else {
3146
+ for (const sf of projectSourceFiles(loaded)) addFile(path5.resolve(sf.fileName), sf, true);
3147
+ }
3148
+ const extra = fg2.sync(["**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts,mdx}"], {
3149
+ cwd: loaded.root,
3150
+ ignore: [...IGNORE_GLOBS, "**/*.d.ts"],
3151
+ absolute: true
3152
+ });
3153
+ for (const file of extra.map((f) => path5.resolve(f))) {
3154
+ if (graph.nodes.has(file)) continue;
3155
+ let text;
3156
+ try {
3157
+ text = readFileSync3(file, "utf8");
3158
+ } catch {
3159
+ continue;
3160
+ }
3161
+ if (file.endsWith(".mdx")) addFile(file, void 0, false, text);
3162
+ else if (SOURCE_EXT_RE.test(file)) {
3163
+ const kind = /\.(tsx|jsx)$/.test(file) ? ts13.ScriptKind.TSX : ts13.ScriptKind.TS;
3164
+ addFile(file, ts13.createSourceFile(file, text, ts13.ScriptTarget.Latest, true, kind), false);
3165
+ }
3166
+ }
3167
+ graph.finalize();
3168
+ return graph;
3169
+ }
3170
+
3171
+ // src/project/packages.ts
3172
+ import { existsSync as existsSync4, openSync, readFileSync as readFileSync4, readSync, closeSync, statSync } from "fs";
3173
+ import { builtinModules } from "module";
3174
+ import path6 from "path";
3175
+ var STYLESHEET_RE = /\.(css|scss|sass|less|styl|pcss)$/;
3176
+ var CSS_MODULE_RE = /\.module\.(css|scss|sass|less|styl|pcss)$/;
3177
+ var ASSET_RE = /\.(svg|png|jpe?g|gif|webp|avif|ico|bmp|mp4|webm|mp3|wav|woff2?|ttf|otf)$/;
3178
+ var PackageClassifier = class {
3179
+ constructor(framework, extraClientOnly) {
3180
+ this.framework = framework;
3181
+ this.extraClientOnly = extraClientOnly;
3182
+ }
3183
+ framework;
3184
+ extraClientOnly;
3185
+ cache = /* @__PURE__ */ new Map();
3186
+ /** Classifies a non-relative specifier (or an unresolved relative one with an asset extension). */
3187
+ classify(specifier, fromFile) {
3188
+ const key = `${path6.dirname(fromFile)}\0${specifier}`;
3189
+ const cached = this.cache.get(key);
3190
+ if (cached) return cached;
3191
+ const info = {
3192
+ specifier,
3193
+ packageName: packageNameOf(specifier),
3194
+ ...this.compute(specifier, fromFile)
3195
+ };
3196
+ this.cache.set(key, info);
3197
+ return info;
3198
+ }
3199
+ compute(specifier, fromFile) {
3200
+ if (CSS_MODULE_RE.test(specifier)) return { kind: "css-module" };
3201
+ if (STYLESHEET_RE.test(specifier)) return { kind: "stylesheet" };
3202
+ if (specifier.endsWith(".json")) return { kind: "json" };
3203
+ if (ASSET_RE.test(specifier)) return { kind: "asset" };
3204
+ if (specifier.startsWith(".") || specifier.startsWith("/")) return { kind: "unresolved" };
3205
+ const name = packageNameOf(specifier);
3206
+ if (specifier.startsWith("node:") || builtinModules.includes(name) || builtinModules.includes(specifier)) {
3207
+ return { kind: "builtin" };
3208
+ }
3209
+ if (name === "react") return { kind: "react" };
3210
+ if (name === "react-dom") return { kind: "react-dom" };
3211
+ if (specifier === "server-only") return { kind: "server-only-marker" };
3212
+ if (specifier === "client-only") return { kind: "client-only-marker" };
3213
+ if (this.framework.classifyModule(specifier)) return { kind: "framework" };
3214
+ if (matchesPackageList(specifier, CSS_IN_JS_PACKAGES)) return { kind: "css-in-js" };
3215
+ if (matchesPackageList(specifier, this.extraClientOnly))
3216
+ return { kind: "client-list", detail: "clientOnlyPackages config" };
3217
+ if (matchesPackageList(specifier, BUILTIN_SERVER_SAFE_PACKAGES))
3218
+ return { kind: "server-safe-list" };
3219
+ if (matchesPackageList(specifier, BUILTIN_CLIENT_ONLY_PACKAGES)) return { kind: "client-list" };
3220
+ const pkgDir = findPackageDir(name, path6.dirname(fromFile));
3221
+ if (!pkgDir) return { kind: "unresolved", detail: `package ${name} is not installed` };
3222
+ const pkg = readPackageJson(pkgDir);
3223
+ if (!pkg) return { kind: "unresolved", detail: `can't read ${name}/package.json` };
3224
+ const entry = resolveEntry(pkgDir, pkg, specifier.slice(name.length));
3225
+ if (entry && startsWithUseClient(entry)) return { kind: "use-client-entry" };
3226
+ const deps = { ...pkg.dependencies, ...pkg.peerDependencies, ...pkg.optionalDependencies };
3227
+ if ("react" in deps || "react-dom" in deps || "next" in deps || "preact" in deps) {
3228
+ return {
3229
+ kind: "react-unknown",
3230
+ detail: `${name} depends on React and isn't known to be server-safe`
3231
+ };
3232
+ }
3233
+ return { kind: "neutral" };
3234
+ }
3235
+ };
3236
+ function findPackageDir(name, fromDir) {
3237
+ let dir = fromDir;
3238
+ for (; ; ) {
3239
+ const candidate = path6.join(dir, "node_modules", name);
3240
+ if (existsSync4(path6.join(candidate, "package.json"))) return candidate;
3241
+ const parent = path6.dirname(dir);
3242
+ if (parent === dir) return void 0;
3243
+ dir = parent;
3244
+ }
3245
+ }
3246
+ function readPackageJson(dir) {
3247
+ try {
3248
+ return JSON.parse(readFileSync4(path6.join(dir, "package.json"), "utf8"));
3249
+ } catch {
3250
+ return void 0;
3251
+ }
3252
+ }
3253
+ var CONDITIONS = ["browser", "import", "module", "default", "require"];
3254
+ function pickConditional(value) {
3255
+ if (typeof value === "string") return value;
3256
+ if (Array.isArray(value)) {
3257
+ for (const v of value) {
3258
+ const picked = pickConditional(v);
3259
+ if (picked) return picked;
3260
+ }
3261
+ return void 0;
3262
+ }
3263
+ if (value && typeof value === "object") {
3264
+ const record = value;
3265
+ for (const condition of CONDITIONS) {
3266
+ if (condition in record) {
3267
+ const picked = pickConditional(record[condition]);
3268
+ if (picked) return picked;
3269
+ }
3270
+ }
3271
+ }
3272
+ return void 0;
3273
+ }
3274
+ function resolveEntry(pkgDir, pkg, subpath) {
3275
+ const key = subpath ? `.${subpath}` : ".";
3276
+ let target;
3277
+ if (pkg.exports !== void 0) {
3278
+ const exportsField = pkg.exports;
3279
+ const isSubpathMap = exportsField !== null && typeof exportsField === "object" && !Array.isArray(exportsField) && Object.keys(exportsField).some((k) => k.startsWith("."));
3280
+ if (isSubpathMap) {
3281
+ const map = exportsField;
3282
+ if (key in map) target = pickConditional(map[key]);
3283
+ else {
3284
+ for (const [pattern, value] of Object.entries(map)) {
3285
+ const star = pattern.indexOf("*");
3286
+ if (star === -1) continue;
3287
+ const prefix = pattern.slice(0, star);
3288
+ const suffix = pattern.slice(star + 1);
3289
+ if (key.startsWith(prefix) && key.endsWith(suffix)) {
3290
+ const matched = key.slice(prefix.length, key.length - suffix.length);
3291
+ target = pickConditional(value)?.replace("*", matched);
3292
+ break;
3293
+ }
3294
+ }
3295
+ }
3296
+ } else if (key === ".") {
3297
+ target = pickConditional(exportsField);
3298
+ }
3299
+ } else if (key === ".") {
3300
+ target = pkg.module ?? pkg.main ?? "index.js";
3301
+ } else {
3302
+ target = subpath.slice(1);
3303
+ }
3304
+ if (!target) return void 0;
3305
+ const base = path6.join(pkgDir, target);
3306
+ for (const candidate of [base, `${base}.js`, `${base}.mjs`, path6.join(base, "index.js")]) {
3307
+ if (existsSync4(candidate) && statSync(candidate).isFile()) return candidate;
3308
+ }
3309
+ return void 0;
3310
+ }
3311
+ function startsWithUseClient(file) {
3312
+ let fd;
3313
+ try {
3314
+ fd = openSync(file, "r");
3315
+ const buffer = Buffer.alloc(1024);
3316
+ const read = readSync(fd, buffer, 0, buffer.length, 0);
3317
+ const head = buffer.subarray(0, read).toString("utf8").replace(/^\uFEFF/, "").replace(/^(\s|\/\/[^\n]*\n|\/\*[\s\S]*?\*\/)*/, "");
3318
+ return /^(['"])use client\1/.test(head) || /^(['"])use strict\1;?\s*(['"])use client\2/.test(head);
3319
+ } catch {
3320
+ return false;
3321
+ } finally {
3322
+ if (fd !== void 0) closeSync(fd);
3323
+ }
3324
+ }
3325
+
3326
+ // src/transform/emit.ts
3327
+ import MagicString from "magic-string";
3328
+ import { ts as ts16 } from "ts-morph";
3329
+
3330
+ // src/transform/text.ts
3331
+ import { ts as ts14 } from "ts-morph";
3332
+ function detectStyle(sf) {
3333
+ let quote = '"';
3334
+ let semi = true;
3335
+ const first = sf.statements.find((s) => ts14.isImportDeclaration(s) || ts14.isExpressionStatement(s));
3336
+ if (first) {
3337
+ const text = first.getText(sf);
3338
+ semi = text.trimEnd().endsWith(";");
3339
+ const literal = ts14.isImportDeclaration(first) ? first.moduleSpecifier : first.expression;
3340
+ const raw = literal.getText(sf);
3341
+ if (raw.startsWith("'")) quote = "'";
3342
+ }
3343
+ let indent = " ";
3344
+ let smallest = Number.POSITIVE_INFINITY;
3345
+ for (const match of sf.text.matchAll(/\n([ \t]+)\S/g)) {
3346
+ const ws = match[1] ?? "";
3347
+ if (ws.startsWith(" ")) {
3348
+ indent = " ";
3349
+ break;
3350
+ }
3351
+ if (ws.length < smallest) {
3352
+ smallest = ws.length;
3353
+ indent = " ".repeat(ws.length);
3354
+ }
3355
+ }
3356
+ return { quote, semi, indent };
3357
+ }
3358
+ function lineEndingOf(text) {
3359
+ const i = text.indexOf("\n");
3360
+ return i > 0 && text[i - 1] === "\r" ? "\r\n" : "\n";
3361
+ }
3362
+ function hasMixedLineEndings(text) {
3363
+ return text.includes("\r\n") && /(?:^|[^\r])\n/.test(text);
3364
+ }
3365
+ function withLineEnding(text, eol) {
3366
+ return text.replace(/\r?\n/g, eol);
3367
+ }
3368
+ function lineStartOf(text, pos) {
3369
+ const i = text.lastIndexOf("\n", pos - 1);
3370
+ return i === -1 ? 0 : i + 1;
3371
+ }
3372
+ function lineEndOf(text, pos) {
3373
+ const i = text.indexOf("\n", pos);
3374
+ return i === -1 ? text.length : i;
3375
+ }
3376
+ function indentationOfLine(text, pos) {
3377
+ const start = lineStartOf(text, pos);
3378
+ const match = /^[ \t]*/.exec(text.slice(start));
3379
+ return match?.[0] ?? "";
3380
+ }
3381
+ function hasMultilineLiteral(node, sf) {
3382
+ let found = false;
3383
+ const visit = (n) => {
3384
+ if (found) return;
3385
+ if ((ts14.isTemplateLiteral(n) || ts14.isStringLiteral(n) || ts14.isNoSubstitutionTemplateLiteral(n)) && n.getText(sf).includes("\n")) {
3386
+ found = true;
3387
+ return;
3388
+ }
3389
+ ts14.forEachChild(n, visit);
3390
+ };
3391
+ visit(node);
3392
+ return found;
3393
+ }
3394
+ function reindent(text, fromIndent, toIndent) {
3395
+ const lines = text.split("\n");
3396
+ return lines.map((line, i) => {
3397
+ if (i === 0) return line;
3398
+ if (line.trim() === "") return "";
3399
+ if (line.startsWith(fromIndent)) return toIndent + line.slice(fromIndent.length);
3400
+ return toIndent + line.trimStart();
3401
+ }).join("\n");
3402
+ }
3403
+ function statementLineRange(sf, node) {
3404
+ const text = sf.text;
3405
+ let start = node.getStart(sf);
3406
+ const comments = ts14.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
3407
+ for (const comment of comments) {
3408
+ const before2 = text.slice(lineStartOf(text, comment.pos), comment.pos);
3409
+ if (before2.trim() === "") {
3410
+ start = Math.min(start, comment.pos);
3411
+ break;
3412
+ }
3413
+ }
3414
+ const end = node.getEnd();
3415
+ const lineStart = lineStartOf(text, start);
3416
+ const lineEnd = lineEndOf(text, end);
3417
+ const before = text.slice(lineStart, start);
3418
+ const after = text.slice(end, lineEnd);
3419
+ if (before.trim() === "" && after.trim() === "") {
3420
+ return { start: lineStart, end: Math.min(text.length, lineEnd + 1), whole: true };
3421
+ }
3422
+ return { start, end, whole: false };
3423
+ }
3424
+
3425
+ // src/transform/typeRefs.ts
3426
+ import { ts as ts15 } from "ts-morph";
3427
+ function typeReferenceHeads(node) {
3428
+ const out = [];
3429
+ const head = (name) => {
3430
+ let current = name;
3431
+ while (ts15.isQualifiedName(current) || ts15.isPropertyAccessExpression(current)) {
3432
+ current = ts15.isQualifiedName(current) ? current.left : current.expression;
3433
+ }
3434
+ return ts15.isIdentifier(current) ? current : void 0;
3435
+ };
3436
+ const visit = (n) => {
3437
+ if (ts15.isTypeReferenceNode(n)) {
3438
+ const id = head(n.typeName);
3439
+ if (id) out.push(id);
3440
+ } else if (ts15.isExpressionWithTypeArguments(n) && ts15.isHeritageClause(n.parent)) {
3441
+ const id = head(n.expression);
3442
+ if (id) out.push(id);
3443
+ } else if (ts15.isTypeQueryNode(n)) {
3444
+ const id = head(n.exprName);
3445
+ if (id) out.push(id);
3446
+ }
3447
+ ts15.forEachChild(n, visit);
3448
+ };
3449
+ visit(node);
3450
+ return out;
3451
+ }
3452
+ var TypeRefCollector = class {
3453
+ constructor(sf, checker, imports, moduleDecls, islandContent) {
3454
+ this.sf = sf;
3455
+ this.checker = checker;
3456
+ this.imports = imports;
3457
+ this.moduleDecls = moduleDecls;
3458
+ this.islandContent = islandContent;
3459
+ }
3460
+ sf;
3461
+ checker;
3462
+ imports;
3463
+ moduleDecls;
3464
+ islandContent;
3465
+ deps = { imports: /* @__PURE__ */ new Set(), moduleDecls: /* @__PURE__ */ new Set(), synthetic: /* @__PURE__ */ new Map() };
3466
+ classify(symbol, name, node) {
3467
+ if (!symbol) return void 0;
3468
+ const binding = this.imports.bySymbol.get(symbol);
3469
+ if (binding) {
3470
+ this.deps.imports.add(binding);
3471
+ return void 0;
3472
+ }
3473
+ const decl = symbol.declarations?.[0];
3474
+ if (!decl) return void 0;
3475
+ if (decl.getSourceFile() !== this.sf) return void 0;
3476
+ if (this.islandContent.some((c) => containsRange(c, decl))) return void 0;
3477
+ let top = decl;
3478
+ while (top.parent && top.parent !== this.sf) top = top.parent;
3479
+ const moduleDecl = this.moduleDecls.find((d) => d.statement === top);
3480
+ if (moduleDecl) {
3481
+ if (!this.deps.moduleDecls.has(moduleDecl)) {
3482
+ this.deps.moduleDecls.add(moduleDecl);
3483
+ if (!moduleDecl.exported) {
3484
+ const failure = this.collectFromNode(moduleDecl.statement);
3485
+ if (failure) return failure;
3486
+ }
3487
+ }
3488
+ return void 0;
3489
+ }
3490
+ return {
3491
+ message: `the type \`${name}\` is declared inside the component and can't be referenced from the island`,
3492
+ node
3493
+ };
3494
+ }
3495
+ /** Type names used inside a node that moves into the island. */
3496
+ collectFromNode(node, exclude = []) {
3497
+ for (const id of typeReferenceHeads(node)) {
3498
+ if (exclude.some((e) => containsRange(e, id))) continue;
3499
+ const failure = this.classify(this.checker.getSymbolAtLocation(id), id.text, id);
3500
+ if (failure) return failure;
3501
+ }
3502
+ return void 0;
3503
+ }
3504
+ /**
3505
+ * Type names inside a printed type (from checker.typeToString at `location`). `import("x").Y`
3506
+ * forms become `import type { Y } from "x"` when x is a package name.
3507
+ */
3508
+ collectFromPrinted(printed, location) {
3509
+ let text = printed;
3510
+ const importTypeRe = /import\("([^"]+)"\)\.([A-Za-z_$][\w$]*)/g;
3511
+ for (const match of printed.matchAll(importTypeRe)) {
3512
+ const spec = match[1] ?? "";
3513
+ const name = match[2] ?? "";
3514
+ if (spec.startsWith(".") || spec.startsWith("/")) {
3515
+ return { message: `can't name the type \`${name}\` from ${spec} in the generated file` };
3516
+ }
3517
+ const names = this.deps.synthetic.get(spec) ?? /* @__PURE__ */ new Set();
3518
+ names.add(name);
3519
+ this.deps.synthetic.set(spec, names);
3520
+ text = text.replace(match[0], name);
3521
+ }
3522
+ const parsed = ts15.createSourceFile(
3523
+ "__type.ts",
3524
+ `type __T = ${text};`,
3525
+ ts15.ScriptTarget.Latest,
3526
+ true
3527
+ );
3528
+ const inScope = /* @__PURE__ */ new Map();
3529
+ for (const symbol of this.checker.getSymbolsInScope(
3530
+ location,
3531
+ ts15.SymbolFlags.Type | ts15.SymbolFlags.Namespace | ts15.SymbolFlags.Alias
3532
+ )) {
3533
+ if (!inScope.has(symbol.getName())) inScope.set(symbol.getName(), symbol);
3534
+ }
3535
+ for (const id of typeReferenceHeads(parsed)) {
3536
+ if (id.text === "__T") continue;
3537
+ if (ts15.isTypeQueryNode(id.parent) || ts15.isQualifiedName(id.parent) && ts15.isTypeQueryNode(id.parent.parent)) {
3538
+ return {
3539
+ message: `a prop's type refers to the value \`${id.text}\` (typeof), which can't be named in the generated file`
3540
+ };
3541
+ }
3542
+ if ([...this.deps.synthetic.values()].some((s) => s.has(id.text))) continue;
3543
+ const symbol = inScope.get(id.text);
3544
+ if (!symbol) {
3545
+ return {
3546
+ message: `can't name the type \`${id.text}\` in the generated file (it isn't imported here)`
3547
+ };
3548
+ }
3549
+ const failure = this.classify(symbol, id.text, void 0);
3550
+ if (failure) return failure;
3551
+ }
3552
+ return { text };
3553
+ }
3554
+ };
3555
+
3556
+ // src/transform/emit.ts
3557
+ function mergeRanges(ranges) {
3558
+ const sorted = [...ranges].sort((a, b) => a.start - b.start);
3559
+ const out = [];
3560
+ for (const r of sorted) {
3561
+ const last = out[out.length - 1];
3562
+ if (last && r.start <= last.end) last.end = Math.max(last.end, r.end);
3563
+ else out.push({ ...r });
3564
+ }
3565
+ return out;
3566
+ }
3567
+ var inAny = (ranges, pos) => ranges.some((r) => pos >= r.start && pos < r.end);
3568
+ function assignNames(plans, taken, renames) {
3569
+ const islands = plans.flatMap((plan) => plan.islands.map((island) => ({ plan, island })));
3570
+ for (const { plan, island } of islands) {
3571
+ const base = islandBaseName(plan.component.name, island.rootTag);
3572
+ let name = base;
3573
+ for (let n = 2; taken.has(name); n++) name = `${base}${n}`;
3574
+ taken.add(name);
3575
+ island.name = name;
3576
+ }
3577
+ for (const { island } of islands) {
3578
+ const renamed = renames[island.name];
3579
+ if (renamed === void 0 || renamed === island.name) continue;
3580
+ if (!/^[A-Z][A-Za-z0-9_$]*$/.test(renamed)) return `"${renamed}" is not a valid component name`;
3581
+ if (taken.has(renamed)) return `"${renamed}" is already used in this file`;
3582
+ taken.delete(island.name);
3583
+ taken.add(renamed);
3584
+ island.name = renamed;
3585
+ }
3586
+ return void 0;
3587
+ }
3588
+ function importClauseText(entries, allTypeOnly) {
3589
+ const def = entries.find((e) => e.binding.imported === "default");
3590
+ const ns = entries.find((e) => e.binding.imported === "*");
3591
+ const named = entries.filter(
3592
+ (e) => e.binding.imported !== "default" && e.binding.imported !== "*"
3593
+ );
3594
+ const parts = [];
3595
+ if (def) parts.push(def.binding.local);
3596
+ if (ns) parts.push(`* as ${ns.binding.local}`);
3597
+ if (named.length > 0) {
3598
+ const items = named.map((e) => {
3599
+ const prefix = e.typeOnly && !allTypeOnly ? "type " : "";
3600
+ const name = e.binding.imported === e.binding.local ? e.binding.local : `${e.binding.imported} as ${e.binding.local}`;
3601
+ return prefix + name;
3602
+ });
3603
+ parts.push(`{ ${items.join(", ")} }`);
3604
+ }
3605
+ return parts.join(", ");
3606
+ }
3607
+ function importStatement(decl, sf, entries, style, specifierText) {
3608
+ const allTypeOnly = entries.every((e) => e.typeOnly);
3609
+ const attributes = decl.attributes ? ` ${decl.attributes.getText(sf)}` : "";
3610
+ const semi = style.semi ? ";" : "";
3611
+ const def = entries.find((e) => e.binding.imported === "default");
3612
+ const rest = entries.filter((e) => e !== def);
3613
+ if (allTypeOnly && def && rest.length > 0) {
3614
+ return [
3615
+ `import type ${def.binding.local} from ${specifierText}${attributes}${semi}`,
3616
+ `import type ${importClauseText(rest, true)} from ${specifierText}${attributes}${semi}`
3617
+ ];
3618
+ }
3619
+ return [
3620
+ `import ${allTypeOnly ? "type " : ""}${importClauseText(entries, allTypeOnly)} from ${specifierText}${attributes}${semi}`
3621
+ ];
3622
+ }
3623
+ function emitSplit(input) {
3624
+ const { ctx, shape, imports, plans } = input;
3625
+ const { sf, checker } = ctx;
3626
+ const text = sf.text;
3627
+ const style = detectStyle(sf);
3628
+ const q = style.quote;
3629
+ const semi = style.semi ? ";" : "";
3630
+ const taken = /* @__PURE__ */ new Set();
3631
+ for (const b of imports.bindings) taken.add(b.local);
3632
+ for (const d of shape.moduleDecls) d.names.forEach((n) => taken.add(n));
3633
+ for (const c of shape.components) taken.add(c.name);
3634
+ const nameProblem = assignNames(plans, taken, input.renames ?? {});
3635
+ if (nameProblem) return skip("unsupported-pattern", nameProblem);
3636
+ const islands = plans.flatMap((p) => p.islands);
3637
+ const islandContent = islands.flatMap((i) => [
3638
+ i.root.node,
3639
+ ...i.units.map((u) => u.node)
3640
+ ]);
3641
+ const collector = new TypeRefCollector(sf, checker, imports, shape.moduleDecls, islandContent);
3642
+ const valueImports = /* @__PURE__ */ new Set();
3643
+ const movedComponents = input.movedComponents ?? [];
3644
+ const copiedDecls = new Set(movedComponents);
3645
+ for (const decl of movedComponents) islandContent.push(decl.statement);
3646
+ for (const island of islands) {
3647
+ island.valueImports.forEach((b) => valueImports.add(b));
3648
+ island.moduleDecls.forEach((d) => copiedDecls.add(d));
3649
+ }
3650
+ const allDecls = [...shape.moduleDecls, ...shape.componentDecls.values()];
3651
+ for (const decl of copiedDecls)
3652
+ if (decl.component && !islandContent.includes(decl.statement))
3653
+ islandContent.push(decl.statement);
3654
+ const declQueue = [...copiedDecls];
3655
+ while (declQueue.length > 0) {
3656
+ const decl = declQueue.pop();
3657
+ let failure;
3658
+ forEachValueRef(decl.statement, (id) => {
3659
+ const symbol = symbolOfRef(checker, id);
3660
+ if (!symbol) return;
3661
+ const binding = imports.bySymbol.get(symbol);
3662
+ if (binding) {
3663
+ valueImports.add(binding);
3664
+ return;
3665
+ }
3666
+ const d = symbol.declarations?.[0];
3667
+ if (!d || d.getSourceFile() !== sf || containsRange(decl.statement, d)) return;
3668
+ const other = allDecls.find((m) => containsRange(m.statement, d));
3669
+ if (other && !copiedDecls.has(other)) {
3670
+ copiedDecls.add(other);
3671
+ if (other.component) islandContent.push(other.statement);
3672
+ declQueue.push(other);
3673
+ } else if (!other) {
3674
+ failure = `copied helper refers to \`${id.text}\`, which can't be moved`;
3675
+ }
3676
+ });
3677
+ if (failure) return skip("unsupported-pattern", failure, locOf(ctx, decl.statement));
3678
+ }
3679
+ const slotNodes = islands.flatMap((i) => i.slots.map((slot) => slot.j.node));
3680
+ for (const node of islandContent) {
3681
+ const failure = collector.collectFromNode(node, slotNodes);
3682
+ if (failure)
3683
+ return skip(
3684
+ "unsupported-pattern",
3685
+ failure.message,
3686
+ failure.node ? locOf(ctx, failure.node) : void 0
3687
+ );
3688
+ }
3689
+ for (const decl of copiedDecls) {
3690
+ const failure = collector.collectFromNode(decl.statement);
3691
+ if (failure) return skip("unsupported-pattern", failure.message, locOf(ctx, decl.statement));
3692
+ }
3693
+ const extraTypeImports = /* @__PURE__ */ new Map();
3694
+ let reactNodeType = "ReactNode";
3695
+ if (!ctx.isJs && islands.some((i) => i.slots.length > 0)) {
3696
+ const fromReact = (b) => b.module.kind === "external" && b.module.info.kind === "react";
3697
+ const named = imports.bindings.find((b) => fromReact(b) && b.imported === "ReactNode");
3698
+ const namespace = imports.bindings.find(
3699
+ (b) => fromReact(b) && (b.imported === "*" || b.imported === "default")
3700
+ );
3701
+ if (named) {
3702
+ reactNodeType = named.local;
3703
+ collector.deps.imports.add(named);
3704
+ } else if (namespace) {
3705
+ reactNodeType = `${namespace.local}.ReactNode`;
3706
+ collector.deps.imports.add(namespace);
3707
+ } else if (taken.has("ReactNode")) {
3708
+ return skip(
3709
+ "unsupported-pattern",
3710
+ "`ReactNode` is already declared in this file, so slot props can't be typed"
3711
+ );
3712
+ } else {
3713
+ extraTypeImports.set("react", ["ReactNode"]);
3714
+ }
3715
+ }
3716
+ const signatures = /* @__PURE__ */ new Map();
3717
+ for (const island of islands) {
3718
+ if (island.props.length === 0 && island.slots.length === 0) {
3719
+ signatures.set(island, "");
3720
+ continue;
3721
+ }
3722
+ const names = [
3723
+ ...island.props.map((p) => p.name),
3724
+ ...island.slots.map((slot) => slot.name)
3725
+ ].join(", ");
3726
+ if (ctx.isJs) {
3727
+ signatures.set(island, `{ ${names} }`);
3728
+ continue;
3729
+ }
3730
+ const members = [];
3731
+ for (const prop of island.props) {
3732
+ if (prop.picked) {
3733
+ const result2 = collector.collectFromPrinted(prop.picked.typeText, island.root.node);
3734
+ if ("message" in result2)
3735
+ return skip(
3736
+ "unsupported-pattern",
3737
+ `prop \`${prop.name}\`: ${result2.message}`,
3738
+ locOf(ctx, prop.refNode)
3739
+ );
3740
+ members.push(`${prop.name}: ${result2.text}`);
3741
+ continue;
3742
+ }
3743
+ const printed = checker.typeToString(
3744
+ prop.type,
3745
+ island.root.node,
3746
+ ts16.TypeFormatFlags.NoTruncation | ts16.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope
3747
+ );
3748
+ const result = collector.collectFromPrinted(printed, island.root.node);
3749
+ if ("message" in result)
3750
+ return skip(
3751
+ "unsupported-pattern",
3752
+ `prop \`${prop.name}\`: ${result.message}`,
3753
+ locOf(ctx, prop.refNode)
3754
+ );
3755
+ members.push(`${prop.name}: ${result.text}`);
3756
+ }
3757
+ for (const slot of island.slots) members.push(`${slot.name}: ${reactNodeType}`);
3758
+ signatures.set(island, `{ ${names} }: { ${members.join("; ")} }`);
3759
+ }
3760
+ const allCopied = /* @__PURE__ */ new Set([...copiedDecls]);
3761
+ for (const decl of collector.deps.moduleDecls) if (!decl.exported) allCopied.add(decl);
3762
+ const exportedTypeImports = [...collector.deps.moduleDecls].filter((d) => d.exported).flatMap((d) => d.names);
3763
+ const islandLines = [];
3764
+ const directiveQuote = q;
3765
+ islandLines.push(`${directiveQuote}use client${directiveQuote}${semi}`);
3766
+ for (const decl of imports.declarations) {
3767
+ const entries = [];
3768
+ for (const binding of imports.bindings) {
3769
+ if (binding.declaration !== decl) continue;
3770
+ const usedAsValue = valueImports.has(binding);
3771
+ const usedAsType = collector.deps.imports.has(binding);
3772
+ if (!usedAsValue && !usedAsType) continue;
3773
+ entries.push({ binding, typeOnly: binding.typeOnly || !usedAsValue });
3774
+ }
3775
+ if (entries.length === 0) continue;
3776
+ const spec = decl.moduleSpecifier;
3777
+ const rebased = input.rebaseSpecifier(spec.text);
3778
+ const specText = rebased === spec.text ? spec.getText(sf) : `${q}${rebased}${q}`;
3779
+ const all = imports.bindings.filter((b) => b.declaration === decl);
3780
+ const sameAsOriginal = rebased === spec.text && entries.length === all.length && entries.every((e) => e.typeOnly === e.binding.typeOnly);
3781
+ const extra = extraTypeImports.get(spec.text);
3782
+ if (extra && !entries.every((e) => e.typeOnly) && decl.importClause?.namedBindings && !ts16.isNamespaceImport(decl.importClause.namedBindings)) {
3783
+ extraTypeImports.delete(spec.text);
3784
+ const clause = importClauseText(entries, false).replace(
3785
+ / \}$/,
3786
+ `, ${extra.map((n) => `type ${n}`).join(", ")} }`
3787
+ );
3788
+ islandLines.push(
3789
+ `import ${clause} from ${specText}${decl.attributes ? ` ${decl.attributes.getText(sf)}` : ""}${semi}`
3790
+ );
3791
+ continue;
3792
+ }
3793
+ if (sameAsOriginal) islandLines.push(decl.getText(sf));
3794
+ else islandLines.push(...importStatement(decl, sf, entries, style, specText));
3795
+ }
3796
+ for (const [spec, names] of extraTypeImports) {
3797
+ islandLines.push(`import type { ${names.join(", ")} } from ${q}${spec}${q}${semi}`);
3798
+ }
3799
+ if (exportedTypeImports.length > 0) {
3800
+ islandLines.push(
3801
+ `import type { ${exportedTypeImports.join(", ")} } from ${q}${input.originalSpecifier}${q}${semi}`
3802
+ );
3803
+ }
3804
+ for (const [spec, names] of collector.deps.synthetic) {
3805
+ islandLines.push(`import type { ${[...names].join(", ")} } from ${q}${spec}${q}${semi}`);
3806
+ }
3807
+ const blocks = [islandLines.join("\n")];
3808
+ const moved = new Set(movedComponents);
3809
+ for (const decl of [...allDecls].sort(
3810
+ (a, b) => a.statement.getStart(sf) - b.statement.getStart(sf)
3811
+ )) {
3812
+ if (!allCopied.has(decl)) continue;
3813
+ let declText = statementSource(decl.statement, sf);
3814
+ if (decl.component) declText = withoutExportModifiers(decl.statement, sf);
3815
+ if (moved.has(decl)) declText = `export ${declText}`;
3816
+ blocks.push(declText);
3817
+ }
3818
+ for (const island of islands) {
3819
+ blocks.push(renderIsland(island, signatures.get(island) ?? "", sf, style));
3820
+ }
3821
+ const islandText = `${blocks.join("\n\n")}
3822
+ `;
3823
+ const ms = new MagicString(text);
3824
+ const removed = [];
3825
+ const directive = getModuleDirective(sf).statement;
3826
+ if (directive) {
3827
+ let end = directive.getEnd();
3828
+ while (end < text.length && /\s/.test(text[end] ?? "")) end++;
3829
+ removed.push({ start: directive.getStart(sf), end });
3830
+ }
3831
+ const unitRanges = [];
3832
+ for (const plan of plans) {
3833
+ for (const unit of plan.removedUnits) unitRanges.push(statementLineRange(sf, unit.node));
3834
+ }
3835
+ for (const r of mergeRanges(unitRanges)) removed.push(collapseBlankLines(text, r));
3836
+ const replacedRoots = islands.map((i) => ({
3837
+ start: i.root.node.getStart(sf),
3838
+ end: i.root.node.getEnd()
3839
+ }));
3840
+ const keptInside = slotNodes.map((n) => ({ start: n.getStart(sf), end: n.getEnd() }));
3841
+ const moduleRemovals = /* @__PURE__ */ new Set();
3842
+ for (const plan of plans) plan.movedModuleDecls.forEach((d) => moduleRemovals.add(d));
3843
+ for (const decl of movedComponents) {
3844
+ removed.push(collapseBlankLines(text, statementLineRange(sf, decl.statement)));
3845
+ }
3846
+ const baseRemoved = () => [
3847
+ ...removed,
3848
+ ...replacedRoots,
3849
+ ...[...moduleRemovals].map((d) => statementLineRange(sf, d.statement))
3850
+ ];
3851
+ for (let changed = true; changed; ) {
3852
+ changed = false;
3853
+ for (const decl of allCopied) {
3854
+ if (moduleRemovals.has(decl) || decl.exported || moved.has(decl)) continue;
3855
+ if (!isReferencedOutside(decl, sf, checker, baseRemoved(), keptInside)) {
3856
+ moduleRemovals.add(decl);
3857
+ changed = true;
3858
+ }
3859
+ }
3860
+ }
3861
+ for (const decl of moduleRemovals) {
3862
+ if (isReferencedOutside(decl, sf, checker, baseRemoved(), keptInside)) {
3863
+ return skip(
3864
+ "unsupported-pattern",
3865
+ `client-only helper ${decl.names.join(", ")} is still used by the server part`,
3866
+ locOf(ctx, decl.statement)
3867
+ );
3868
+ }
3869
+ removed.push(collapseBlankLines(text, statementLineRange(sf, decl.statement)));
3870
+ }
3871
+ const allRemoved = [...removed, ...replacedRoots];
3872
+ const keyRanges = islands.flatMap(
3873
+ (i) => i.keyAttr ? [{ start: i.keyAttr.getStart(sf), end: i.keyAttr.getEnd() }] : []
3874
+ );
3875
+ const remainingUse = /* @__PURE__ */ new Map();
3876
+ forEachIdentifier(sf, (id) => {
3877
+ const pos = id.getStart(sf);
3878
+ if (inAny(allRemoved, pos) && !inAny(keyRanges, pos) && !inAny(keptInside, pos)) return;
3879
+ const symbol = symbolOfRef(checker, id);
3880
+ const binding = symbol ? imports.bySymbol.get(symbol) : void 0;
3881
+ if (!binding) return;
3882
+ if (containsRange(binding.declaration, id)) return;
3883
+ const use = remainingUse.get(binding) ?? { value: false, type: false };
3884
+ if (isInTypePosition2(id)) use.type = true;
3885
+ else use.value = true;
3886
+ remainingUse.set(binding, use);
3887
+ });
3888
+ let lastKeptImport;
3889
+ for (const decl of imports.declarations) {
3890
+ const all = imports.bindings.filter((b) => b.declaration === decl);
3891
+ if (!decl.importClause) {
3892
+ lastKeptImport = decl;
3893
+ continue;
3894
+ }
3895
+ const kept = all.filter((b) => remainingUse.has(b));
3896
+ const range = statementLineRange(sf, decl);
3897
+ if (kept.length === 0) {
3898
+ removed.push(range);
3899
+ ms.remove(range.start, range.end);
3900
+ continue;
3901
+ }
3902
+ lastKeptImport = decl;
3903
+ const valueUsed = kept.some((b) => remainingUse.get(b)?.value && !b.typeOnly);
3904
+ const moduleIsNeutral = kept.every((b) => isNeutralModule(b));
3905
+ const needsTypeOnly = !valueUsed && !decl.importClause.isTypeOnly && !moduleIsNeutral;
3906
+ if (kept.length === all.length && !needsTypeOnly) continue;
3907
+ const entries = kept.map((b) => ({ binding: b, typeOnly: b.typeOnly || needsTypeOnly }));
3908
+ const spec = decl.moduleSpecifier.getText(sf);
3909
+ ms.overwrite(
3910
+ decl.getStart(sf),
3911
+ decl.getEnd(),
3912
+ importStatement(decl, sf, entries, style, spec).join("\n")
3913
+ );
3914
+ }
3915
+ if (directive) ms.remove(directive.getStart(sf), removed[0]?.end ?? directive.getEnd());
3916
+ for (const r of mergeRanges(unitRanges)) {
3917
+ const collapsed = collapseBlankLines(text, r);
3918
+ ms.remove(collapsed.start, collapsed.end);
3919
+ }
3920
+ for (const decl of moduleRemovals) {
3921
+ const r = collapseBlankLines(text, statementLineRange(sf, decl.statement));
3922
+ ms.remove(r.start, r.end);
3923
+ }
3924
+ for (const island of islands) {
3925
+ const attrs = [];
3926
+ if (island.keyAttr) attrs.push(island.keyAttr.getText(sf));
3927
+ for (const prop of island.props)
3928
+ attrs.push(`${prop.name}={${prop.picked ? prop.picked.serverExpr : prop.name}}`);
3929
+ const rootStart = island.root.node.getStart(sf);
3930
+ const slotIndent = indentationOfLine(text, rootStart) + style.indent;
3931
+ const slotSource = (node) => {
3932
+ const start = node.getStart(sf);
3933
+ const raw = text.slice(start, node.getEnd());
3934
+ return hasMultilineLiteral(node, sf) ? raw : reindent(raw, indentationOfLine(text, start), slotIndent);
3935
+ };
3936
+ let usage;
3937
+ const [onlySlot] = island.slots;
3938
+ if (island.slots.length === 1 && onlySlot?.name === "children") {
3939
+ const open = `<${island.name}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>`;
3940
+ usage = `${open}
3941
+ ${slotIndent}${slotSource(onlySlot.j.node)}
3942
+ ${indentationOfLine(text, rootStart)}</${island.name}>`;
3943
+ } else {
3944
+ for (const slot of island.slots) attrs.push(`${slot.name}={${slotSource(slot.j.node)}}`);
3945
+ usage = `<${island.name}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} />`;
3946
+ }
3947
+ ms.overwrite(rootStart, island.root.node.getEnd(), usage);
3948
+ }
3949
+ for (const decl of movedComponents) {
3950
+ const r = collapseBlankLines(text, statementLineRange(sf, decl.statement));
3951
+ ms.remove(r.start, r.end);
3952
+ }
3953
+ const importedBack = movedComponents.filter(
3954
+ (decl) => isReferencedOutside(decl, sf, checker, [...removed, ...replacedRoots], keptInside)
3955
+ ).flatMap((decl) => decl.names);
3956
+ const importNames = [...islands.map((i) => i.name), ...importedBack];
3957
+ if (importNames.length === 0)
3958
+ return skip("unsupported-pattern", "nothing would be imported from the island file");
3959
+ const importLine = `import { ${importNames.join(", ")} } from ${q}${input.islandSpecifier}${q}${semi}`;
3960
+ const firstImport = imports.declarations[0];
3961
+ if (lastKeptImport) ms.appendLeft(lastKeptImport.getEnd(), `
3962
+ ${importLine}`);
3963
+ else if (firstImport) {
3964
+ ms.prependRight(statementLineRange(sf, firstImport).start, `${importLine}
3965
+ `);
3966
+ } else {
3967
+ const firstKept = sf.statements.find((s) => s !== directive && !inAny(removed, s.getStart(sf)));
3968
+ const at = firstKept ? lineStartOf(text, firstKept.getStart(sf)) : text.length;
3969
+ ms.prependRight(at, `${importLine}
3970
+
3971
+ `);
3972
+ }
3973
+ return ok({ serverText: ms.toString(), islandText, islands });
3974
+ }
3975
+ function statementSource(statement, sf) {
3976
+ const range = statementLineRange(sf, statement);
3977
+ return sf.text.slice(range.start, statement.getEnd()).replace(/^\s*\n/, "");
3978
+ }
3979
+ function withoutExportModifiers(statement, sf) {
3980
+ const range = statementLineRange(sf, statement);
3981
+ const base = range.start;
3982
+ const ms = new MagicString(sf.text.slice(base, statement.getEnd()));
3983
+ const modifiers = ts16.canHaveModifiers(statement) ? ts16.getModifiers(statement) ?? [] : [];
3984
+ for (const modifier of modifiers) {
3985
+ if (modifier.kind !== ts16.SyntaxKind.ExportKeyword && modifier.kind !== ts16.SyntaxKind.DefaultKeyword)
3986
+ continue;
3987
+ let end = modifier.getEnd();
3988
+ while (/\s/.test(sf.text[end] ?? "")) end++;
3989
+ ms.remove(modifier.getStart(sf) - base, end - base);
3990
+ }
3991
+ return ms.toString().replace(/^\s*\n/, "");
3992
+ }
3993
+ function isNeutralModule(binding) {
3994
+ if (binding.module.kind === "project") return binding.module.directive !== "use client";
3995
+ return [
3996
+ "neutral",
3997
+ "server-safe-list",
3998
+ "json",
3999
+ "asset",
4000
+ "css-module",
4001
+ "stylesheet",
4002
+ "react",
4003
+ "builtin",
4004
+ "server-only-marker"
4005
+ ].includes(binding.module.info.kind);
4006
+ }
4007
+ function isInTypePosition2(id) {
4008
+ let node = id;
4009
+ while (node.parent) {
4010
+ if (ts16.isTypeNode(node.parent) && !ts16.isExpressionWithTypeArguments(node.parent)) return true;
4011
+ if (ts16.isTypeAliasDeclaration(node.parent) || ts16.isInterfaceDeclaration(node.parent))
4012
+ return true;
4013
+ if (ts16.isExpression(node.parent) || ts16.isStatement(node.parent)) return false;
4014
+ node = node.parent;
4015
+ }
4016
+ return false;
4017
+ }
4018
+ function isReferencedOutside(decl, sf, checker, removed, kept = []) {
4019
+ const symbols = /* @__PURE__ */ new Set();
4020
+ for (const name of decl.names) {
4021
+ const nameNode = decl.statement.name;
4022
+ if (nameNode && ts16.isIdentifier(nameNode)) {
4023
+ const s = checker.getSymbolAtLocation(nameNode);
4024
+ if (s) symbols.add(s);
4025
+ }
4026
+ if (ts16.isVariableStatement(decl.statement)) {
4027
+ for (const d of decl.statement.declarationList.declarations) {
4028
+ if (ts16.isIdentifier(d.name) && d.name.text === name) {
4029
+ const s = checker.getSymbolAtLocation(d.name);
4030
+ if (s) symbols.add(s);
4031
+ }
4032
+ }
4033
+ }
4034
+ }
4035
+ let found = false;
4036
+ forEachIdentifier(sf, (id) => {
4037
+ if (found) return;
4038
+ if (containsRange(decl.statement, id)) return;
4039
+ const pos = id.getStart(sf);
4040
+ if (inAny(removed, pos) && !inAny(kept, pos)) return;
4041
+ const symbol = symbolOfRef(checker, id);
4042
+ if (symbol && symbols.has(symbol)) found = true;
4043
+ });
4044
+ return found;
4045
+ }
4046
+ function collapseBlankLines(text, range) {
4047
+ const { start } = range;
4048
+ let { end } = range;
4049
+ const prevLineEnd = start - 1;
4050
+ const prevLine = prevLineEnd >= 0 ? text.slice(lineStartOf(text, prevLineEnd), prevLineEnd) : "";
4051
+ const isBlankAt = (pos) => pos < text.length && text.slice(pos, lineEndOf(text, pos)).trim() === "";
4052
+ if (start > 0 && text[start - 1] === "\n") {
4053
+ if (prevLine.trimEnd().endsWith("{") || prevLine.trimEnd().endsWith("(")) {
4054
+ while (end < text.length && isBlankAt(end) && text[lineEndOf(text, end)] === "\n")
4055
+ end = lineEndOf(text, end) + 1;
4056
+ } else if (prevLine.trim() === "" && isBlankAt(end) && text[lineEndOf(text, end)] === "\n") {
4057
+ end = lineEndOf(text, end) + 1;
4058
+ }
4059
+ }
4060
+ return { start, end };
4061
+ }
4062
+ function renderIsland(island, signature, sf, style) {
4063
+ const text = sf.text;
4064
+ const semi = style.semi ? ";" : "";
4065
+ const ind = style.indent;
4066
+ const lines = [`export function ${island.name}(${signature}) {`];
4067
+ const blankAfter = (node) => {
4068
+ const rest = text.slice(node.getEnd());
4069
+ const gap = /^[^\S\n]*(?:\/\/[^\n]*)?\n([^\S\n]*\n)/.exec(rest);
4070
+ return gap !== null;
4071
+ };
4072
+ island.units.forEach((unit, i) => {
4073
+ lines.push(renderUnit(unit, sf, ind));
4074
+ const next = island.units[i + 1];
4075
+ const separated = next ? /\n[^\S\n]*\n/.test(text.slice(unit.node.getEnd(), statementLineRange(sf, next.node).start)) : blankAfter(unit.node);
4076
+ if (separated) lines.push("");
4077
+ });
4078
+ const root = island.root.node;
4079
+ const rootStart = root.getStart(sf);
4080
+ const ms = new MagicString(text);
4081
+ for (const slot of island.slots) {
4082
+ ms.overwrite(slot.j.node.getStart(sf), slot.j.node.getEnd(), `{${slot.name}}`);
4083
+ }
4084
+ if (island.keyAttr) {
4085
+ let start = island.keyAttr.getStart(sf);
4086
+ while (start > 0 && /[ \t]/.test(text[start - 1] ?? "")) start--;
4087
+ ms.remove(start, island.keyAttr.getEnd());
4088
+ }
4089
+ let rootText = ms.slice(rootStart, root.getEnd());
4090
+ const multiline = rootText.includes("\n");
4091
+ if (!multiline) {
4092
+ lines.push(`${ind}return ${rootText}${semi}`);
4093
+ } else {
4094
+ if (!hasMultilineLiteral(root, sf))
4095
+ rootText = reindent(rootText, indentationOfLine(text, rootStart), ind + ind);
4096
+ lines.push(`${ind}return (`, `${ind}${ind}${rootText}`, `${ind})${semi}`);
4097
+ }
4098
+ lines.push("}");
4099
+ return lines.join("\n");
4100
+ }
4101
+ function renderUnit(unit, sf, ind) {
4102
+ const text = sf.text;
4103
+ const range = statementLineRange(sf, unit.node);
4104
+ const start = range.whole ? range.start : unit.node.getStart(sf);
4105
+ let chunk = text.slice(start, unit.node.getEnd());
4106
+ const lineIndent = indentationOfLine(text, unit.node.getStart(sf));
4107
+ chunk = chunk.replace(/^[ \t]*/, "");
4108
+ if (!hasMultilineLiteral(unit.node, sf)) chunk = reindent(chunk, lineIndent, ind);
4109
+ return `${ind}${chunk}`;
4110
+ }
4111
+
4112
+ // src/transform/format.ts
4113
+ import { createRequire as createRequire2 } from "module";
4114
+ import path7 from "path";
4115
+ import { pathToFileURL as pathToFileURL2 } from "url";
4116
+ var cache = /* @__PURE__ */ new Map();
4117
+ function loadPrettier(root) {
4118
+ let pending = cache.get(root);
4119
+ if (!pending) {
4120
+ pending = (async () => {
4121
+ try {
4122
+ const require2 = createRequire2(path7.join(root, "package.json"));
4123
+ const entry = require2.resolve("prettier");
4124
+ const mod = await import(pathToFileURL2(entry).href);
4125
+ return typeof mod.format === "function" ? mod : mod.default;
4126
+ } catch {
4127
+ return void 0;
4128
+ }
4129
+ })();
4130
+ cache.set(root, pending);
4131
+ }
4132
+ return pending;
4133
+ }
4134
+ async function formatWithProjectPrettier(root, file, text) {
4135
+ const prettier = await loadPrettier(root);
4136
+ if (!prettier) return text;
4137
+ const configFile = await prettier.resolveConfigFile(file);
4138
+ if (!configFile || path7.relative(root, configFile).startsWith("..")) return text;
4139
+ const options = await prettier.resolveConfig(file, { config: configFile }) ?? {};
4140
+ return prettier.format(text, { ...options, filepath: file });
4141
+ }
4142
+
4143
+ // src/verify/selfCheck.ts
4144
+ import path8 from "path";
4145
+ import { ts as ts17 } from "ts-morph";
4146
+ function diagnosticsOf(program, sf, bucket) {
4147
+ const out = /* @__PURE__ */ new Map();
4148
+ const all = [...program.getSyntacticDiagnostics(sf), ...program.getSemanticDiagnostics(sf)];
4149
+ for (const d of all) {
4150
+ const key = `${bucket}|TS${d.code}|${ts17.flattenDiagnosticMessageText(d.messageText, " ")}`;
4151
+ out.set(key, (out.get(key) ?? 0) + 1);
4152
+ }
4153
+ return out;
4154
+ }
4155
+ function merge(into, from) {
4156
+ for (const [k, v] of from) into.set(k, (into.get(k) ?? 0) + v);
4157
+ }
4158
+ function selfCheck(input) {
4159
+ const { loaded, file, islandFile } = input;
4160
+ const project = loaded.project;
4161
+ const original = project.getSourceFile(file);
4162
+ if (!original) return skip("self-check-failed", "the original file is not in the project");
4163
+ const originalText = original.getFullText();
4164
+ const before = /* @__PURE__ */ new Map();
4165
+ {
4166
+ const program = project.getProgram().compilerObject;
4167
+ const sf = program.getSourceFile(file);
4168
+ if (sf) merge(before, diagnosticsOf(program, sf, "self"));
4169
+ for (const importer of input.importers) {
4170
+ const isf = program.getSourceFile(importer);
4171
+ if (isf) merge(before, diagnosticsOf(program, isf, importer));
4172
+ }
4173
+ }
4174
+ let islandWrapper;
4175
+ try {
4176
+ setSourceText(original, input.serverText);
4177
+ islandWrapper = project.createSourceFile(islandFile, input.islandText, { overwrite: false });
4178
+ const program = project.getProgram().compilerObject;
4179
+ const checker = program.getTypeChecker();
4180
+ const serverSf = program.getSourceFile(file);
4181
+ const islandSf = program.getSourceFile(islandFile);
4182
+ if (!serverSf || !islandSf)
4183
+ return skip("self-check-failed", "generated files are missing from the program");
4184
+ for (const sf of [serverSf, islandSf]) {
4185
+ const syntax = program.getSyntacticDiagnostics(sf);
4186
+ const first = syntax[0];
4187
+ if (first) {
4188
+ return skip(
4189
+ "self-check-failed",
4190
+ `${path8.basename(sf.fileName)} doesn't parse: ${ts17.flattenDiagnosticMessageText(first.messageText, " ")}`
4191
+ );
4192
+ }
4193
+ }
4194
+ const after = /* @__PURE__ */ new Map();
4195
+ merge(after, diagnosticsOf(program, serverSf, "self"));
4196
+ merge(after, diagnosticsOf(program, islandSf, "self"));
4197
+ for (const importer of input.importers) {
4198
+ const isf = program.getSourceFile(importer);
4199
+ if (isf) merge(after, diagnosticsOf(program, isf, importer));
4200
+ }
4201
+ const added = [];
4202
+ for (const [key, count] of after) {
4203
+ if (count > (before.get(key) ?? 0)) added.push(key.split("|").slice(1).join(": "));
4204
+ }
4205
+ if (added.length > 0) {
4206
+ return skip(
4207
+ "self-check-failed",
4208
+ `typechecking the output found new errors: ${added.slice(0, 3).join("; ")}`
4209
+ );
4210
+ }
4211
+ if (getModuleDirective(serverSf).directive === "use client") {
4212
+ return skip("self-check-failed", 'the rewritten file still starts with "use client"');
4213
+ }
4214
+ const imports = buildImportTable(serverSf, checker, input.graph);
4215
+ const facts = new FileFacts(
4216
+ serverSf,
4217
+ checker,
4218
+ imports,
4219
+ input.framework,
4220
+ input.moduleSafety.check,
4221
+ input.serverSafeHooks
4222
+ );
4223
+ const islandSymbols = new Set(
4224
+ imports.bindings.filter((b) => b.specifier === input.islandSpecifier).map((b) => b.symbol)
4225
+ );
4226
+ for (const marker of facts.scanMarkers(serverSf)) {
4227
+ if (marker.kind === "client-import" && islandSymbols.size > 0 && isIslandTag(marker.node, checker, islandSymbols))
4228
+ continue;
4229
+ return skip(
4230
+ "self-check-failed",
4231
+ `the rewritten file still uses client-only code (${marker.detail})`
4232
+ );
4233
+ }
4234
+ let problem;
4235
+ const visit = (n) => {
4236
+ if (problem) return;
4237
+ if (ts17.isJsxOpeningElement(n) || ts17.isJsxSelfClosingElement(n)) {
4238
+ const intrinsic = isIntrinsicTagName(n.tagName);
4239
+ const crossesBoundary = !intrinsic && isClientReferenceTag(n.tagName, facts, islandSymbols, checker);
4240
+ for (const attr of n.attributes.properties) {
4241
+ if (ts17.isJsxSpreadAttribute(attr)) {
4242
+ if (crossesBoundary && !checkSerializable(checker, checker.getTypeAtLocation(attr.expression), {
4243
+ primitivesOnly: input.isJs
4244
+ }).ok) {
4245
+ problem = `spread props on <${n.tagName.getText(serverSf)}> are not serializable`;
4246
+ }
4247
+ continue;
4248
+ }
4249
+ const name = attr.name.getText(serverSf);
4250
+ if (intrinsic && (/^on[A-Z]/.test(name) || name === "ref")) {
4251
+ problem = `<${n.tagName.getText(serverSf)}> still has ${name}`;
4252
+ return;
4253
+ }
4254
+ const init = attr.initializer;
4255
+ if (!init || !ts17.isJsxExpression(init) || !init.expression) continue;
4256
+ const type = checker.getTypeAtLocation(init.expression);
4257
+ const action = isServerActionExpr(init.expression, facts, input.serverActionProps);
4258
+ if (isCallableType(type) && !action) {
4259
+ problem = `<${n.tagName.getText(serverSf)}> receives a function in ${name}`;
4260
+ return;
4261
+ }
4262
+ if (crossesBoundary && !action) {
4263
+ const verdict = checkSerializable(checker, type, { primitivesOnly: input.isJs });
4264
+ if (!verdict.ok) {
4265
+ problem = `prop ${name} of <${n.tagName.getText(serverSf)}> ${verdict.reason}`;
4266
+ return;
4267
+ }
4268
+ }
4269
+ }
4270
+ }
4271
+ ts17.forEachChild(n, visit);
4272
+ };
4273
+ visit(serverSf);
4274
+ if (problem) return skip("self-check-failed", problem);
4275
+ for (const statement of islandSf.statements) {
4276
+ if (!ts17.isImportDeclaration(statement) || !ts17.isStringLiteral(statement.moduleSpecifier))
4277
+ continue;
4278
+ if (statement.importClause?.isTypeOnly) continue;
4279
+ const spec = statement.moduleSpecifier.text;
4280
+ const resolved = ts17.resolveModuleName(
4281
+ spec,
4282
+ islandFile,
4283
+ { ...loaded.compilerOptions, allowJs: true },
4284
+ ts17.sys
4285
+ ).resolvedModule;
4286
+ if (resolved && !resolved.isExternalLibraryImport && !resolved.resolvedFileName.includes("/node_modules/"))
4287
+ continue;
4288
+ const info = input.classifier.classify(spec, islandFile);
4289
+ if (info.kind === "builtin" || info.kind === "server-only-marker") {
4290
+ return skip("self-check-failed", `the island file imports the server-only module ${spec}`);
4291
+ }
4292
+ if (info.kind === "framework") {
4293
+ const rule = input.framework.classifyModule(spec);
4294
+ if (rule?.kind === "per-name") {
4295
+ const names = namedImports(statement);
4296
+ if (names.some((n) => rule.classify(n) === "server-only")) {
4297
+ return skip(
4298
+ "self-check-failed",
4299
+ `the island file imports the server-only module ${spec}`
4300
+ );
4301
+ }
4302
+ }
4303
+ }
4304
+ }
4305
+ return ok(void 0);
4306
+ } finally {
4307
+ setSourceText(original, originalText);
4308
+ if (islandWrapper) project.removeSourceFile(islandWrapper);
4309
+ }
4310
+ }
4311
+ function namedImports(decl) {
4312
+ const clause = decl.importClause;
4313
+ if (!clause) return [];
4314
+ const out = [];
4315
+ if (clause.name) out.push("default");
4316
+ const b = clause.namedBindings;
4317
+ if (b && ts17.isNamedImports(b))
4318
+ for (const e of b.elements) out.push((e.propertyName ?? e.name).text);
4319
+ if (b && ts17.isNamespaceImport(b)) out.push("*");
4320
+ return out;
4321
+ }
4322
+ function isIslandTag(node, checker, islandSymbols) {
4323
+ if (!ts17.isIdentifier(node)) return false;
4324
+ const symbol = checker.getSymbolAtLocation(node);
4325
+ return symbol !== void 0 && islandSymbols.has(symbol);
4326
+ }
4327
+ function isClientReferenceTag(tag, facts, islandSymbols, checker) {
4328
+ let head = tag;
4329
+ while (ts17.isPropertyAccessExpression(head)) head = head.expression;
4330
+ if (!ts17.isIdentifier(head)) return true;
4331
+ const symbol = checker.getSymbolAtLocation(head);
4332
+ if (symbol && islandSymbols.has(symbol)) return true;
4333
+ const info = facts.classifyRef(head);
4334
+ if (info.kind === "import") return info.usage.cls !== "neutral";
4335
+ return info.kind !== "file";
4336
+ }
4337
+ function isServerActionExpr(expr, facts, serverActionProps) {
4338
+ const e = stripParens(expr);
4339
+ if (!ts17.isIdentifier(e)) return false;
4340
+ const info = facts.classifyRef(e);
4341
+ if (info.kind === "import") return info.usage.cls === "server-action";
4342
+ if (info.kind === "file" && serverActionProps && ts17.isBindingElement(info.declaration)) {
4343
+ const element = info.declaration;
4344
+ const propName = element.propertyName ?? element.name;
4345
+ const inParameter = ts17.isObjectBindingPattern(element.parent) && ts17.isParameter(element.parent.parent);
4346
+ return inParameter && ts17.isIdentifier(propName) && serverActionProps.has(propName.text);
4347
+ }
4348
+ return false;
4349
+ }
4350
+
4351
+ // src/split.ts
4352
+ var STYLESHEET_RE2 = /\.(css|scss|sass|less|styl|pcss)$/;
4353
+ function createRunContext(root, config, options = {}) {
4354
+ const framework = nextAdapter;
4355
+ const loaded = loadProject(root, {
4356
+ lazy: options.lazy ?? false,
4357
+ ...options.tsconfig ? { tsconfig: options.tsconfig } : {}
4358
+ });
4359
+ const classifier = new PackageClassifier(framework, config.clientOnlyPackages);
4360
+ const graph = buildModuleGraph(loaded, classifier);
4361
+ const serverSafeHooks = new Set(config.serverSafeHooks);
4362
+ const moduleSafety = new ModuleSafety(
4363
+ () => loaded.project.getProgram().compilerObject,
4364
+ graph,
4365
+ framework,
4366
+ serverSafeHooks
4367
+ );
4368
+ return {
4369
+ root,
4370
+ config,
4371
+ framework,
4372
+ loaded,
4373
+ graph,
4374
+ classifier,
4375
+ moduleSafety,
4376
+ serverSafeHooks,
4377
+ pageExtensions: readPageExtensions(root),
4378
+ measurer: void 0
4379
+ };
4380
+ }
4381
+ async function runSplit(options = {}) {
4382
+ const root = realpathSync(path9.resolve(options.root ?? process.cwd()));
4383
+ const configFile = options.noConfigFile ? void 0 : options.configFile ? path9.resolve(root, options.configFile) : findConfigFile(root);
4384
+ const fileConfig = configFile ? await loadConfigFile(configFile) : void 0;
4385
+ const config = resolveConfig(fileConfig, options.config);
4386
+ const run = createRunContext(root, config);
4387
+ if (options.measure !== false) {
4388
+ const { BundleMeasurer } = await import("./bundleSize-KPGJRR3B.js");
4389
+ run.measurer = new BundleMeasurer(root, run.graph, run.loaded.configFile);
4390
+ }
4391
+ const { graph } = run;
4392
+ const targets = selectTargets(root, options.paths, config);
4393
+ const files = [];
4394
+ for (const file of targets) {
4395
+ const node = graph.nodes.get(file);
4396
+ if (node?.directive !== "use client") continue;
4397
+ const result = await processFile(run, file);
4398
+ files.push(result);
4399
+ options.onFile?.(result);
4400
+ }
4401
+ if (options.write) {
4402
+ for (const result of files) {
4403
+ if (result.status !== "split") continue;
4404
+ for (const [rel2, content] of Object.entries(result.outputs)) {
4405
+ const abs = path9.join(root, rel2);
4406
+ mkdirSync(path9.dirname(abs), { recursive: true });
4407
+ writeFileSync2(abs, content);
4408
+ }
4409
+ }
4410
+ }
4411
+ return {
4412
+ files,
4413
+ filesScanned: targets.length,
4414
+ rerun: (relFile, renames) => processFile(run, path9.resolve(root, relFile), renames)
4415
+ };
4416
+ }
4417
+ function selectTargets(root, paths, config) {
4418
+ const ignore = [...IGNORE_GLOBS, ...config.exclude];
4419
+ const sourceGlob = "**/*.{tsx,jsx,ts,js,mts,mjs}";
4420
+ let files;
4421
+ if (paths && paths.length > 0) {
4422
+ files = [];
4423
+ for (const p of paths) {
4424
+ const abs = path9.resolve(root, p);
4425
+ if (!existsSync5(abs)) continue;
4426
+ if (/\.(tsx|jsx|ts|js|mts|mjs)$/.test(abs) && !abs.endsWith(".d.ts")) files.push(abs);
4427
+ else
4428
+ files.push(
4429
+ ...fg3.sync([sourceGlob], { cwd: abs, ignore: [...ignore, "**/*.d.ts"], absolute: true })
4430
+ );
4431
+ }
4432
+ } else {
4433
+ files = fg3.sync(
4434
+ config.include.map((g) => g.endsWith("/**") ? `${g}/*` : g),
4435
+ { cwd: root, ignore: [...ignore, "**/*.d.ts"], absolute: true }
4436
+ );
4437
+ }
4438
+ return [...new Set(files.map((f) => path9.resolve(f)))].filter((f) => /\.(tsx|jsx|ts|js|mts|mjs)$/.test(f)).sort();
4439
+ }
4440
+ function rel(root, file) {
4441
+ return path9.relative(root, file).split(path9.sep).join("/");
4442
+ }
4443
+ async function processFile(run, file, renames = {}) {
4444
+ const relFile = rel(run.root, file);
4445
+ const base = {
4446
+ file: relFile,
4447
+ status: "skipped",
4448
+ islands: [],
4449
+ staticRatio: 0,
4450
+ diff: "",
4451
+ outputs: {}
4452
+ };
4453
+ try {
4454
+ const planned = await planFile(run, file, renames);
4455
+ if (!planned.ok) return { ...base, skipped: planned.skip };
4456
+ const value = planned.value;
4457
+ const diff = Object.entries(value.outputs).map(([relPath, content]) => {
4458
+ const isOriginal = relPath === relFile;
4459
+ return createTwoFilesPatch(
4460
+ isOriginal ? `a/${relPath}` : "/dev/null",
4461
+ `b/${relPath}`,
4462
+ isOriginal ? value.originalText : "",
4463
+ content,
4464
+ void 0,
4465
+ void 0,
4466
+ { context: 3 }
4467
+ );
4468
+ }).join("");
4469
+ return {
4470
+ file: relFile,
4471
+ status: "split",
4472
+ islands: value.islands,
4473
+ staticRatio: value.staticRatio,
4474
+ ...value.bytesSaved ? { bytesSaved: value.bytesSaved } : {},
4475
+ ...value.measureError ? { measureError: value.measureError } : {},
4476
+ diff,
4477
+ outputs: value.outputs
4478
+ };
4479
+ } catch (error) {
4480
+ const err = error;
4481
+ return {
4482
+ ...base,
4483
+ skipped: { code: "internal-error", message: `${err.message}
4484
+ ${err.stack ?? ""}` }
4485
+ };
4486
+ }
4487
+ }
4488
+ function analyzeFile(run, file, renames = {}) {
4489
+ const { loaded, graph, config, framework, root } = run;
4490
+ const node = graph.nodes.get(file);
4491
+ if (!node?.inProgram) {
4492
+ return skip(
4493
+ "unsupported-pattern",
4494
+ "the file is not part of the TypeScript program (for .js/.jsx files, enable allowJs or add a jsconfig.json)"
4495
+ );
4496
+ }
4497
+ const baseName = path9.basename(file).replace(/\.[^.]+$/, "");
4498
+ if (framework.mustStayClientFiles.includes(baseName)) {
4499
+ return skip(
4500
+ "unsupported-pattern",
4501
+ `Next.js requires ${path9.basename(file)} to be a Client Component`
4502
+ );
4503
+ }
4504
+ const program = loaded.project.getProgram().compilerObject;
4505
+ const sf = program.getSourceFile(file);
4506
+ if (!sf) return skip("parse-error", "the file could not be loaded");
4507
+ const syntax = program.getSyntacticDiagnostics(sf)[0];
4508
+ if (syntax) {
4509
+ const lc = sf.getLineAndCharacterOfPosition(syntax.start ?? 0);
4510
+ return skip("parse-error", ts18.flattenDiagnosticMessageText(syntax.messageText, " "), {
4511
+ line: lc.line + 1,
4512
+ column: lc.character + 1
4513
+ });
4514
+ }
4515
+ const checker = program.getTypeChecker();
4516
+ const isJs = /\.(jsx?|mjs|cjs)$/.test(file);
4517
+ const ctx = {
4518
+ program,
4519
+ checker,
4520
+ sf,
4521
+ file,
4522
+ graph,
4523
+ config,
4524
+ framework,
4525
+ isJs,
4526
+ moduleSafety: run.moduleSafety
4527
+ };
4528
+ const ext = path9.extname(file);
4529
+ const dir = config.islandsDir ? path9.join(path9.dirname(file), config.islandsDir) : path9.dirname(file);
4530
+ const islandFile = path9.join(dir, `${path9.basename(file, ext)}${config.clientFileSuffix}${ext}`);
4531
+ if (existsSync5(islandFile) || loaded.project.getSourceFile(islandFile)) {
4532
+ return skip(
4533
+ "output-exists",
4534
+ `${rel(root, islandFile)} already exists; existing files are never merged into`
4535
+ );
4536
+ }
4537
+ if (!run.pageExtensions.ok) return skip("would-create-route", run.pageExtensions.reason);
4538
+ if (couldBecomeRoute(root, islandFile, run.pageExtensions.extensions)) {
4539
+ return skip(
4540
+ "would-create-route",
4541
+ `${rel(root, islandFile)} could be picked up as a Next.js route`
4542
+ );
4543
+ }
4544
+ const imports = buildImportTable(sf, checker, graph);
4545
+ const facts = new FileFacts(
4546
+ sf,
4547
+ checker,
4548
+ imports,
4549
+ framework,
4550
+ run.moduleSafety.check,
4551
+ run.serverSafeHooks
4552
+ );
4553
+ for (const decl of imports.sideEffectImports) {
4554
+ const spec = decl.moduleSpecifier.text;
4555
+ if (!STYLESHEET_RE2.test(spec)) {
4556
+ return skip(
4557
+ "unsupported-pattern",
4558
+ `has a side-effect import of ${spec}, which would then run on the server only`,
4559
+ locOf(ctx, decl)
4560
+ );
4561
+ }
4562
+ }
4563
+ const fileSkip = specialApiSkip(ctx, facts);
4564
+ if (fileSkip) return { ok: false, skip: fileSkip };
4565
+ const env = findEnvProblem(sf, checker, framework);
4566
+ if (env) return skip("server-env-difference", env.message, locOf(ctx, env.node));
4567
+ const shape = analyzeFileShape(ctx, facts);
4568
+ if (!shape.ok) return shape;
4569
+ const components = shape.value.components;
4570
+ const importerCheck = checkImporters(program, graph, file, components);
4571
+ if (!importerCheck.ok) return importerCheck;
4572
+ const wholesale = /* @__PURE__ */ new Set();
4573
+ let plans = [];
4574
+ let unchanged = [];
4575
+ for (let pass = 0; pass <= components.length; pass++) {
4576
+ plans = [];
4577
+ unchanged = [];
4578
+ let grew = false;
4579
+ for (const component of components) {
4580
+ if (wholesale.has(component)) continue;
4581
+ const plan = planComponent(ctx, facts, shape.value, component, {
4582
+ serverActionProps: importerCheck.value.serverActionProps,
4583
+ wholesale
4584
+ });
4585
+ if (plan.ok) {
4586
+ plans.push(plan.value);
4587
+ continue;
4588
+ }
4589
+ const code = plan.skip.code;
4590
+ if (code === "no-islands") {
4591
+ unchanged.push(component);
4592
+ continue;
4593
+ }
4594
+ const movable = (code === "fully-interactive" || code === "state-spans-component") && component.exported === "none" && components.length > 1;
4595
+ if (movable) {
4596
+ wholesale.add(component);
4597
+ grew = true;
4598
+ continue;
4599
+ }
4600
+ const prefix = components.length > 1 ? `${component.name}: ` : "";
4601
+ return { ok: false, skip: { ...plan.skip, message: `${prefix}${plan.skip.message}` } };
4602
+ }
4603
+ if (!grew) break;
4604
+ }
4605
+ if (plans.length === 0 && wholesale.size === 0) {
4606
+ return skip(
4607
+ "no-islands",
4608
+ `${components.map((c) => c.name).join(", ")} ${components.length === 1 ? "uses" : "use"} no client-only features; removing "use client" may be enough (the tool never removes it automatically)`
4609
+ );
4610
+ }
4611
+ if (components.every((c) => wholesale.has(c) || c.exported === "none") && plans.length === 0) {
4612
+ return skip(
4613
+ "fully-interactive",
4614
+ "every component in the file is interactive; nothing would stay on the server"
4615
+ );
4616
+ }
4617
+ const jsxCounts = (c) => countJsx(c.fn);
4618
+ const jsxTotal = plans.reduce((n, p) => n + p.jsxTotal, 0) + [...wholesale].reduce((n, c) => n + jsxCounts(c), 0) + unchanged.reduce((n, c) => n + jsxCounts(c), 0);
4619
+ const staticJsx = plans.reduce((n, p) => n + p.staticRatio * p.jsxTotal, 0) + unchanged.reduce((n, c) => n + jsxCounts(c), 0);
4620
+ const staticRatio = jsxTotal === 0 ? 0 : staticJsx / jsxTotal;
4621
+ if (staticRatio < config.minStaticRatio) {
4622
+ return skip(
4623
+ "below-threshold",
4624
+ `only ${Math.round(staticRatio * 100)}% of the JSX would stay on the server (minStaticRatio is ${Math.round(config.minStaticRatio * 100)}%)`
4625
+ );
4626
+ }
4627
+ const islandSpecifier = relativeSpecifier(path9.dirname(file), islandFile);
4628
+ const originalSpecifier = relativeSpecifier(dir, file);
4629
+ const emitted = emitSplit({
4630
+ renames,
4631
+ ctx,
4632
+ shape: shape.value,
4633
+ imports,
4634
+ plans,
4635
+ islandSpecifier,
4636
+ originalSpecifier,
4637
+ movedComponents: [...wholesale].flatMap((c) => {
4638
+ const decl = shape.value.componentDecls.get(c);
4639
+ return decl ? [decl] : [];
4640
+ }),
4641
+ rebaseSpecifier: (spec) => spec.startsWith(".") && dir !== path9.dirname(file) ? relativeSpecifier(dir, path9.resolve(path9.dirname(file), spec), true) : spec
4642
+ });
4643
+ if (!emitted.ok) return emitted;
4644
+ const eol = lineEndingOf(sf.text);
4645
+ const serverText = hasMixedLineEndings(sf.text) ? emitted.value.serverText : withLineEnding(emitted.value.serverText, eol);
4646
+ const islandText = withLineEnding(emitted.value.islandText, eol);
4647
+ const movedInfo = [...wholesale].map((c) => ({
4648
+ name: c.name,
4649
+ rootTag: rootTagOf(c, sf),
4650
+ props: [],
4651
+ line: sf.getLineAndCharacterOfPosition(c.statement.getStart(sf)).line + 1,
4652
+ moved: true
4653
+ }));
4654
+ const islands = [
4655
+ ...emitted.value.islands.map((i) => ({
4656
+ name: i.name,
4657
+ rootTag: i.rootTag,
4658
+ props: [...i.props.map((p) => p.name), ...i.slots.map((slot) => slot.name)],
4659
+ line: i.line
4660
+ })),
4661
+ ...movedInfo
4662
+ ].sort((a, b) => a.line - b.line);
4663
+ const main = components.find((c) => c.exported === "default") ?? components.find((c) => c.exported !== "none") ?? components[0];
4664
+ return ok({
4665
+ file,
4666
+ islandFile,
4667
+ islandSpecifier,
4668
+ serverText,
4669
+ islandText,
4670
+ islands,
4671
+ staticRatio,
4672
+ importers: importerCheck.value.importers,
4673
+ serverActionProps: importerCheck.value.serverActionProps,
4674
+ isJs,
4675
+ originalText: sf.text,
4676
+ componentName: main?.name ?? path9.basename(file, ext)
4677
+ });
4678
+ }
4679
+ function checkAnalyzed(run, analyzed, serverText = analyzed.serverText, islandText = analyzed.islandText) {
4680
+ return selfCheck({
4681
+ loaded: run.loaded,
4682
+ graph: run.graph,
4683
+ framework: run.framework,
4684
+ classifier: run.classifier,
4685
+ moduleSafety: run.moduleSafety,
4686
+ serverSafeHooks: run.serverSafeHooks,
4687
+ file: analyzed.file,
4688
+ islandFile: analyzed.islandFile,
4689
+ islandSpecifier: analyzed.islandSpecifier,
4690
+ serverText,
4691
+ islandText,
4692
+ importers: analyzed.importers,
4693
+ serverActionProps: analyzed.serverActionProps,
4694
+ isJs: analyzed.isJs
4695
+ });
4696
+ }
4697
+ async function planFile(run, file, renames) {
4698
+ const { config, root } = run;
4699
+ const analyzed = analyzeFile(run, file, renames);
4700
+ if (!analyzed.ok) return analyzed;
4701
+ const a = analyzed.value;
4702
+ let serverText = a.serverText;
4703
+ let islandText = a.islandText;
4704
+ if (config.prettier === "auto") {
4705
+ serverText = await formatWithProjectPrettier(root, file, serverText);
4706
+ islandText = await formatWithProjectPrettier(root, a.islandFile, islandText);
4707
+ }
4708
+ const checked = checkAnalyzed(run, a, serverText, islandText);
4709
+ if (!checked.ok) return checked;
4710
+ const saving = measureAnalyzed(run, a, islandText);
4711
+ if (!saving.ok) return saving;
4712
+ return ok({
4713
+ outputs: { [rel(root, file)]: serverText, [rel(root, a.islandFile)]: islandText },
4714
+ islands: a.islands,
4715
+ staticRatio: a.staticRatio,
4716
+ ...saving.value,
4717
+ originalText: a.originalText
4718
+ });
4719
+ }
4720
+ function measureAnalyzed(run, analyzed, islandText = analyzed.islandText) {
4721
+ const { measurer, config } = run;
4722
+ if (!measurer) return ok({});
4723
+ const measured = measurer.measure(
4724
+ analyzed.file,
4725
+ analyzed.originalText,
4726
+ analyzed.islandFile,
4727
+ islandText
4728
+ );
4729
+ const threshold = formatBytes(config.minBytesSaved);
4730
+ if ("error" in measured) {
4731
+ if (config.minBytesSaved === 0) return ok({ measureError: measured.error });
4732
+ return skip(
4733
+ "measure-failed",
4734
+ `the saving couldn't be measured (${measured.error}), so minBytesSaved (${threshold}) can't be checked; set minBytesSaved to 0 or pass --no-measure to split without an estimate`
4735
+ );
4736
+ }
4737
+ const { bytesSaved } = measured;
4738
+ if (bytesSaved.conservative < config.minBytesSaved) {
4739
+ return skip(
4740
+ "below-threshold",
4741
+ `saves an estimated ${formatBytes(bytesSaved.conservative)} gzip (minBytesSaved is ${threshold})`
4742
+ );
4743
+ }
4744
+ return ok({ bytesSaved });
4745
+ }
4746
+ function specialApiSkip(ctx, facts) {
4747
+ for (const marker of facts.scanMarkers(ctx.sf)) {
4748
+ const loc = locOf(ctx, marker.node);
4749
+ switch (marker.special) {
4750
+ case "css-in-js":
4751
+ return {
4752
+ code: "css-in-js-runtime",
4753
+ message: `uses ${marker.detail}; runtime CSS-in-JS is always skipped`,
4754
+ location: loc
4755
+ };
4756
+ case "context":
4757
+ return {
4758
+ code: "context-provider",
4759
+ message: "creates a React Context (createContext)",
4760
+ location: loc
4761
+ };
4762
+ case "class":
4763
+ return {
4764
+ code: "class-component",
4765
+ message: "uses React.Component / PureComponent",
4766
+ location: loc
4767
+ };
4768
+ case "hoc":
4769
+ return {
4770
+ code: "hoc",
4771
+ message: `uses ${marker.detail}, which this version doesn't split`,
4772
+ location: loc
4773
+ };
4774
+ case "lazy":
4775
+ case "dynamic":
4776
+ return {
4777
+ code: "dynamic-import",
4778
+ message: `uses ${marker.detail}, which this version doesn't split`,
4779
+ location: loc
4780
+ };
4781
+ default:
4782
+ break;
4783
+ }
4784
+ }
4785
+ let dynamicImport;
4786
+ const visit = (n) => {
4787
+ if (dynamicImport) return;
4788
+ if (ts18.isCallExpression(n) && n.expression.kind === ts18.SyntaxKind.ImportKeyword)
4789
+ dynamicImport = n;
4790
+ ts18.forEachChild(n, visit);
4791
+ };
4792
+ visit(ctx.sf);
4793
+ if (dynamicImport)
4794
+ return {
4795
+ code: "dynamic-import",
4796
+ message: "uses import(), which this version doesn't split",
4797
+ location: locOf(ctx, dynamicImport)
4798
+ };
4799
+ return void 0;
4800
+ }
4801
+ function rootTagOf(component, sf) {
4802
+ let tag = component.name;
4803
+ const visit = (n) => {
4804
+ if (ts18.isJsxElement(n) || ts18.isJsxSelfClosingElement(n) || ts18.isJsxFragment(n)) {
4805
+ tag = tagNameText(sf, n);
4806
+ return true;
4807
+ }
4808
+ return ts18.forEachChild(n, visit) ?? false;
4809
+ };
4810
+ if (component.fn.body) visit(component.fn.body);
4811
+ return tag;
4812
+ }
4813
+ function countJsx(node) {
4814
+ let n = 0;
4815
+ const visit = (child) => {
4816
+ if (ts18.isJsxElement(child) || ts18.isJsxSelfClosingElement(child) || ts18.isJsxFragment(child)) n++;
4817
+ ts18.forEachChild(child, visit);
4818
+ };
4819
+ visit(node);
4820
+ return n;
4821
+ }
4822
+ function relativeSpecifier(fromDir, toFile, keepExtension = false) {
4823
+ let target = path9.relative(fromDir, toFile).split(path9.sep).join("/");
4824
+ if (!keepExtension) target = target.replace(/\.(tsx|ts|jsx|js|mts|mjs)$/, "");
4825
+ return target.startsWith(".") ? target : `./${target}`;
4826
+ }
4827
+ function formatBytes(bytes) {
4828
+ return bytes >= 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${bytes} B`;
4829
+ }
4830
+
4831
+ export {
4832
+ SKIP_CODES,
4833
+ SKIP_TITLES,
4834
+ ConfigError,
4835
+ resolveConfig,
4836
+ findConfigFile,
4837
+ loadConfigFileSync,
4838
+ setSourceText,
4839
+ ensureLoaded,
4840
+ createRunContext,
4841
+ runSplit,
4842
+ analyzeFile,
4843
+ checkAnalyzed,
4844
+ measureAnalyzed,
4845
+ formatBytes
4846
+ };
4847
+ //# sourceMappingURL=chunk-6OAWHATP.js.map