styled-components-to-stylex-codemod 0.0.55 → 0.0.57

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
@@ -37,6 +37,8 @@ The adapter maps your project's `props.theme.*` access, CSS variables, and helpe
37
37
 
38
38
  When a component wraps another component that internally uses styled-components (e.g. `styled(GroupHeader)` where `GroupHeader` renders a `StyledHeader`), CSS cascade conflicts can arise after migration. Convert leaf files — the ones that don't wrap other styled-components — first, then work your way up. The codemod will bail with a warning if it detects this pattern.
39
39
 
40
+ Run [`analyzeMigrationPlan`](#planning-manual-conversions) to get the ordered, bottom-up list of files to convert by hand first.
41
+
40
42
  ### 4. Verify, iterate, clean up
41
43
 
42
44
  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.
@@ -380,6 +382,24 @@ await runTransform({
380
382
 
381
383
  </details>
382
384
 
385
+ ### Planning manual conversions
386
+
387
+ `analyzeMigrationPlan` runs the codemod in analysis-only mode (it never writes files) and returns the bottom-up ordered list of files you must convert by hand — the genuine blockers the codemod can't convert — each with its consumer count, the exports to convert, direct auto-migration payoff, secondary blocker-chain context, and the bail reasons. `formatMigrationPlan` renders it as a report with direct unlocks emphasized first so raw chain involvement is not mistaken for files unlocked by one blocker alone.
388
+
389
+ ```ts
390
+ import { analyzeMigrationPlan, formatMigrationPlan } from "styled-components-to-stylex-codemod";
391
+
392
+ const plan = await analyzeMigrationPlan({
393
+ files: "src/**/*.tsx",
394
+ consumerPaths: "src/**/*.tsx",
395
+ adapter, // the same adapter you pass to runTransform
396
+ });
397
+
398
+ console.log(formatMigrationPlan(plan));
399
+ ```
400
+
401
+ Try it against this repo's own fixtures with `node scripts/migration-plan.mts`.
402
+
383
403
  ### Adapter
384
404
 
385
405
  Adapters are the main extension point, see full example above. They let you control:
@@ -1,6 +1,60 @@
1
- import path from "node:path";
1
+ import { n as isTemplatePlaceholderInSelectorContext, r as PLACEHOLDER_RE } from "./selector-context-heuristic-Dptd93Xe.mjs";
2
+ import path, { relative, resolve } from "node:path";
2
3
  import { readFileSync } from "node:fs";
3
4
  import { parse } from "@babel/parser";
5
+ //#region src/internal/utilities/ast-safety.ts
6
+ /**
7
+ * AST safety checks for null nodes and structural issues.
8
+ * Core concepts: recursive validation and error reporting.
9
+ */
10
+ /**
11
+ * `JSON.stringify` replacer that drops noisy AST positional metadata
12
+ * (`loc`/`tokens`/`comments`/`start`/`end`) and replaces circular references
13
+ * with `"[Circular]"`, so AST nodes can be serialized for logging or as a
14
+ * stable structural key. Create a fresh replacer per `stringify` call — it
15
+ * keeps per-serialization state.
16
+ */
17
+ function createAstSafeJsonReplacer() {
18
+ const seen = /* @__PURE__ */ new WeakSet();
19
+ return (key, value) => {
20
+ if (key === "loc" || key === "tokens" || key === "comments" || key === "start" || key === "end") return;
21
+ if (value && typeof value === "object") {
22
+ if (seen.has(value)) return "[Circular]";
23
+ seen.add(value);
24
+ }
25
+ return value;
26
+ };
27
+ }
28
+ /** AST node types whose `elements` array legitimately contains `null` (holes/elisions). */
29
+ const ARRAY_ELEMENT_HOLE_TYPES = new Set(["ArrayPattern", "ArrayExpression"]);
30
+ function assertNoNullNodesInArrays(node) {
31
+ const seen = /* @__PURE__ */ new WeakSet();
32
+ const visit = (cur, path, allowNulls) => {
33
+ if (cur === null || cur === void 0) return;
34
+ if (Array.isArray(cur)) {
35
+ for (let i = 0; i < cur.length; i++) {
36
+ if (cur[i] === null) {
37
+ if (!allowNulls) throw new Error(`Null AST node in array at ${path}[${i}]`);
38
+ continue;
39
+ }
40
+ visit(cur[i], `${path}[${i}]`, false);
41
+ }
42
+ return;
43
+ }
44
+ if (typeof cur !== "object") return;
45
+ const curObj = cur;
46
+ if (seen.has(curObj)) return;
47
+ seen.add(curObj);
48
+ const nodeType = cur.type;
49
+ const hasValidElementHoles = typeof nodeType === "string" && ARRAY_ELEMENT_HOLE_TYPES.has(nodeType);
50
+ for (const [k, v] of Object.entries(cur)) {
51
+ if (v === null) continue;
52
+ if (typeof v === "object") visit(v, `${path}.${k}`, hasValidElementHoles && k === "elements");
53
+ }
54
+ };
55
+ visit(node, "root", false);
56
+ }
57
+ //#endregion
4
58
  //#region src/internal/logger.ts
5
59
  /**
6
60
  * Logger and warning types for transform diagnostics.
@@ -89,6 +143,14 @@ var Logger = class Logger {
89
143
  static createReport() {
90
144
  return new LoggerReport([...Logger.collected], Logger.fileCount, Logger.maxExamples);
91
145
  }
146
+ /**
147
+ * Restore the collected warnings to a previous snapshot (from
148
+ * `createReport().getWarnings()`). Used to undo the side effects of an
149
+ * analysis-only dry run on the process-global logger.
150
+ */
151
+ static restoreWarnings(warnings) {
152
+ Logger.collected = [...warnings];
153
+ }
92
154
  /** @internal - for testing only */
93
155
  static _clearCollected() {
94
156
  Logger.collected = [];
@@ -123,20 +185,9 @@ var Logger = class Logger {
123
185
  }
124
186
  static formatContext(context) {
125
187
  if (typeof context === "undefined") return null;
126
- return JSON.stringify(context, createContextReplacer(), 2);
188
+ return JSON.stringify(context, createAstSafeJsonReplacer(), 2);
127
189
  }
128
190
  };
129
- function createContextReplacer() {
130
- const seen = /* @__PURE__ */ new WeakSet();
131
- return (key, value) => {
132
- if (key === "loc" || key === "tokens" || key === "comments" || key === "start" || key === "end") return;
133
- if (value && typeof value === "object") {
134
- if (seen.has(value)) return "[Circular]";
135
- seen.add(value);
136
- }
137
- return value;
138
- };
139
- }
140
191
  const MAX_DEPENDED_FILE_GROUPS = 15;
141
192
  var LoggerReport = class {
142
193
  warnings;
@@ -273,14 +324,16 @@ var LoggerReport = class {
273
324
  }
274
325
  }
275
326
  };
327
+ /**
328
+ * Extract the depended-on file path from a cascade-conflict warning's context
329
+ * (the base component's defining file that must be converted first). Owned here
330
+ * alongside the warning-type definition so consumers share one invariant.
331
+ */
276
332
  function getCascadeDependedFilePath(warning) {
277
333
  const context = warning.context;
278
- if (!context || typeof context !== "object") return;
279
- const record = context;
280
- const definitionPath = record.definitionPath;
281
- if (typeof definitionPath === "string") return definitionPath;
282
- const importedPath = record.importedPath;
283
- return typeof importedPath === "string" ? importedPath : void 0;
334
+ if (!context) return;
335
+ if (typeof context.definitionPath === "string") return context.definitionPath;
336
+ return typeof context.importedPath === "string" ? context.importedPath : void 0;
284
337
  }
285
338
  function uniqueSorted(values) {
286
339
  return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
@@ -425,22 +478,6 @@ function getFileImportsFromRes(name) {
425
478
  return cached;
426
479
  }
427
480
  //#endregion
428
- //#region src/internal/utilities/default-export-name.ts
429
- /**
430
- * Regex helpers for inspecting a module's default export by source text.
431
- *
432
- * These operate on raw source strings (not the AST) so they can be shared by
433
- * both prepass and transform-step layers without import-graph coupling.
434
- */
435
- /**
436
- * Returns the local name of a PascalCase default export, supporting both
437
- * `export default Name` and `export { Name as default }` forms. Returns
438
- * `undefined` when no PascalCase default export is found.
439
- */
440
- function findDefaultExportedLocalName(source) {
441
- return source.match(/\bexport\s+default\s+([A-Z][A-Za-z0-9]*)\b/)?.[1] ?? source.match(/\bexport\s*\{[^}]*\b([A-Z][A-Za-z0-9]*)\s+as\s+default\b[^}]*\}/)?.[1];
442
- }
443
- //#endregion
444
481
  //#region src/internal/prepass/prepass-parser.ts
445
482
  /**
446
483
  * Shared babel parser for prepass modules.
@@ -538,6 +575,309 @@ const FLOW_PLUGINS = [
538
575
  "throwExpressions"
539
576
  ];
540
577
  //#endregion
578
+ //#region src/internal/utilities/collection-utils.ts
579
+ /** Add a value to a Set stored in a Map, creating the Set if it doesn't exist. */
580
+ function addToSetMap(map, key, value) {
581
+ let set = map.get(key);
582
+ if (!set) {
583
+ set = /* @__PURE__ */ new Set();
584
+ map.set(key, set);
585
+ }
586
+ set.add(value);
587
+ }
588
+ //#endregion
589
+ //#region src/internal/prepass/scan-cross-file-selectors.ts
590
+ /**
591
+ * Pre-filter: matches any bare `${Identifier}` template expression.
592
+ * Used to skip files that only contain arrow functions or member expressions
593
+ * in template literals (e.g. `${props => ...}`, `${theme.color}`).
594
+ */
595
+ const BARE_TEMPLATE_IDENTIFIER_RE = /\$\{\s*[a-zA-Z_$][\w$]*\s*\}/;
596
+ /**
597
+ * Categorize cross-file selector usages into marker sidecar and global selector bridge maps.
598
+ *
599
+ * Bridge usages (from already-converted files) are skipped — the consumer handles marker
600
+ * generation via the forward selector handler, so no sidecar/bridge is needed on the target.
601
+ */
602
+ function categorizeSelectorUsages(usages, componentsNeedingMarkerSidecar, componentsNeedingGlobalSelectorBridge) {
603
+ for (const usage of usages) {
604
+ if (usage.bridgeComponentName) continue;
605
+ if (usage.consumerIsTransformed) addToSetMap(componentsNeedingMarkerSidecar, usage.resolvedPath, usage.importedName);
606
+ addToSetMap(componentsNeedingGlobalSelectorBridge, usage.resolvedPath, usage.importedName);
607
+ }
608
+ }
609
+ /**
610
+ * Regex matching bridge GlobalSelector export patterns (global for matchAll).
611
+ * Matches both:
612
+ * - Old format: `export const XGlobalSelector = ".sc2sx-..."`
613
+ * - New format: `` export const XGlobalSelector = `.${xBridgeClass}` ``
614
+ */
615
+ const BRIDGE_EXPORT_RE = /export\s+const\s+(\w+GlobalSelector)\s*=\s*(?:["']\.sc2sx-|`\.\$\{)/g;
616
+ /**
617
+ * Detect whether an imported name is a bridge GlobalSelector from an
618
+ * already-converted StyleX file.
619
+ *
620
+ * Detection criteria (hybrid fast + safe):
621
+ * 1. Variable name ends with "GlobalSelector" AND the stripped name starts uppercase
622
+ * 2. Target file contains "@stylexjs/stylex" (string check, no parse)
623
+ * 3. Target file has a matching `export const XGlobalSelector = ".sc2sx-"` pattern
624
+ *
625
+ * @returns The stripped component name (e.g., "CollapseArrowIcon" for
626
+ * "CollapseArrowIconGlobalSelector"), or null if not a bridge.
627
+ */
628
+ function detectBridgeGlobalSelector(importedName, resolvedPath, readFile) {
629
+ if (!importedName.endsWith("GlobalSelector")) return null;
630
+ const stripped = importedName.slice(0, -14);
631
+ if (!stripped || !/^[A-Z]/.test(stripped)) return null;
632
+ const content = readFile(resolvedPath);
633
+ if (!content || !content.includes("@stylexjs/stylex")) return null;
634
+ let found = false;
635
+ for (const m of content.matchAll(BRIDGE_EXPORT_RE)) if (m[1] === importedName) {
636
+ found = true;
637
+ break;
638
+ }
639
+ if (!found) return null;
640
+ return stripped;
641
+ }
642
+ /**
643
+ * If `importedName` is a bridge GlobalSelector, populate bridge fields on `usage`
644
+ * and find the corresponding component import from the same source.
645
+ */
646
+ function applyBridgeFields(usage, importedName, localName, resolvedPath, importMap, readFile) {
647
+ const bridgeName = detectBridgeGlobalSelector(importedName, resolvedPath, readFile);
648
+ if (!bridgeName) return;
649
+ usage.bridgeComponentName = bridgeName;
650
+ const imp = importMap.get(localName);
651
+ if (!imp) return;
652
+ let defaultImportLocal;
653
+ for (const [otherLocal, otherImp] of importMap) {
654
+ if (otherImp.source !== imp.source || otherLocal === localName) continue;
655
+ if (otherImp.importedName === bridgeName) {
656
+ usage.bridgeComponentLocalName = otherLocal;
657
+ defaultImportLocal = void 0;
658
+ break;
659
+ }
660
+ if (otherImp.importedName === "default" && defaultImportLocal === void 0) defaultImportLocal = otherLocal;
661
+ }
662
+ if (defaultImportLocal !== void 0) usage.bridgeComponentLocalName = defaultImportLocal;
663
+ }
664
+ /** Global version for matchAll/replace operations */
665
+ const PLACEHOLDER_RE_G = new RegExp(PLACEHOLDER_RE.source, "g");
666
+ /**
667
+ * Walk the AST collecting ImportDeclaration and TaggedTemplateExpression nodes.
668
+ *
669
+ * Uses a targeted recursive walk — only descends into node types that can
670
+ * contain these targets (skips type annotations, comments, etc.).
671
+ */
672
+ function walkForImportsAndTemplates(node, imports, templates) {
673
+ if (!node || typeof node !== "object") return;
674
+ const n = node;
675
+ if (n.type === "ImportDeclaration") {
676
+ imports.push(n);
677
+ return;
678
+ }
679
+ if (n.type === "TaggedTemplateExpression") templates.push(n);
680
+ for (const key of Object.keys(n)) {
681
+ if (key === "type" || key === "start" || key === "end" || key === "loc") continue;
682
+ const val = n[key];
683
+ if (Array.isArray(val)) for (const child of val) walkForImportsAndTemplates(child, imports, templates);
684
+ else if (val && typeof val === "object" && val.type) walkForImportsAndTemplates(val, imports, templates);
685
+ }
686
+ }
687
+ /** Build a map of localName → import info from raw ImportDeclaration nodes. */
688
+ function buildImportMapFromNodes(importNodes) {
689
+ const map = /* @__PURE__ */ new Map();
690
+ for (const node of importNodes) {
691
+ const sourceValue = node.source?.value;
692
+ if (typeof sourceValue !== "string") continue;
693
+ const specifiers = node.specifiers;
694
+ if (!specifiers) continue;
695
+ for (const spec of specifiers) {
696
+ const localName = getNodeName(spec.local);
697
+ if (!localName) continue;
698
+ if (spec.type === "ImportDefaultSpecifier") map.set(localName, {
699
+ source: sourceValue,
700
+ importedName: "default"
701
+ });
702
+ else if (spec.type === "ImportNamespaceSpecifier") map.set(localName, {
703
+ source: sourceValue,
704
+ importedName: "*"
705
+ });
706
+ else if (spec.type === "ImportSpecifier") {
707
+ const importedName = getNodeName(spec.imported) ?? localName;
708
+ map.set(localName, {
709
+ source: sourceValue,
710
+ importedName
711
+ });
712
+ }
713
+ }
714
+ }
715
+ return map;
716
+ }
717
+ /**
718
+ * Local identifiers that refer to `styled` from `"styled-components"` (default and/or
719
+ * `import { styled }` / `import { styled as sc }`).
720
+ */
721
+ function collectStyledLocalBindingNames(importNodes) {
722
+ const names = /* @__PURE__ */ new Set();
723
+ for (const node of importNodes) {
724
+ if (node.source?.value !== "styled-components") continue;
725
+ const specifiers = node.specifiers;
726
+ if (!specifiers) continue;
727
+ for (const spec of specifiers) if (spec.type === "ImportDefaultSpecifier") {
728
+ const name = getNodeName(spec.local);
729
+ if (name) names.add(name);
730
+ } else if (spec.type === "ImportSpecifier") {
731
+ if (getNodeName(spec.imported) === "styled") {
732
+ const localName = getNodeName(spec.local);
733
+ if (localName) names.add(localName);
734
+ }
735
+ }
736
+ }
737
+ return names;
738
+ }
739
+ /** Find the local name for the styled-components default import. */
740
+ function findStyledImportNameFromNodes(importNodes) {
741
+ let namedStyledLocal;
742
+ for (const node of importNodes) {
743
+ if (node.source?.value !== "styled-components") continue;
744
+ const specifiers = node.specifiers;
745
+ if (!specifiers) continue;
746
+ for (const spec of specifiers) if (spec.type === "ImportDefaultSpecifier") {
747
+ const name = getNodeName(spec.local);
748
+ if (name) return name;
749
+ } else if (spec.type === "ImportSpecifier") {
750
+ if (getNodeName(spec.imported) === "styled") {
751
+ const localName = getNodeName(spec.local);
752
+ if (localName) namedStyledLocal = localName;
753
+ }
754
+ }
755
+ }
756
+ return namedStyledLocal;
757
+ }
758
+ /**
759
+ * Find local names of `css` imported from styled-components.
760
+ * Handles aliased imports like `import { css as sc } from "styled-components"`.
761
+ */
762
+ function findCssImportNamesFromNodes(importNodes) {
763
+ const names = /* @__PURE__ */ new Set();
764
+ for (const node of importNodes) {
765
+ if (node.source?.value !== "styled-components") continue;
766
+ const specifiers = node.specifiers;
767
+ if (!specifiers) continue;
768
+ for (const spec of specifiers) if (spec.type === "ImportSpecifier") {
769
+ if (getNodeName(spec.imported) === "css") {
770
+ const localName = getNodeName(spec.local);
771
+ if (localName) names.add(localName);
772
+ }
773
+ }
774
+ }
775
+ return names;
776
+ }
777
+ /**
778
+ * Find local names of imported components used as selectors inside
779
+ * styled-components template literals (both `styled` and `css` tagged templates).
780
+ */
781
+ function findComponentSelectorLocalsFromNodes(templateNodes, styledImportName, cssImportNames) {
782
+ const selectorLocals = /* @__PURE__ */ new Set();
783
+ for (const node of templateNodes) {
784
+ if (!isStyledTag(node.tag, styledImportName) && !isCssTag(node.tag, cssImportNames)) continue;
785
+ const quasi = node.quasi;
786
+ if (!quasi) continue;
787
+ const quasis = quasi.quasis;
788
+ const expressions = quasi.expressions;
789
+ if (!quasis || !expressions) continue;
790
+ const rawParts = [];
791
+ for (let i = 0; i < quasis.length; i++) {
792
+ const value = quasis[i]?.value;
793
+ rawParts.push(value?.raw ?? "");
794
+ if (i < expressions.length) rawParts.push(`__SC_EXPR_${i}__`);
795
+ }
796
+ const rawCss = rawParts.join("");
797
+ for (const match of rawCss.matchAll(PLACEHOLDER_RE_G)) {
798
+ const exprIndex = Number(match[1]);
799
+ const pos = match.index;
800
+ if (isTemplatePlaceholderInSelectorContext(rawCss, pos, match[0].length)) {
801
+ const expr = expressions[exprIndex];
802
+ if (expr?.type === "Identifier" && typeof expr.name === "string") selectorLocals.add(expr.name);
803
+ }
804
+ }
805
+ }
806
+ return selectorLocals;
807
+ }
808
+ /**
809
+ * Check whether a styled-components tag expression is a styled call.
810
+ * Matches: styled.div, styled(X), styled.div.attrs(...), styled(X).withConfig(...), etc.
811
+ */
812
+ function isStyledTag(tag, styledName) {
813
+ if (!tag || typeof tag !== "object") return false;
814
+ if (tag.type === "MemberExpression") {
815
+ const obj = tag.object;
816
+ if (obj?.type === "Identifier" && obj.name === styledName) return true;
817
+ }
818
+ if (tag.type === "CallExpression") {
819
+ const callee = tag.callee;
820
+ if (callee?.type === "Identifier" && callee.name === styledName) return true;
821
+ if (callee?.type === "MemberExpression" && callee.object) return isStyledTag(callee.object, styledName);
822
+ }
823
+ return false;
824
+ }
825
+ /** Check if a template tag is the `css` helper from styled-components. */
826
+ function isCssTag(tag, cssImportNames) {
827
+ if (!tag || !cssImportNames || cssImportNames.size === 0) return false;
828
+ return tag.type === "Identifier" && typeof tag.name === "string" && cssImportNames.has(tag.name);
829
+ }
830
+ /**
831
+ * Build the shared `DEBUG_CODEMOD` report lines for cross-file prepass info.
832
+ * `header` distinguishes the standalone scan from the unified prepass; callers
833
+ * may append their own sections before writing.
834
+ */
835
+ function buildCrossFileDebugLines(header, scannedFiles, info) {
836
+ const cwd = process.cwd();
837
+ const rel = (p) => relative(cwd, p);
838
+ const lines = [header];
839
+ lines.push(` Scanned ${scannedFiles.length} file(s)`);
840
+ if (info.selectorUsages.size === 0) lines.push(" No cross-file selector usages found.");
841
+ else {
842
+ lines.push(` Found cross-file selector usages in ${info.selectorUsages.size} file(s):`);
843
+ for (const [consumer, usages] of info.selectorUsages) for (const u of usages) lines.push(` ${rel(consumer)} → ${u.importedName} (from ${rel(u.resolvedPath)}, transformed=${u.consumerIsTransformed})`);
844
+ }
845
+ if (info.componentsNeedingMarkerSidecar.size > 0) {
846
+ lines.push(" Components needing marker sidecar (both consumer and target transformed):");
847
+ for (const [file, names] of info.componentsNeedingMarkerSidecar) lines.push(` ${rel(file)}: ${[...names].join(", ")}`);
848
+ }
849
+ if (info.componentsNeedingGlobalSelectorBridge.size > 0) {
850
+ lines.push(" Components needing global selector bridge className (consumer not transformed):");
851
+ for (const [file, names] of info.componentsNeedingGlobalSelectorBridge) lines.push(` ${rel(file)}: ${[...names].join(", ")}`);
852
+ }
853
+ return lines;
854
+ }
855
+ /** Safely extract the name string from an AST identifier-like node. */
856
+ function getNodeName(node) {
857
+ if (!node || typeof node !== "object") return;
858
+ if (node.type === "Identifier" && typeof node.name === "string") return node.name;
859
+ }
860
+ /** Deduplicate and resolve two file lists into a single array of absolute paths. */
861
+ function deduplicateAndResolve(filesToTransform, consumerPaths) {
862
+ const seen = /* @__PURE__ */ new Set();
863
+ const result = [];
864
+ for (const f of filesToTransform) {
865
+ const abs = resolve(f);
866
+ if (!seen.has(abs)) {
867
+ seen.add(abs);
868
+ result.push(abs);
869
+ }
870
+ }
871
+ for (const f of consumerPaths) {
872
+ const abs = resolve(f);
873
+ if (!seen.has(abs)) {
874
+ seen.add(abs);
875
+ result.push(abs);
876
+ }
877
+ }
878
+ return result;
879
+ }
880
+ //#endregion
541
881
  //#region src/internal/utilities/ast-walk.ts
542
882
  const SKIPPED_KEYS = new Set([
543
883
  "loc",
@@ -563,4 +903,4 @@ function walkAst(root, visitor) {
563
903
  visit(root);
564
904
  }
565
905
  //#endregion
566
- export { fileImportsFrom as a, resolveBarrelReExport as c, Logger as d, PARTIAL_MIGRATION_INCOMPLETE_WARNING as f, fileExports as i, resolveBarrelReExportBinding as l, createPrepassParser as n, findImportSource as o, UNSUPPORTED_SHOULD_FORWARD_PROP_WARNING as p, findDefaultExportedLocalName as r, getReExportedSourceName as s, walkAst as t, CASCADE_CONFLICT_WARNING as u };
906
+ export { PARTIAL_MIGRATION_INCOMPLETE_WARNING as C, createAstSafeJsonReplacer as D, assertNoNullNodesInArrays as E, Logger as S, getCascadeDependedFilePath as T, findImportSource as _, buildImportMapFromNodes as a, resolveBarrelReExportBinding as b, deduplicateAndResolve as c, findStyledImportNameFromNodes as d, walkForImportsAndTemplates as f, fileImportsFrom as g, fileExports as h, buildCrossFileDebugLines as i, findComponentSelectorLocalsFromNodes as l, createPrepassParser as m, BARE_TEMPLATE_IDENTIFIER_RE as n, categorizeSelectorUsages as o, addToSetMap as p, applyBridgeFields as r, collectStyledLocalBindingNames as s, walkAst as t, findCssImportNamesFromNodes as u, getReExportedSourceName as v, UNSUPPORTED_SHOULD_FORWARD_PROP_WARNING as w, CASCADE_CONFLICT_WARNING as x, resolveBarrelReExport as y };
@@ -1,6 +1,6 @@
1
+ import { t as isSelectorContext } from "./selector-context-heuristic-Dptd93Xe.mjs";
1
2
  import { r as toRealPath } from "./path-utils-BC4U8X_q.mjs";
2
3
  import { r as escapeRegex } from "./string-utils-Bo3cWgss.mjs";
3
- import { t as isSelectorContext } from "./selector-context-heuristic-LVizWWOR.mjs";
4
4
  import { readFileSync } from "node:fs";
5
5
  //#region src/internal/bridge-consumer-patcher.ts
6
6
  /**
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as CollectedWarning, c as MarkerFileContext, l as defineAdapter, n as TransformMode, o as AdapterInput, s as ImportSource } from "./transform-types-BGGNjb8R.mjs";
1
+ import { a as ImportSource, i as AdapterInput, o as MarkerFileContext, s as defineAdapter, t as CollectedWarning } from "./logger-ByYsVkrB.mjs";
2
2
 
3
3
  //#region src/run.d.ts
4
4
  interface RunTransformOptions {
@@ -64,21 +64,11 @@ interface RunTransformOptions {
64
64
  */
65
65
  maxExamples?: number;
66
66
  /**
67
- * Suppress jscodeshift runner output.
67
+ * Suppress console output: both the per-file runner messages and the final
68
+ * warning summary report. Warnings are still collected and returned.
68
69
  * @default false
69
70
  */
70
71
  silent?: boolean;
71
- /**
72
- * Controls which styled declarations are eligible for conversion.
73
- *
74
- * - `"all"` converts every supported styled declaration.
75
- * - `"leavesOnly"` only converts declarations whose render base is intrinsic
76
- * after adapter resolution, or that wrap another leaf styled declaration in
77
- * the transform run (including cross-file imports).
78
- *
79
- * @default "all"
80
- */
81
- transformMode?: TransformMode;
82
72
  /**
83
73
  * When true, allow the codemod to leave individual styled declarations as-is when
84
74
  * they hit an unsupported pattern while transforming the rest of the file. This
@@ -99,6 +89,13 @@ interface RunTransformOptions {
99
89
  * @default false
100
90
  */
101
91
  collectStandaloneFileResults?: boolean;
92
+ /**
93
+ * Absolute paths of files to treat as already converted to StyleX, even though
94
+ * they are not. Cascade-conflict checks then "see past" these files so a
95
+ * consumer's own unsupported patterns surface instead of being masked by the
96
+ * cascade bail. Intended for analysis-only/dry runs (e.g. the migration plan).
97
+ */
98
+ assumeConvertedFiles?: string[];
102
99
  }
103
100
  interface RunTransformResult {
104
101
  /** Number of files that had errors */
@@ -113,6 +110,8 @@ interface RunTransformResult {
113
110
  timeElapsed: number;
114
111
  /** Warnings emitted during transformation */
115
112
  warnings: CollectedWarning[];
113
+ /** Per-file outcomes from the dependency-ordered run. */
114
+ fileResults: TransformFileResult[];
116
115
  /** Per-file outcomes from isolated transforms, populated when requested. */
117
116
  standaloneFileResults?: TransformFileResult[];
118
117
  /** Warnings from isolated transforms, populated when requested. */
@@ -162,4 +161,83 @@ type TransformFileResult = {
162
161
  status: "error" | "skipped" | "unchanged" | "transformed";
163
162
  };
164
163
  //#endregion
165
- export { type AdapterInput, type ImportSource, type MarkerFileContext, defineAdapter, runTransform };
164
+ //#region src/migration-plan.d.ts
165
+ interface MigrationPlanOptions {
166
+ /** Glob pattern(s) for the files the codemod would transform. */
167
+ files: string | string[];
168
+ /** Glob pattern(s) of additional files to scan for consumers, or `null`. */
169
+ consumerPaths: string | string[] | null;
170
+ /** Adapter for the transform (same adapter you would pass to `runTransform`). */
171
+ adapter: AdapterInput;
172
+ /** jscodeshift parser to use. @default "tsx" */
173
+ parser?: "babel" | "babylon" | "flow" | "ts" | "tsx";
174
+ /**
175
+ * Safety cap on fixpoint analysis passes used to reveal cascade-masked
176
+ * blockers. Analysis throws if it doesn't stabilize within this many passes
177
+ * (rather than returning a partial plan). @default 50
178
+ */
179
+ maxAnalysisPasses?: number;
180
+ }
181
+ interface ImportedExportUsage {
182
+ /** Exported name consumers import (`"default"` for a default import, `"*"` for a namespace). */
183
+ exportName: string;
184
+ /** Number of distinct files importing this export. */
185
+ consumerCount: number;
186
+ }
187
+ interface ManualConversionReason {
188
+ /** The bail message explaining why the codemod cannot convert the file. */
189
+ message: string;
190
+ /** Source locations where the unsupported pattern occurs. */
191
+ locations: Array<{
192
+ filePath: string;
193
+ line: number;
194
+ column: number;
195
+ }>;
196
+ }
197
+ interface ManualConversionFile {
198
+ /** Path of the file that must be converted by hand. */
199
+ filePath: string;
200
+ /** 1-based position in the recommended bottom-up conversion order. */
201
+ order: number;
202
+ /** Number of distinct files that import from this file. */
203
+ consumerCount: number;
204
+ /**
205
+ * Number of files that auto-convert once this file is converted, because this
206
+ * file is their ONLY remaining blocker (single cascade blocker, no unsupported
207
+ * patterns of their own). Converting this file is sufficient for these.
208
+ */
209
+ soleBlockerFileCount: number;
210
+ /**
211
+ * Number of files that cascade-bail on this file (raw chain involvement).
212
+ * Includes files that also have other blockers, so converting this file alone
213
+ * does not necessarily unblock all of them. Always ≥ `soleBlockerFileCount`.
214
+ */
215
+ blockedFileCount: number;
216
+ /** Which exports consumers import, so you know what to convert first. */
217
+ importedExports: ImportedExportUsage[];
218
+ /** Why the codemod cannot convert this file automatically. */
219
+ reasons: ManualConversionReason[];
220
+ /** Other files in this plan that this file imports (must be converted first). */
221
+ dependsOn: string[];
222
+ }
223
+ interface MigrationPlan {
224
+ /** Files to convert by hand, ordered by unblock impact (dependencies first). */
225
+ manualConversionFiles: ManualConversionFile[];
226
+ /** Total number of files matched by the `files` glob. */
227
+ totalFiles: number;
228
+ /**
229
+ * Number of distinct files that auto-convert once ALL listed files are
230
+ * converted (cascade-blocked files that have no unsupported patterns of their
231
+ * own). This is the whole-plan payoff, not attributable to any single file.
232
+ */
233
+ unlocksFileCount: number;
234
+ }
235
+ /**
236
+ * Run the codemod in analysis-only (dry) mode and compute the ordered list of
237
+ * files that block the rest of the migration and must be converted manually.
238
+ */
239
+ declare function analyzeMigrationPlan(options: MigrationPlanOptions): Promise<MigrationPlan>;
240
+ /** Render a {@link MigrationPlan} as a human-readable, actionable report. */
241
+ declare function formatMigrationPlan(plan: MigrationPlan): string;
242
+ //#endregion
243
+ export { type AdapterInput, type ImportSource, type ImportedExportUsage, type ManualConversionFile, type ManualConversionReason, type MarkerFileContext, type MigrationPlan, type MigrationPlanOptions, analyzeMigrationPlan, defineAdapter, formatMigrationPlan, runTransform };