styled-components-to-stylex-codemod 0.0.13 → 0.0.15

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/README.md CHANGED
@@ -4,10 +4,6 @@ Transform styled-components to StyleX.
4
4
 
5
5
  **[Try it in the online playground](https://skovhus.github.io/styled-components-to-stylex-codemod/)** — experiment with the transform in your browser.
6
6
 
7
- > [!WARNING]
8
- >
9
- > **Very much under construction (alpha):** this codemod is still early in development — expect rough edges! 🚧
10
-
11
7
  ## Installation
12
8
 
13
9
  ```bash
@@ -24,10 +20,46 @@ Use `runTransform` to transform files matching a glob pattern:
24
20
  import { runTransform, defineAdapter } from "styled-components-to-stylex-codemod";
25
21
 
26
22
  const adapter = defineAdapter({
23
+ // Map theme paths and CSS variables to StyleX expressions
24
+ resolveValue(ctx) {
25
+ return null;
26
+ },
27
+ // Map helper function calls to StyleX expressions
28
+ resolveCall(ctx) {
29
+ return null;
30
+ },
31
+ // Control which components accept external className/style and polymorphic `as`
32
+ externalInterface(ctx) {
33
+ return { style: false, as: false };
34
+ },
35
+ // Optional: use a helper for merging StyleX styles with external className/style
36
+ styleMerger: null,
37
+ });
38
+
39
+ await runTransform({
40
+ files: "src/**/*.tsx",
41
+ consumerPaths: null, // set to a glob to enable cross-file selector support
42
+ adapter,
43
+ dryRun: false,
44
+ parser: "tsx",
45
+ formatterCommands: ["pnpm prettier --write"],
46
+ });
47
+ ```
48
+
49
+ <details>
50
+ <summary>Full adapter example</summary>
51
+
52
+ ```ts
53
+ import { runTransform, defineAdapter } from "styled-components-to-stylex-codemod";
54
+
55
+ const adapter = defineAdapter({
56
+ /**
57
+ * Resolve dynamic values in styled template literals to StyleX expressions.
58
+ * Called for theme access (`props.theme.x`), CSS variables (`var(--x)`),
59
+ * and imported values. Return `{ expr, imports }` or `null` to skip.
60
+ */
27
61
  resolveValue(ctx) {
28
62
  if (ctx.kind === "theme") {
29
- // Called for patterns like: ${(props) => props.theme.color.primary}
30
- // `ctx.path` is the dotted path after `theme.`
31
63
  const varName = ctx.path.replace(/\./g, "_");
32
64
  return {
33
65
  expr: `tokens.${varName}`,
@@ -41,39 +73,11 @@ const adapter = defineAdapter({
41
73
  }
42
74
 
43
75
  if (ctx.kind === "cssVariable") {
44
- // Called for CSS values containing `var(--...)`
45
- // Note: `fallback` is the raw fallback string inside `var(--x, <fallback>)` (if present).
46
- // Note: `definedValue` is populated when the transformer sees a local `--x: <value>` definition.
47
- const { name, fallback, definedValue } = ctx;
48
-
49
- // Example: lift `var(--base-size)` to StyleX vars, and optionally drop a matching local definition.
50
- if (name === "--base-size") {
51
- return {
52
- expr: "calcVars.baseSize",
53
- imports: [
54
- {
55
- from: { kind: "specifier", value: "./css-calc.stylex" },
56
- names: [{ imported: "calcVars" }],
57
- },
58
- ],
59
- ...(definedValue === "16px" ? { dropDefinition: true } : {}),
60
- };
61
- }
62
-
63
- // Generic mapping: `--kebab-case` -> `vars.kebabCase`
64
- // e.g. `--color-primary` -> `vars.colorPrimary`
65
- const toCamelCase = (cssVarName: string) =>
66
- cssVarName
67
- .replace(/^--/, "")
68
- .split("-")
69
- .filter(Boolean)
70
- .map((part, i) => (i === 0 ? part : part[0]?.toUpperCase() + part.slice(1)))
71
- .join("");
72
-
73
- // If you care about fallbacks, you can use `fallback` here to decide whether to resolve or not.
74
- void fallback;
76
+ const toCamelCase = (s: string) =>
77
+ s.replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
78
+
75
79
  return {
76
- expr: `vars.${toCamelCase(name)}`,
80
+ expr: `vars.${toCamelCase(ctx.name)}`,
77
81
  imports: [
78
82
  {
79
83
  from: { kind: "specifier", value: "./css-variables.stylex" },
@@ -86,19 +90,12 @@ const adapter = defineAdapter({
86
90
  return null;
87
91
  },
88
92
 
93
+ /**
94
+ * Resolve helper function calls in template interpolations.
95
+ * e.g. `${transitionSpeed("slow")}` → `transitionSpeedVars.slow`
96
+ * Return `{ expr, imports }` or `null` to bail the file with a warning.
97
+ */
89
98
  resolveCall(ctx) {
90
- // Called for template interpolations like: ${transitionSpeed("slowTransition")}
91
- // `calleeImportedName` is the imported symbol name (works even with aliasing).
92
- // `calleeSource` tells you where it came from:
93
- // - { kind: "absolutePath", value: "/abs/path" } for relative imports
94
- // - { kind: "specifier", value: "some-package/foo" } for package imports
95
- //
96
- // The codemod determines how to use the result based on context:
97
- // - If `ctx.cssProperty` exists (e.g., `border: ${helper()}`) → result is used as a CSS value
98
- // - If `ctx.cssProperty` is undefined (e.g., `${helper()}`) → result is used as a StyleX style object
99
- //
100
- // Use `ctx.cssProperty` to return the appropriate expression for the context.
101
-
102
99
  const arg0 = ctx.args[0];
103
100
  const key = arg0?.kind === "literal" && typeof arg0.value === "string" ? arg0.value : null;
104
101
  if (ctx.calleeImportedName !== "transitionSpeed" || !key) {
@@ -116,27 +113,43 @@ const adapter = defineAdapter({
116
113
  };
117
114
  },
118
115
 
119
- externalInterface() {
120
- return null;
116
+ /**
117
+ * Control which exported components accept external className/style
118
+ * and/or polymorphic `as` prop. Return `{ styles, as }` flags.
119
+ */
120
+ externalInterface(ctx) {
121
+ if (ctx.filePath.includes("/shared/components/")) {
122
+ return { styles: true, as: true };
123
+ }
124
+ return { styles: false, as: false };
121
125
  },
122
126
 
123
- styleMerger: null,
127
+ /**
128
+ * When `externalInterface` enables styles, use a helper to merge
129
+ * StyleX styles with external className/style props.
130
+ * See test-cases/lib/mergedSx.ts for a reference implementation.
131
+ */
132
+ styleMerger: {
133
+ functionName: "mergedSx",
134
+ importSource: { kind: "specifier", value: "./lib/mergedSx" },
135
+ },
124
136
  });
125
137
 
126
- const result = await runTransform({
138
+ await runTransform({
127
139
  files: "src/**/*.tsx",
140
+ consumerPaths: null,
128
141
  adapter,
129
142
  dryRun: false,
130
- parser: "tsx", // "babel" | "babylon" | "flow" | "ts" | "tsx"
131
- formatterCommands: ["pnpm prettier --write"], // optional: format transformed files
143
+ parser: "tsx",
144
+ formatterCommands: ["pnpm prettier --write"],
132
145
  });
133
-
134
- console.log(result);
135
146
  ```
136
147
 
148
+ </details>
149
+
137
150
  ### Adapter
138
151
 
139
- Adapters are the main extension point. They let you control:
152
+ Adapters are the main extension point, see full example above. They let you control:
140
153
 
141
154
  - how theme paths, CSS variables, and imported values are turned into StyleX-compatible JS values (`resolveValue`)
142
155
  - what extra imports to inject into transformed files (returned from `resolveValue`)
@@ -144,109 +157,56 @@ Adapters are the main extension point. They let you control:
144
157
  - which exported components should support external className/style extension and/or polymorphic `as` prop (`externalInterface`)
145
158
  - how className/style merging is handled for components accepting external styling (`styleMerger`)
146
159
 
147
- #### Style Merger
148
-
149
- When a component accepts external `className` and/or `style` props (e.g., via `shouldSupportExternalStyling`, or when wrapping a base component that already accepts these props), the generated code needs to merge StyleX styles with externally passed values.
160
+ #### Cross-file selectors (`consumerPaths`)
150
161
 
151
- > **Note:** Allowing external className/style props is generally discouraged in StyleX as it bypasses the type-safe styling system. However, it can be useful during migration to maintain compatibility with existing code that passes these props.
162
+ `consumerPaths` is required. Pass `null` to opt out, or a glob pattern to enable cross-file selector scanning.
152
163
 
153
- By default, this generates verbose inline merging code. You can provide a `styleMerger` to use a helper function instead for cleaner output:
164
+ When transforming a subset of files, other files may reference your styled components as CSS selectors (e.g. `${Icon} { fill: red }`). Pass `consumerPaths` to scan those files and wire up cross-file selectors automatically:
154
165
 
155
166
  ```ts
156
- const adapter = defineAdapter({
157
- resolveValue(ctx) {
158
- // ... value resolution logic
159
- return null;
160
- },
161
-
162
- resolveCall() {
163
- return null;
164
- },
165
-
166
- externalInterface(ctx) {
167
- if (ctx.filePath.includes("/shared/components/")) {
168
- return { styles: true, as: true };
169
- }
170
- return { styles: false, as: false };
171
- },
172
-
173
- // Use a custom merger function for cleaner output
174
- styleMerger: {
175
- functionName: "mergedSx",
176
- importSource: { kind: "specifier", value: "./lib/mergedSx" },
177
- },
167
+ await runTransform({
168
+ files: "src/components/**/*.tsx", // files to transform
169
+ consumerPaths: "src/**/*.tsx", // additional files to scan for cross-file usage
170
+ adapter,
178
171
  });
179
172
  ```
180
173
 
181
- The merger function should have this signature:
174
+ - Files in **both** `files` and `consumerPaths` use the **marker sidecar** strategy (both consumer and target are transformed, using `stylex.defineMarker()`).
175
+ - Files in `consumerPaths` but **not** in `files` use the **bridge** strategy (a stable `className` is added to the converted component so unconverted consumers' selectors still work).
182
176
 
183
- ```ts
184
- function mergedSx(
185
- styles: StyleXStyles,
186
- className?: string,
187
- style?: React.CSSProperties,
188
- ): ReturnType<typeof stylex.props>;
189
- ```
190
-
191
- See [`test-cases/lib/mergedSx.ts`](./test-cases/lib/mergedSx.ts) for a reference implementation.
177
+ #### Auto-detecting external interface usage (experimental)
192
178
 
193
- #### External Interface (Styles and Polymorphic `as` Support)
179
+ Instead of manually specifying which components need `styles` or `as` support, set `externalInterface: "auto"` to auto-detect usage by scanning consumer code.
194
180
 
195
- Transformed components are "closed" by default — they don't accept external `className` or `style` props, and exported components only get `as` support when it is used inside the file. Use `externalInterface` to control which exported components should support these features:
181
+ > [!NOTE]
182
+ > Experimental. Requires `consumerPaths` and a successful prepass scan.
183
+ > If prepass fails, `runTransform()` throws (fail-fast) when `externalInterface: "auto"` is used.
196
184
 
197
185
  ```ts
198
- const adapter = defineAdapter({
199
- resolveValue(ctx) {
200
- // ... value resolution logic
201
- return null;
202
- },
203
-
204
- resolveCall() {
205
- return null;
206
- },
207
-
208
- externalInterface(ctx) {
209
- // ctx: { filePath, componentName, exportName, isDefaultExport }
210
-
211
- // Example: Enable styles and `as` for all exports in shared components folder
212
- if (ctx.filePath.includes("/shared/components/")) {
213
- return { styles: true, as: true };
214
- }
215
-
216
- // Example: Enable only styles (no `as` prop)
217
- if (ctx.filePath.includes("/design-system/")) {
218
- return { styles: true, as: false };
219
- }
220
-
221
- // Example: Enable only `as` prop (no style merging)
222
- if (ctx.componentName === "Typography") {
223
- return { styles: false, as: true };
224
- }
186
+ import { runTransform, defineAdapter } from "styled-components-to-stylex-codemod";
225
187
 
226
- // Disable both (default)
227
- return { styles: false, as: false };
228
- },
188
+ const adapter = defineAdapter({
189
+ // ...
190
+ externalInterface: "auto",
191
+ });
229
192
 
230
- styleMerger: null,
193
+ await runTransform({
194
+ files: "src/**/*.tsx",
195
+ consumerPaths: "src/**/*.tsx", // required for auto-detection
196
+ adapter,
231
197
  });
232
198
  ```
233
199
 
234
- The `externalInterface` method returns:
235
-
236
- - `{ styles: false, as: false }` — no external interface
237
- - `{ styles: true, as: false }` — accept className/style props only
238
- - `{ styles: true, as: true }` — accept className/style props AND polymorphic `as` prop
239
- - `{ styles: false, as: true }` — accept only polymorphic `as` prop (no style merging)
240
-
241
- When `styles: true`, the generated component will:
200
+ When `externalInterface: "auto"` is set, `runTransform()` scans `files` and `consumerPaths` for `styled(Component)` calls and `<Component as={...}>` JSX usage, resolves imports back to the component definition files, and returns the appropriate `{ styles, as }` flags automatically.
242
201
 
243
- - Accept `className` and `style` props
244
- - Merge them with the StyleX-generated styles
245
- - Forward remaining props via `...rest`
202
+ If that prepass scan fails, `runTransform()` stops and throws an actionable error rather than silently falling back to non-auto behavior.
246
203
 
247
- When `as: true` is also set, the component will additionally accept a polymorphic `as` prop for rendering as a different element type.
204
+ Troubleshooting prepass failures with `"auto"`:
248
205
 
249
- When `{ styles: false, as: true }`, the generated component will accept a polymorphic `as` prop but won't include className/style merging.
206
+ - verify `consumerPaths` globs match the files you expect
207
+ - confirm the selected parser matches your source syntax (`parser: "tsx"`, `parser: "ts"`, etc.)
208
+ - check resolver inputs (import paths, tsconfig path aliases, and related module resolution config)
209
+ - if needed, switch to a manual `externalInterface(ctx)` function to continue migration while you fix prepass inputs
250
210
 
251
211
  #### Dynamic interpolations
252
212
 
@@ -263,7 +223,7 @@ When the codemod encounters an interpolation inside a styled template literal, i
263
223
  - helper calls applied to prop values (e.g. `shadow(props.shadow)`) by emitting a StyleX style function that calls the helper at runtime
264
224
  - conditional CSS blocks via ternary (e.g. `props.$dim ? "opacity: 0.5;" : ""`)
265
225
 
266
- If the pipeline cant resolve an interpolation:
226
+ If the pipeline can't resolve an interpolation:
267
227
 
268
228
  - for some dynamic value cases, the transform preserves the value as a wrapper inline style so output keeps visual parity (at the cost of using `style={...}` for that prop)
269
229
  - otherwise, the declaration containing that interpolation is **dropped** and a warning is produced (manual follow-up required)
@@ -274,6 +234,39 @@ If the pipeline can’t resolve an interpolation:
274
234
  - **createGlobalStyle**: detected usage is reported as an **unsupported-feature** warning (StyleX does not support global styles in the same way).
275
235
  - **Theme prop overrides**: passing a `theme` prop directly to styled components (e.g. `<Button theme={...} />`) is not supported and will bail with a warning.
276
236
 
237
+ ## Migration game plan
238
+
239
+ ### 1. Define your theme and mixins as StyleX
240
+
241
+ Before running the codemod, convert your theme object and shared style helpers into StyleX equivalents:
242
+
243
+ ```ts
244
+ // tokens.stylex.ts — theme variables
245
+ import * as stylex from "@stylexjs/stylex";
246
+
247
+ // Before: { colors: { primary: "#0066cc" }, spacing: { sm: "8px" } }
248
+ export const colors = stylex.defineVars({ primary: "#0066cc" });
249
+ export const spacing = stylex.defineVars({ sm: "8px" });
250
+ ```
251
+
252
+ ```ts
253
+ // helpers.stylex.ts — shared mixins
254
+ import * as stylex from "@stylexjs/stylex";
255
+
256
+ // Before: export const truncate = () => `white-space: nowrap; overflow: hidden; ...`
257
+ export const truncate = stylex.create({
258
+ base: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" },
259
+ });
260
+ ```
261
+
262
+ ### 2. Write an adapter and run the codemod
263
+
264
+ The adapter maps your project's `props.theme.*` access, CSS variables, and helper calls to the StyleX equivalents from step 1. See [Usage](#usage) for the full API.
265
+
266
+ ### 3. Verify, iterate, clean up
267
+
268
+ Build and test your project. Review warnings — they tell you which files were skipped and why. Fix adapter gaps, re-run on remaining files, and repeat until done. [Report issues](https://github.com/skovhus/styled-components-to-stylex-codemod/issues) with input/output examples if the codemod produces incorrect results.
269
+
277
270
  ## License
278
271
 
279
272
  MIT
@@ -0,0 +1,122 @@
1
+ import { t as isSelectorContext } from "./selector-context-heuristic-CGwiJ3HL.mjs";
2
+ import { readFileSync } from "node:fs";
3
+
4
+ //#region src/internal/bridge-consumer-patcher.ts
5
+ /**
6
+ * Post-transform consumer patching for global selector bridges.
7
+ *
8
+ * After the target component is transformed and gets a bridge className,
9
+ * patch unconverted consumer files to:
10
+ * 1. Import the bridge's GlobalSelector variable from the target module
11
+ * 2. Replace `${Component}` selector references with `${ComponentGlobalSelector}`
12
+ */
13
+ /**
14
+ * Build a mapping from consumer file paths to their required replacements,
15
+ * cross-referencing prepass selector usages with successful bridge results.
16
+ */
17
+ function buildConsumerReplacements(selectorUsages, bridgeResults) {
18
+ const consumerReplacements = /* @__PURE__ */ new Map();
19
+ const bridgeLookup = /* @__PURE__ */ new Map();
20
+ for (const [targetPath, results] of bridgeResults) for (const result of results) {
21
+ bridgeLookup.set(`${targetPath}:${result.componentName}`, result);
22
+ if (result.exportName && result.exportName !== result.componentName) bridgeLookup.set(`${targetPath}:${result.exportName}`, result);
23
+ }
24
+ for (const [consumerPath, usages] of selectorUsages) for (const usage of usages) {
25
+ if (usage.consumerIsTransformed) continue;
26
+ const bridge = bridgeLookup.get(`${usage.resolvedPath}:${usage.importedName}`);
27
+ if (!bridge) continue;
28
+ let replacements = consumerReplacements.get(consumerPath);
29
+ if (!replacements) {
30
+ replacements = [];
31
+ consumerReplacements.set(consumerPath, replacements);
32
+ }
33
+ replacements.push({
34
+ localName: usage.localName,
35
+ importSource: usage.importSource,
36
+ globalSelectorVarName: bridge.globalSelectorVarName,
37
+ importedName: usage.importedName
38
+ });
39
+ }
40
+ return consumerReplacements;
41
+ }
42
+ /**
43
+ * Patch a single consumer file:
44
+ * 1. Add import for each GlobalSelector variable
45
+ * 2. Replace `${Component}` in styled template selectors with `${ComponentGlobalSelector}`
46
+ *
47
+ * Returns the patched source or null if no changes were made.
48
+ */
49
+ function patchConsumerFile(filePath, replacements) {
50
+ let source;
51
+ try {
52
+ source = readFileSync(filePath, "utf-8");
53
+ } catch {
54
+ return null;
55
+ }
56
+ if (replacements.length === 0) return null;
57
+ const bySource = /* @__PURE__ */ new Map();
58
+ for (const r of replacements) {
59
+ let list = bySource.get(r.importSource);
60
+ if (!list) {
61
+ list = [];
62
+ bySource.set(r.importSource, list);
63
+ }
64
+ list.push(r);
65
+ }
66
+ let modified = source;
67
+ for (const [importSource, reps] of bySource) {
68
+ const varNames = reps.map((r) => r.globalSelectorVarName);
69
+ const importRegex = new RegExp(`(import\\s+(?:(?:\\{[^}]*\\}|[^;{]+)\\s+from\\s+['"]${escapeRegExp(importSource)}['"])\\s*;?)`);
70
+ if (modified.match(importRegex)) {
71
+ const namedImportRegex = new RegExp(`(import\\s+(?:[\\w$]+\\s*,\\s*)?\\{)([^}]*)(\\}\\s+from\\s+['"]${escapeRegExp(importSource)}['"]\\s*;?)`);
72
+ const namedMatch = modified.match(namedImportRegex);
73
+ if (namedMatch) {
74
+ const existingNames = namedMatch[2];
75
+ const newNames = varNames.filter((name) => !hasExactImportName(existingNames, name));
76
+ if (newNames.length > 0) {
77
+ const separator = existingNames.trimEnd().endsWith(",") ? " " : ", ";
78
+ modified = modified.replace(namedImportRegex, `$1${existingNames.trimEnd()}${separator}${newNames.join(", ")} $3`);
79
+ }
80
+ } else {
81
+ const newImport = `import { ${varNames.join(", ")} } from "${importSource}";`;
82
+ modified = modified.replace(importRegex, `$1\n${newImport}`);
83
+ }
84
+ } else {
85
+ const newImport = `import { ${varNames.join(", ")} } from "${importSource}";`;
86
+ const lastImportIdx = modified.lastIndexOf("\nimport ");
87
+ if (lastImportIdx !== -1) {
88
+ const importEnd = findImportEnd(modified, lastImportIdx + 1);
89
+ modified = modified.slice(0, importEnd) + "\n" + newImport + modified.slice(importEnd);
90
+ } else modified = newImport + "\n" + modified;
91
+ }
92
+ }
93
+ for (const r of replacements) {
94
+ const templateExprRegex = new RegExp(`(\\$\\{\\s*)${escapeRegExp(r.localName)}(\\s*\\})`, "g");
95
+ modified = modified.replace(templateExprRegex, (match, prefix, suffix, offset) => {
96
+ if (isInStyledTemplateSelectorContext(modified, offset, match.length)) return `${prefix}${r.globalSelectorVarName}${suffix}`;
97
+ return match;
98
+ });
99
+ }
100
+ return modified !== source ? modified : null;
101
+ }
102
+ /** Find the end position of an import statement starting at startIdx (handles multi-line imports). */
103
+ function findImportEnd(source, startIdx) {
104
+ const semiIdx = source.indexOf(";", startIdx);
105
+ if (semiIdx === -1) return source.indexOf("\n", startIdx);
106
+ return semiIdx + 1;
107
+ }
108
+ /** Check if a template expression at the given position is in a CSS selector context. */
109
+ function isInStyledTemplateSelectorContext(source, offset, length) {
110
+ const after = source.slice(offset + length).trimStart();
111
+ return isSelectorContext(source.slice(Math.max(0, offset - 200), offset).trimEnd(), after);
112
+ }
113
+ /** Check if a name appears as a distinct identifier in an import specifier list string. */
114
+ function hasExactImportName(importSpecifiers, name) {
115
+ return new RegExp(`(?:^|[^A-Za-z0-9_$])${escapeRegExp(name)}(?:$|[^A-Za-z0-9_$])`).test(importSpecifiers);
116
+ }
117
+ function escapeRegExp(s) {
118
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
119
+ }
120
+
121
+ //#endregion
122
+ export { buildConsumerReplacements, patchConsumerFile };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as defineAdapter, r as Adapter, t as CollectedWarning } from "./logger-kU4pnRpt.mjs";
1
+ import { a as defineAdapter, i as AdapterInput, t as CollectedWarning } from "./logger-B7SOfCti.mjs";
2
2
 
3
3
  //#region src/run.d.ts
4
4
  interface RunTransformOptions {
@@ -7,11 +7,32 @@ interface RunTransformOptions {
7
7
  * @example "src/**\/*.tsx" or ["src/**\/*.ts", "src/**\/*.tsx"]
8
8
  */
9
9
  files: string | string[];
10
+ /**
11
+ * File glob(s) to scan for cross-file component selector usage, or `null` to opt out.
12
+ *
13
+ * When set to a glob pattern, files matching this glob that are NOT in `files` trigger
14
+ * the bridge strategy (stable bridge className for incremental migration when consumers
15
+ * are not transformed). Files in both globs use the marker sidecar strategy (both
16
+ * consumer and target are transformed).
17
+ *
18
+ * Required when `externalInterface` is `"auto"`.
19
+ *
20
+ * @example "src/**\/*.tsx"
21
+ * @example null // opt out of cross-file scanning
22
+ */
23
+ consumerPaths: string | string[] | null;
10
24
  /**
11
25
  * Adapter for customizing the transform.
12
26
  * Controls value resolution and resolver-provided imports.
27
+ *
28
+ * Use `externalInterface: "auto"` to auto-detect which exported components
29
+ * need external className/style and polymorphic `as` support by scanning
30
+ * consumer code specified via `consumerPaths` (or `files`).
31
+ *
32
+ * Note: `"auto"` requires prepass scanning to succeed. If prepass fails,
33
+ * runTransform throws instead of silently falling back.
13
34
  */
14
- adapter: Adapter;
35
+ adapter: AdapterInput;
15
36
  /**
16
37
  * Dry run - don't write changes to files
17
38
  * @default false
@@ -35,7 +56,7 @@ interface RunTransformOptions {
35
56
  formatterCommands?: string[];
36
57
  /**
37
58
  * Maximum number of examples shown per warning category in the summary.
38
- * @default 15
59
+ * @default 3
39
60
  */
40
61
  maxExamples?: number;
41
62
  }
@@ -73,6 +94,7 @@ interface RunTransformResult {
73
94
  *
74
95
  * await runTransform({
75
96
  * files: 'src/**\/*.tsx',
97
+ * consumerPaths: null,
76
98
  * adapter,
77
99
  * dryRun: true,
78
100
  * });
@@ -80,4 +102,4 @@ interface RunTransformResult {
80
102
  */
81
103
  declare function runTransform(options: RunTransformOptions): Promise<RunTransformResult>;
82
104
  //#endregion
83
- export { defineAdapter, runTransform };
105
+ export { type AdapterInput, defineAdapter, runTransform };