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.
- package/CHANGELOG.md +116 -0
- package/LICENSE +21 -0
- package/README.md +279 -2
- package/dist/bundleSize-KPGJRR3B.js +8 -0
- package/dist/bundleSize-KPGJRR3B.js.map +1 -0
- package/dist/chunk-6OAWHATP.js +4847 -0
- package/dist/chunk-6OAWHATP.js.map +1 -0
- package/dist/chunk-YCSBOPV3.js +134 -0
- package/dist/chunk-YCSBOPV3.js.map +1 -0
- package/dist/chunk-YQKO5FJM.js +155 -0
- package/dist/chunk-YQKO5FJM.js.map +1 -0
- package/dist/cli.js +257 -0
- package/dist/cli.js.map +1 -0
- package/dist/eslint/index.d.ts +17 -0
- package/dist/eslint/index.js +176 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/index.d.ts +108 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive-JF66CJGV.js +66 -0
- package/dist/interactive-JF66CJGV.js.map +1 -0
- package/package.json +112 -4
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeFile,
|
|
3
|
+
checkAnalyzed,
|
|
4
|
+
createRunContext,
|
|
5
|
+
ensureLoaded,
|
|
6
|
+
findConfigFile,
|
|
7
|
+
loadConfigFileSync,
|
|
8
|
+
measureAnalyzed,
|
|
9
|
+
resolveConfig,
|
|
10
|
+
setSourceText
|
|
11
|
+
} from "../chunk-6OAWHATP.js";
|
|
12
|
+
import {
|
|
13
|
+
BundleMeasurer
|
|
14
|
+
} from "../chunk-YQKO5FJM.js";
|
|
15
|
+
import "../chunk-YCSBOPV3.js";
|
|
16
|
+
|
|
17
|
+
// src/eslint/rules/prefer-server-split.ts
|
|
18
|
+
import path2 from "path";
|
|
19
|
+
|
|
20
|
+
// src/eslint/engine.ts
|
|
21
|
+
import { existsSync, realpathSync } from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
var DEFAULT_MIN_STATIC_RATIO = 0.5;
|
|
24
|
+
var runs = /* @__PURE__ */ new Map();
|
|
25
|
+
var configs = /* @__PURE__ */ new Map();
|
|
26
|
+
function findTsconfig(file) {
|
|
27
|
+
let dir = path.dirname(file);
|
|
28
|
+
for (; ; ) {
|
|
29
|
+
for (const name of ["tsconfig.json", "jsconfig.json"]) {
|
|
30
|
+
const candidate = path.join(dir, name);
|
|
31
|
+
if (existsSync(candidate)) return candidate;
|
|
32
|
+
}
|
|
33
|
+
const parent = path.dirname(dir);
|
|
34
|
+
if (parent === dir) return void 0;
|
|
35
|
+
dir = parent;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function projectConfig(root) {
|
|
39
|
+
if (!configs.has(root)) {
|
|
40
|
+
const file = findConfigFile(root);
|
|
41
|
+
configs.set(root, file ? loadConfigFileSync(file) : void 0);
|
|
42
|
+
}
|
|
43
|
+
return configs.get(root);
|
|
44
|
+
}
|
|
45
|
+
function importerClosure(run, file) {
|
|
46
|
+
const out = /* @__PURE__ */ new Set();
|
|
47
|
+
const queue = [file];
|
|
48
|
+
while (queue.length > 0) {
|
|
49
|
+
const target = queue.shift();
|
|
50
|
+
for (const edge of run.graph.importersOf(target)) {
|
|
51
|
+
if (edge.kind === "type" || out.has(edge.from)) continue;
|
|
52
|
+
out.add(edge.from);
|
|
53
|
+
if (edge.kind === "reexport") queue.push(edge.from);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return [...out];
|
|
57
|
+
}
|
|
58
|
+
function analyzeForLint(args) {
|
|
59
|
+
const found = args.tsconfig ?? findTsconfig(args.file);
|
|
60
|
+
if (!found || !existsSync(found)) return void 0;
|
|
61
|
+
const tsconfig = realpathSync(found);
|
|
62
|
+
const file = realpathSync(args.file);
|
|
63
|
+
const root = path.dirname(tsconfig);
|
|
64
|
+
const fileConfig = projectConfig(root);
|
|
65
|
+
const minStaticRatio = Math.max(
|
|
66
|
+
args.options.minStaticRatio ?? DEFAULT_MIN_STATIC_RATIO,
|
|
67
|
+
resolveConfig(fileConfig).minStaticRatio
|
|
68
|
+
);
|
|
69
|
+
const key = `${args.typed ? "typed" : "lazy"}\0${tsconfig}\0${minStaticRatio}`;
|
|
70
|
+
let run = runs.get(key);
|
|
71
|
+
if (!run) {
|
|
72
|
+
const config = resolveConfig(fileConfig, { minStaticRatio, prettier: false });
|
|
73
|
+
run = createRunContext(root, config, { lazy: !args.typed, tsconfig });
|
|
74
|
+
runs.set(key, run);
|
|
75
|
+
}
|
|
76
|
+
const node = run.graph.nodes.get(file);
|
|
77
|
+
if (!node?.inProgram) return void 0;
|
|
78
|
+
if (!args.typed) ensureLoaded(run.loaded, [file, ...importerClosure(run, file)]);
|
|
79
|
+
const sf = run.loaded.project.getSourceFile(file);
|
|
80
|
+
if (!sf) return void 0;
|
|
81
|
+
if (sf.getFullText() !== args.text) setSourceText(sf, args.text);
|
|
82
|
+
const analyzed = analyzeFile(run, file);
|
|
83
|
+
if (!analyzed.ok) return analyzed;
|
|
84
|
+
if (args.options.selfCheck !== false) {
|
|
85
|
+
const checked = checkAnalyzed(run, analyzed.value);
|
|
86
|
+
if (!checked.ok) return checked;
|
|
87
|
+
}
|
|
88
|
+
if (args.options.measure !== false) {
|
|
89
|
+
run.measurer ??= new BundleMeasurer(root, run.graph, run.loaded.configFile);
|
|
90
|
+
const saving = measureAnalyzed(run, analyzed.value);
|
|
91
|
+
if (!saving.ok) return saving;
|
|
92
|
+
}
|
|
93
|
+
return analyzed;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/eslint/rules/prefer-server-split.ts
|
|
97
|
+
var preferServerSplit = {
|
|
98
|
+
meta: {
|
|
99
|
+
type: "suggestion",
|
|
100
|
+
docs: {
|
|
101
|
+
description: 'Suggest splitting partly-interactive "use client" components into a Server Component plus client islands'
|
|
102
|
+
},
|
|
103
|
+
schema: [
|
|
104
|
+
{
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
minStaticRatio: { type: "number", minimum: 0, maximum: 1 },
|
|
108
|
+
selfCheck: { type: "boolean" },
|
|
109
|
+
measure: { type: "boolean" }
|
|
110
|
+
},
|
|
111
|
+
additionalProperties: false
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
messages: {
|
|
115
|
+
preferSplit: "{{component}} is {{ratio}}% static with {{count}} interactive island{{plural}} ({{tags}}). Run: npx auto-server-split {{file}} --write"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
create(context) {
|
|
119
|
+
const [options = {}] = context.options;
|
|
120
|
+
return {
|
|
121
|
+
Program(program) {
|
|
122
|
+
const directive = program.body.find(
|
|
123
|
+
(statement) => statement.type === "ExpressionStatement" && "directive" in statement && statement.directive === "use client"
|
|
124
|
+
);
|
|
125
|
+
if (!directive) return;
|
|
126
|
+
const file = context.physicalFilename;
|
|
127
|
+
if (!/\.(tsx|jsx|ts|js|mts|mjs)$/.test(file) || !path2.isAbsolute(file)) return;
|
|
128
|
+
const services = context.sourceCode.parserServices;
|
|
129
|
+
const tsProgram = services?.program ?? void 0;
|
|
130
|
+
const configFilePath = tsProgram?.getCompilerOptions().configFilePath;
|
|
131
|
+
const result = analyzeForLint({
|
|
132
|
+
file,
|
|
133
|
+
text: context.sourceCode.text,
|
|
134
|
+
typed: tsProgram !== void 0,
|
|
135
|
+
tsconfig: typeof configFilePath === "string" ? configFilePath : void 0,
|
|
136
|
+
options
|
|
137
|
+
});
|
|
138
|
+
if (!result?.ok) return;
|
|
139
|
+
const { islands, staticRatio, componentName } = result.value;
|
|
140
|
+
const tags = [...new Set(islands.map((i) => `<${i.rootTag}>`))].join(", ");
|
|
141
|
+
context.report({
|
|
142
|
+
node: directive,
|
|
143
|
+
messageId: "preferSplit",
|
|
144
|
+
data: {
|
|
145
|
+
component: componentName,
|
|
146
|
+
ratio: String(Math.round(staticRatio * 100)),
|
|
147
|
+
count: String(islands.length),
|
|
148
|
+
plural: islands.length === 1 ? "" : "s",
|
|
149
|
+
tags,
|
|
150
|
+
file: path2.relative(context.cwd, file).split(path2.sep).join("/")
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// src/eslint/index.ts
|
|
159
|
+
var plugin = {
|
|
160
|
+
meta: {
|
|
161
|
+
name: "auto-server-split",
|
|
162
|
+
version: true ? "1.0.0" : "0.0.0-dev"
|
|
163
|
+
},
|
|
164
|
+
rules: { "prefer-server-split": preferServerSplit },
|
|
165
|
+
configs: {}
|
|
166
|
+
};
|
|
167
|
+
plugin.configs.recommended = {
|
|
168
|
+
name: "auto-server-split/recommended",
|
|
169
|
+
plugins: { "auto-server-split": plugin },
|
|
170
|
+
rules: { "auto-server-split/prefer-server-split": ["warn", { minStaticRatio: 0.5 }] }
|
|
171
|
+
};
|
|
172
|
+
var eslint_default = plugin;
|
|
173
|
+
export {
|
|
174
|
+
eslint_default as default
|
|
175
|
+
};
|
|
176
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/eslint/rules/prefer-server-split.ts","../../src/eslint/engine.ts","../../src/eslint/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Rule } from \"eslint\";\nimport { analyzeForLint } from \"../engine.js\";\n\ntype Options = [{ minStaticRatio?: number; selfCheck?: boolean; measure?: boolean }?];\n\ntype ParserServicesWithProgram = {\n program?: { getCompilerOptions(): { configFilePath?: unknown } } | null;\n};\n\n/**\n * Reports `\"use client\"` files that `auto-server-split` can split. Report only: a split creates a\n * new file, which an ESLint fix can't do, so the CLI is the fixer (see docs/decisions.md).\n */\nexport const preferServerSplit: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n 'Suggest splitting partly-interactive \"use client\" components into a Server Component plus client islands',\n },\n schema: [\n {\n type: \"object\",\n properties: {\n minStaticRatio: { type: \"number\", minimum: 0, maximum: 1 },\n selfCheck: { type: \"boolean\" },\n measure: { type: \"boolean\" },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferSplit:\n \"{{component}} is {{ratio}}% static with {{count}} interactive island{{plural}} ({{tags}}). Run: npx auto-server-split {{file}} --write\",\n },\n },\n\n create(context) {\n const [options = {}] = context.options as Options;\n return {\n Program(program) {\n const directive = program.body.find(\n (statement) =>\n statement.type === \"ExpressionStatement\" &&\n \"directive\" in statement &&\n statement.directive === \"use client\",\n );\n if (!directive) return;\n const file = context.physicalFilename;\n if (!/\\.(tsx|jsx|ts|js|mts|mjs)$/.test(file) || !path.isAbsolute(file)) return;\n\n const services = context.sourceCode.parserServices as ParserServicesWithProgram | undefined;\n const tsProgram = services?.program ?? undefined;\n const configFilePath = tsProgram?.getCompilerOptions().configFilePath;\n const result = analyzeForLint({\n file,\n text: context.sourceCode.text,\n typed: tsProgram !== undefined,\n tsconfig: typeof configFilePath === \"string\" ? configFilePath : undefined,\n options,\n });\n if (!result?.ok) return;\n const { islands, staticRatio, componentName } = result.value;\n const tags = [...new Set(islands.map((i) => `<${i.rootTag}>`))].join(\", \");\n context.report({\n node: directive,\n messageId: \"preferSplit\",\n data: {\n component: componentName,\n ratio: String(Math.round(staticRatio * 100)),\n count: String(islands.length),\n plural: islands.length === 1 ? \"\" : \"s\",\n tags,\n file: path.relative(context.cwd, file).split(path.sep).join(\"/\"),\n },\n });\n },\n };\n },\n};\n","import { existsSync, realpathSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { Outcome } from \"../analysis/skip.js\";\nimport { findConfigFile, loadConfigFileSync, resolveConfig } from \"../config/config.js\";\nimport { BundleMeasurer } from \"../measure/bundleSize.js\";\nimport { ensureLoaded, setSourceText } from \"../project/loadProject.js\";\nimport type { Analyzed, RunContext } from \"../split.js\";\nimport { analyzeFile, checkAnalyzed, createRunContext, measureAnalyzed } from \"../split.js\";\nimport type { UserConfig } from \"../types.js\";\n\ntype LintOptions = {\n /** Report only when at least this share of the JSX stays on the server (default 0.5). */\n minStaticRatio?: number;\n /** Run the in-memory self-check before reporting (default true). */\n selfCheck?: boolean;\n /** Measure the saving with esbuild and apply `minBytesSaved` (default true). */\n measure?: boolean;\n};\n\n/** The rule's own default: lint reports clear wins only. The config's threshold always applies too. */\nconst DEFAULT_MIN_STATIC_RATIO = 0.5;\n\n/** One cached run per tsconfig, mode and threshold, for the lifetime of the lint process. */\nconst runs = new Map<string, RunContext>();\n/** The project's auto-split config, read once per project root. */\nconst configs = new Map<string, UserConfig | undefined>();\n\nfunction findTsconfig(file: string): string | undefined {\n let dir = path.dirname(file);\n for (;;) {\n for (const name of [\"tsconfig.json\", \"jsconfig.json\"]) {\n const candidate = path.join(dir, name);\n if (existsSync(candidate)) return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\n/** The config file the CLI would load for this root, any format (invalid configs throw). */\nfunction projectConfig(root: string): UserConfig | undefined {\n if (!configs.has(root)) {\n const file = findConfigFile(root);\n configs.set(root, file ? loadConfigFileSync(file) : undefined);\n }\n return configs.get(root);\n}\n\nfunction importerClosure(run: RunContext, file: string): string[] {\n const out = new Set<string>();\n const queue = [file];\n while (queue.length > 0) {\n const target = queue.shift() as string;\n for (const edge of run.graph.importersOf(target)) {\n if (edge.kind === \"type\" || out.has(edge.from)) continue;\n out.add(edge.from);\n if (edge.kind === \"reexport\") queue.push(edge.from);\n }\n }\n return [...out];\n}\n\n/**\n * Runs the core engine for one linted file. `typed` (parserServices.program present) loads the\n * whole project; otherwise a lazy project loads only this file, what it imports, and its importers.\n * Applies the same config, self-check and size threshold as the CLI, so it only reports files the\n * CLI would split. Returns undefined when the file can't be analysed or verified.\n */\nexport function analyzeForLint(args: {\n file: string;\n text: string;\n typed: boolean;\n tsconfig: string | undefined;\n options: LintOptions;\n}): Outcome<Analyzed> | undefined {\n const found = args.tsconfig ?? findTsconfig(args.file);\n if (!found || !existsSync(found)) return undefined;\n const tsconfig = realpathSync(found);\n const file = realpathSync(args.file);\n const root = path.dirname(tsconfig);\n const fileConfig = projectConfig(root);\n // The rule option can raise the bar, never lower it below what the CLI applies.\n const minStaticRatio = Math.max(\n args.options.minStaticRatio ?? DEFAULT_MIN_STATIC_RATIO,\n resolveConfig(fileConfig).minStaticRatio,\n );\n const key = `${args.typed ? \"typed\" : \"lazy\"}\\0${tsconfig}\\0${minStaticRatio}`;\n let run = runs.get(key);\n if (!run) {\n const config = resolveConfig(fileConfig, { minStaticRatio, prettier: false });\n run = createRunContext(root, config, { lazy: !args.typed, tsconfig });\n runs.set(key, run);\n }\n const node = run.graph.nodes.get(file);\n if (!node?.inProgram) return undefined; // not covered by the tsconfig: importers can't be verified\n\n if (!args.typed) ensureLoaded(run.loaded, [file, ...importerClosure(run, file)]);\n const sf = run.loaded.project.getSourceFile(file);\n if (!sf) return undefined;\n // Analyse what the editor/CLI is linting, not what's on disk.\n if (sf.getFullText() !== args.text) setSourceText(sf, args.text);\n\n const analyzed = analyzeFile(run, file);\n if (!analyzed.ok) return analyzed;\n if (args.options.selfCheck !== false) {\n const checked = checkAnalyzed(run, analyzed.value);\n if (!checked.ok) return checked;\n }\n if (args.options.measure !== false) {\n run.measurer ??= new BundleMeasurer(root, run.graph, run.loaded.configFile);\n const saving = measureAnalyzed(run, analyzed.value);\n if (!saving.ok) return saving;\n }\n return analyzed;\n}\n\n/** For tests: forget cached projects and configs. */\nexport function clearLintCache(): void {\n runs.clear();\n configs.clear();\n}\n","import type { ESLint, Linter } from \"eslint\";\nimport { preferServerSplit } from \"./rules/prefer-server-split.js\";\n\ndeclare const __VERSION__: string;\n\nconst plugin = {\n meta: {\n name: \"auto-server-split\",\n version: typeof __VERSION__ === \"string\" ? __VERSION__ : \"0.0.0-dev\",\n },\n rules: { \"prefer-server-split\": preferServerSplit },\n configs: {} as { recommended: Linter.Config },\n} satisfies ESLint.Plugin;\n\nplugin.configs.recommended = {\n name: \"auto-server-split/recommended\",\n plugins: { \"auto-server-split\": plugin },\n rules: { \"auto-server-split/prefer-server-split\": [\"warn\", { minStaticRatio: 0.5 }] },\n};\n\nexport default plugin;\n"],"mappings":"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","names":["path","path"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every reason the tool can decline to split a file. Codes are stable: they appear in
|
|
3
|
+
* JSON output, CI logs and docs/skip-reasons.md, so never rename one.
|
|
4
|
+
*/
|
|
5
|
+
declare const SKIP_CODES: readonly ["imported-by-client", "exports-non-components", "state-spans-component", "context-provider", "css-in-js-runtime", "non-serializable-prop", "untyped-boundary", "spread-props", "conditional-hooks-or-returns", "class-component", "dynamic-import", "render-prop", "hoc", "server-env-difference", "no-islands", "fully-interactive", "below-threshold", "measure-failed", "output-exists", "would-create-route", "unsupported-pattern", "self-check-failed", "parse-error", "internal-error"];
|
|
6
|
+
type SkipCode = (typeof SKIP_CODES)[number];
|
|
7
|
+
type SourceLocation = {
|
|
8
|
+
line: number;
|
|
9
|
+
column: number;
|
|
10
|
+
};
|
|
11
|
+
type Skip = {
|
|
12
|
+
code: SkipCode;
|
|
13
|
+
message: string;
|
|
14
|
+
location?: SourceLocation;
|
|
15
|
+
};
|
|
16
|
+
/** Short, human-readable titles used when grouping skips in the CLI summary. */
|
|
17
|
+
declare const SKIP_TITLES: Record<SkipCode, string>;
|
|
18
|
+
|
|
19
|
+
type IslandInfo = {
|
|
20
|
+
name: string;
|
|
21
|
+
rootTag: string;
|
|
22
|
+
props: string[];
|
|
23
|
+
line: number;
|
|
24
|
+
/** True for a whole helper component moved into the island file (not a generated island). */
|
|
25
|
+
moved?: boolean;
|
|
26
|
+
};
|
|
27
|
+
type FileResult = {
|
|
28
|
+
file: string;
|
|
29
|
+
status: "split" | "skipped" | "unchanged";
|
|
30
|
+
skipped?: Skip;
|
|
31
|
+
islands: IslandInfo[];
|
|
32
|
+
/** Share of JSX nodes that end up outside islands, 0..1. */
|
|
33
|
+
staticRatio: number;
|
|
34
|
+
/** Gzip bytes saved; `conservative` is the headline "estimated" number. */
|
|
35
|
+
bytesSaved?: {
|
|
36
|
+
isolated: number;
|
|
37
|
+
conservative: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Why `bytesSaved` is missing although measurement was on (esbuild couldn't bundle a file). Only
|
|
41
|
+
* splits with `minBytesSaved: 0` get here; otherwise the file is skipped as `measure-failed`.
|
|
42
|
+
*/
|
|
43
|
+
measureError?: string;
|
|
44
|
+
/** Unified diff covering all touched/created files. */
|
|
45
|
+
diff: string;
|
|
46
|
+
/** Project-relative path -> new content, for every touched or created file. */
|
|
47
|
+
outputs: Record<string, string>;
|
|
48
|
+
};
|
|
49
|
+
type SplitResult = {
|
|
50
|
+
files: FileResult[];
|
|
51
|
+
};
|
|
52
|
+
type UserConfig = {
|
|
53
|
+
include?: string[];
|
|
54
|
+
exclude?: string[];
|
|
55
|
+
clientOnlyPackages?: string[];
|
|
56
|
+
serverSafeHooks?: string[];
|
|
57
|
+
clientFileSuffix?: string;
|
|
58
|
+
islandsDir?: string;
|
|
59
|
+
minBytesSaved?: number;
|
|
60
|
+
minStaticRatio?: number;
|
|
61
|
+
prettier?: "auto" | false;
|
|
62
|
+
mongoose?: {
|
|
63
|
+
autoSerialize?: boolean;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
type ResolvedConfig = {
|
|
67
|
+
include: string[];
|
|
68
|
+
exclude: string[];
|
|
69
|
+
clientOnlyPackages: string[];
|
|
70
|
+
serverSafeHooks: string[];
|
|
71
|
+
clientFileSuffix: string;
|
|
72
|
+
islandsDir: string | undefined;
|
|
73
|
+
minBytesSaved: number;
|
|
74
|
+
minStaticRatio: number;
|
|
75
|
+
prettier: "auto" | false;
|
|
76
|
+
mongoose: {
|
|
77
|
+
autoSerialize: boolean;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
type SplitOptions = {
|
|
81
|
+
/** Project root (where tsconfig.json lives). Defaults to process.cwd(). */
|
|
82
|
+
root?: string;
|
|
83
|
+
/** Files or directories to consider, relative to root. Defaults to the config `include`. */
|
|
84
|
+
paths?: string[];
|
|
85
|
+
/** Write the outputs to disk. Default false. */
|
|
86
|
+
write?: boolean;
|
|
87
|
+
/** Overrides for the config file. */
|
|
88
|
+
config?: UserConfig;
|
|
89
|
+
/** Explicit config file path (relative to root). */
|
|
90
|
+
configFile?: string;
|
|
91
|
+
/** Skip loading a config file from disk. */
|
|
92
|
+
noConfigFile?: boolean;
|
|
93
|
+
/** Skip the esbuild size measurement (faster; bytesSaved is omitted and minBytesSaved is not applied). */
|
|
94
|
+
measure?: boolean;
|
|
95
|
+
/** Called with each file result as soon as it is ready. */
|
|
96
|
+
onFile?: (result: FileResult) => void;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** Identity helper that gives `auto-split.config.ts` type checking. */
|
|
100
|
+
declare function defineConfig(config: UserConfig): UserConfig;
|
|
101
|
+
/**
|
|
102
|
+
* Finds partly-interactive "use client" components and splits them into a Server Component plus
|
|
103
|
+
* generated client islands. Nothing is written unless `write: true`.
|
|
104
|
+
*/
|
|
105
|
+
declare function split(options?: SplitOptions): Promise<SplitResult>;
|
|
106
|
+
|
|
107
|
+
export { SKIP_CODES, SKIP_TITLES, defineConfig, split };
|
|
108
|
+
export type { FileResult, IslandInfo, ResolvedConfig, Skip, SkipCode, SourceLocation, SplitOptions, SplitResult, UserConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SKIP_CODES,
|
|
3
|
+
SKIP_TITLES,
|
|
4
|
+
runSplit
|
|
5
|
+
} from "./chunk-6OAWHATP.js";
|
|
6
|
+
import "./chunk-YCSBOPV3.js";
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
function defineConfig(config) {
|
|
10
|
+
return config;
|
|
11
|
+
}
|
|
12
|
+
async function split(options = {}) {
|
|
13
|
+
const { files } = await runSplit(options);
|
|
14
|
+
return { files };
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
SKIP_CODES,
|
|
18
|
+
SKIP_TITLES,
|
|
19
|
+
defineConfig,
|
|
20
|
+
split
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { runSplit } from \"./split.js\";\nimport type { SplitOptions, SplitResult, UserConfig } from \"./types.js\";\n\nexport type * from \"./types.js\";\nexport { SKIP_CODES, SKIP_TITLES } from \"./analysis/skip.js\";\n\n/** Identity helper that gives `auto-split.config.ts` type checking. */\nexport function defineConfig(config: UserConfig): UserConfig {\n return config;\n}\n\n/**\n * Finds partly-interactive \"use client\" components and splits them into a Server Component plus\n * generated client islands. Nothing is written unless `write: true`.\n */\nexport async function split(options: SplitOptions = {}): Promise<SplitResult> {\n const { files } = await runSplit(options);\n return { files };\n}\n"],"mappings":";;;;;;;;AAOO,SAAS,aAAa,QAAgC;AAC3D,SAAO;AACT;AAMA,eAAsB,MAAM,UAAwB,CAAC,GAAyB;AAC5E,QAAM,EAAE,MAAM,IAAI,MAAM,SAAS,OAAO;AACxC,SAAO,EAAE,MAAM;AACjB;","names":[]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/interactive.ts
|
|
2
|
+
import { createInterface } from "readline/promises";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
async function reviewInteractively(files, rerun, io) {
|
|
5
|
+
const rl = createInterface({ input: io.input, output: io.output, terminal: false });
|
|
6
|
+
const lines = rl[Symbol.asyncIterator]();
|
|
7
|
+
const ask = async (question) => {
|
|
8
|
+
io.output.write(question);
|
|
9
|
+
const next = await lines.next();
|
|
10
|
+
return next.done ? void 0 : String(next.value).trim();
|
|
11
|
+
};
|
|
12
|
+
const print = (text = "") => io.output.write(`${text}
|
|
13
|
+
`);
|
|
14
|
+
const accepted = [];
|
|
15
|
+
try {
|
|
16
|
+
review: for (const original of files.filter((f) => f.status === "split")) {
|
|
17
|
+
let current = original;
|
|
18
|
+
const defaults = original.islands.map((i) => i.name);
|
|
19
|
+
const renames = {};
|
|
20
|
+
print();
|
|
21
|
+
print(io.describe(current));
|
|
22
|
+
for (; ; ) {
|
|
23
|
+
const answer = (await ask(
|
|
24
|
+
`Apply this split? ${pc.bold("[y]")}es / ${pc.bold("[n]")}o / ${pc.bold("[r]")}ename islands / ${pc.bold("[d]")}iff / ${pc.bold("[q]")}uit: `
|
|
25
|
+
))?.toLowerCase();
|
|
26
|
+
if (answer === void 0 || answer === "q") break review;
|
|
27
|
+
if (answer === "y") {
|
|
28
|
+
accepted.push(current);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
if (answer === "n") break;
|
|
32
|
+
if (answer === "d") {
|
|
33
|
+
print(io.colorDiff(current.diff));
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (answer === "r") {
|
|
37
|
+
const proposed = { ...renames };
|
|
38
|
+
for (const [i, island] of current.islands.entries()) {
|
|
39
|
+
if (island.moved) continue;
|
|
40
|
+
const name = await ask(` Rename ${island.name} to (enter to keep): `);
|
|
41
|
+
if (name === void 0) break review;
|
|
42
|
+
const original2 = defaults[i];
|
|
43
|
+
if (name && original2) proposed[original2] = name;
|
|
44
|
+
}
|
|
45
|
+
const result = await rerun(current.file, proposed);
|
|
46
|
+
if (result.status === "split") {
|
|
47
|
+
Object.assign(renames, proposed);
|
|
48
|
+
current = result;
|
|
49
|
+
print(io.describe(current));
|
|
50
|
+
} else {
|
|
51
|
+
print(pc.yellow(` Rename rejected: ${result.skipped?.message ?? "unknown reason"}`));
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
print(" Please answer y, n, r, d or q.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} finally {
|
|
59
|
+
rl.close();
|
|
60
|
+
}
|
|
61
|
+
return accepted;
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
reviewInteractively
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=interactive-JF66CJGV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/interactive.ts"],"sourcesContent":["import { createInterface } from \"node:readline/promises\";\nimport type { Readable, Writable } from \"node:stream\";\nimport pc from \"picocolors\";\nimport type { FileResult } from \"./types.js\";\n\ntype Rerun = (relFile: string, renames: Readonly<Record<string, string>>) => Promise<FileResult>;\n\ntype Io = {\n input: Readable;\n output: Writable;\n colorDiff: (diff: string) => string;\n describe: (file: FileResult) => string;\n};\n\n/**\n * Walks through each proposed split: [y]es / [n]o / [r]ename islands / [d]iff / [q]uit.\n * Renames go back through the engine (and its self-check), so a bad name can't produce bad code.\n * Returns the accepted results, with renames applied.\n */\nexport async function reviewInteractively(\n files: FileResult[],\n rerun: Rerun,\n io: Io,\n): Promise<FileResult[]> {\n const rl = createInterface({ input: io.input, output: io.output, terminal: false });\n const lines = rl[Symbol.asyncIterator]();\n const ask = async (question: string): Promise<string | undefined> => {\n io.output.write(question);\n const next = await lines.next();\n return next.done ? undefined : String(next.value).trim();\n };\n const print = (text = \"\") => io.output.write(`${text}\\n`);\n const accepted: FileResult[] = [];\n\n try {\n review: for (const original of files.filter((f) => f.status === \"split\")) {\n let current = original;\n const defaults = original.islands.map((i) => i.name);\n const renames: Record<string, string> = {};\n print();\n print(io.describe(current));\n for (;;) {\n const answer = (\n await ask(\n `Apply this split? ${pc.bold(\"[y]\")}es / ${pc.bold(\"[n]\")}o / ${pc.bold(\"[r]\")}ename islands / ${pc.bold(\"[d]\")}iff / ${pc.bold(\"[q]\")}uit: `,\n )\n )?.toLowerCase();\n if (answer === undefined || answer === \"q\") break review;\n if (answer === \"y\") {\n accepted.push(current);\n break;\n }\n if (answer === \"n\") break;\n if (answer === \"d\") {\n print(io.colorDiff(current.diff));\n continue;\n }\n if (answer === \"r\") {\n const proposed = { ...renames };\n for (const [i, island] of current.islands.entries()) {\n if (island.moved) continue;\n const name = await ask(` Rename ${island.name} to (enter to keep): `);\n if (name === undefined) break review;\n const original = defaults[i];\n if (name && original) proposed[original] = name;\n }\n const result = await rerun(current.file, proposed);\n if (result.status === \"split\") {\n Object.assign(renames, proposed);\n current = result;\n print(io.describe(current));\n } else {\n print(pc.yellow(` Rename rejected: ${result.skipped?.message ?? \"unknown reason\"}`));\n }\n continue;\n }\n print(\" Please answer y, n, r, d or q.\");\n }\n }\n } finally {\n rl.close();\n }\n return accepted;\n}\n"],"mappings":";AAAA,SAAS,uBAAuB;AAEhC,OAAO,QAAQ;AAiBf,eAAsB,oBACpB,OACA,OACA,IACuB;AACvB,QAAM,KAAK,gBAAgB,EAAE,OAAO,GAAG,OAAO,QAAQ,GAAG,QAAQ,UAAU,MAAM,CAAC;AAClF,QAAM,QAAQ,GAAG,OAAO,aAAa,EAAE;AACvC,QAAM,MAAM,OAAO,aAAkD;AACnE,OAAG,OAAO,MAAM,QAAQ;AACxB,UAAM,OAAO,MAAM,MAAM,KAAK;AAC9B,WAAO,KAAK,OAAO,SAAY,OAAO,KAAK,KAAK,EAAE,KAAK;AAAA,EACzD;AACA,QAAM,QAAQ,CAAC,OAAO,OAAO,GAAG,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AACxD,QAAM,WAAyB,CAAC;AAEhC,MAAI;AACF,WAAQ,YAAW,YAAY,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,GAAG;AACxE,UAAI,UAAU;AACd,YAAM,WAAW,SAAS,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AACnD,YAAM,UAAkC,CAAC;AACzC,YAAM;AACN,YAAM,GAAG,SAAS,OAAO,CAAC;AAC1B,iBAAS;AACP,cAAM,UACJ,MAAM;AAAA,UACJ,qBAAqB,GAAG,KAAK,KAAK,CAAC,QAAQ,GAAG,KAAK,KAAK,CAAC,OAAO,GAAG,KAAK,KAAK,CAAC,mBAAmB,GAAG,KAAK,KAAK,CAAC,SAAS,GAAG,KAAK,KAAK,CAAC;AAAA,QACxI,IACC,YAAY;AACf,YAAI,WAAW,UAAa,WAAW,IAAK,OAAM;AAClD,YAAI,WAAW,KAAK;AAClB,mBAAS,KAAK,OAAO;AACrB;AAAA,QACF;AACA,YAAI,WAAW,IAAK;AACpB,YAAI,WAAW,KAAK;AAClB,gBAAM,GAAG,UAAU,QAAQ,IAAI,CAAC;AAChC;AAAA,QACF;AACA,YAAI,WAAW,KAAK;AAClB,gBAAM,WAAW,EAAE,GAAG,QAAQ;AAC9B,qBAAW,CAAC,GAAG,MAAM,KAAK,QAAQ,QAAQ,QAAQ,GAAG;AACnD,gBAAI,OAAO,MAAO;AAClB,kBAAM,OAAO,MAAM,IAAI,YAAY,OAAO,IAAI,uBAAuB;AACrE,gBAAI,SAAS,OAAW,OAAM;AAC9B,kBAAMA,YAAW,SAAS,CAAC;AAC3B,gBAAI,QAAQA,UAAU,UAASA,SAAQ,IAAI;AAAA,UAC7C;AACA,gBAAM,SAAS,MAAM,MAAM,QAAQ,MAAM,QAAQ;AACjD,cAAI,OAAO,WAAW,SAAS;AAC7B,mBAAO,OAAO,SAAS,QAAQ;AAC/B,sBAAU;AACV,kBAAM,GAAG,SAAS,OAAO,CAAC;AAAA,UAC5B,OAAO;AACL,kBAAM,GAAG,OAAO,sBAAsB,OAAO,SAAS,WAAW,gBAAgB,EAAE,CAAC;AAAA,UACtF;AACA;AAAA,QACF;AACA,cAAM,kCAAkC;AAAA,MAC1C;AAAA,IACF;AAAA,EACF,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACA,SAAO;AACT;","names":["original"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,114 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auto-server-split",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Split partly-interactive \"use client\" components into server components plus small generated client islands.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nextjs",
|
|
7
|
+
"react-server-components",
|
|
8
|
+
"rsc",
|
|
9
|
+
"use client",
|
|
10
|
+
"codemod",
|
|
11
|
+
"eslint-plugin"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Himesh Gunathilake",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/HimeshGunathilaka/auto-server-split.git"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/HimeshGunathilaka/auto-server-split#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/HimeshGunathilaka/auto-server-split/issues"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"bin": {
|
|
27
|
+
"auto-server-split": "dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./eslint": {
|
|
35
|
+
"types": "./dist/eslint/index.d.ts",
|
|
36
|
+
"import": "./dist/eslint/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"CHANGELOG.md"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20"
|
|
48
|
+
},
|
|
49
|
+
"packageManager": "pnpm@10.33.3",
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
53
|
+
"lint": "eslint .",
|
|
54
|
+
"format": "prettier --write .",
|
|
55
|
+
"format:check": "prettier --check .",
|
|
56
|
+
"pretest": "tsup --silent",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"test:watch": "vitest",
|
|
59
|
+
"test:e2e": "node e2e/run.mjs",
|
|
60
|
+
"prepublishOnly": "pnpm lint && pnpm typecheck && pnpm test"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"citty": "0.2.2",
|
|
64
|
+
"diff": "9.0.0",
|
|
65
|
+
"esbuild": "0.28.2",
|
|
66
|
+
"fast-glob": "3.3.3",
|
|
67
|
+
"magic-string": "1.4.3",
|
|
68
|
+
"picocolors": "1.1.1",
|
|
69
|
+
"ts-morph": "28.0.0"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"eslint": ">=9",
|
|
73
|
+
"prettier": ">=3",
|
|
74
|
+
"typescript": ">=5.4"
|
|
75
|
+
},
|
|
76
|
+
"peerDependenciesMeta": {
|
|
77
|
+
"typescript": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"prettier": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"eslint": {
|
|
84
|
+
"optional": true
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"@eslint/js": "^10.0.1",
|
|
89
|
+
"@playwright/test": "1.64.1",
|
|
90
|
+
"@types/node": "^22.20.5",
|
|
91
|
+
"@types/react": "19.3.0",
|
|
92
|
+
"@types/react-dom": "19.3.0",
|
|
93
|
+
"date-fns": "4.4.0",
|
|
94
|
+
"eslint": "10.13.0",
|
|
95
|
+
"framer-motion": "^12.43.0",
|
|
96
|
+
"globals": "^17.13.0",
|
|
97
|
+
"marked": "18.1.1",
|
|
98
|
+
"mongoose": "9.11.2",
|
|
99
|
+
"next": "16.4.2",
|
|
100
|
+
"prettier": "3.9.10",
|
|
101
|
+
"react": "19.3.1",
|
|
102
|
+
"react-dom": "19.3.1",
|
|
103
|
+
"styled-components": "6.6.0",
|
|
104
|
+
"tsup": "9.0.0",
|
|
105
|
+
"typescript": "6.0.3",
|
|
106
|
+
"typescript-eslint": "8.71.2",
|
|
107
|
+
"vitest": "5.0.5"
|
|
108
|
+
},
|
|
109
|
+
"pnpm": {
|
|
110
|
+
"onlyBuiltDependencies": [
|
|
111
|
+
"esbuild"
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
}
|