bestax-migrate 2.3.8 → 2.4.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.
Files changed (72) hide show
  1. package/README.md +21 -17
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +45 -11
  5. package/dist/sources/_shared/bestax-import.d.ts +48 -0
  6. package/dist/sources/_shared/bestax-import.d.ts.map +1 -0
  7. package/dist/sources/_shared/bestax-import.js +142 -0
  8. package/dist/sources/_shared/css-imports.d.ts +33 -0
  9. package/dist/sources/_shared/css-imports.d.ts.map +1 -0
  10. package/dist/sources/_shared/css-imports.js +103 -0
  11. package/dist/sources/_shared/deps-common.d.ts +51 -0
  12. package/dist/sources/_shared/deps-common.d.ts.map +1 -0
  13. package/dist/sources/_shared/deps-common.js +115 -0
  14. package/dist/sources/_shared/jsx-utils.d.ts +28 -0
  15. package/dist/sources/_shared/jsx-utils.d.ts.map +1 -1
  16. package/dist/sources/_shared/jsx-utils.js +59 -1
  17. package/dist/sources/_shared/make-styles-transform.d.ts +6 -2
  18. package/dist/sources/_shared/make-styles-transform.d.ts.map +1 -1
  19. package/dist/sources/_shared/make-styles-transform.js +12 -4
  20. package/dist/sources/_shared/props.d.ts.map +1 -1
  21. package/dist/sources/_shared/props.js +2 -2
  22. package/dist/sources/_shared/specials-utils.d.ts +1 -1
  23. package/dist/sources/_shared/specials-utils.d.ts.map +1 -1
  24. package/dist/sources/_shared/specials-utils.js +19 -1
  25. package/dist/sources/bloomer/deps.d.ts.map +1 -1
  26. package/dist/sources/bloomer/deps.js +14 -89
  27. package/dist/sources/bloomer/transform.d.ts.map +1 -1
  28. package/dist/sources/bloomer/transform.js +12 -186
  29. package/dist/sources/bulma-classes/class-map.d.ts +161 -0
  30. package/dist/sources/bulma-classes/class-map.d.ts.map +1 -0
  31. package/dist/sources/bulma-classes/class-map.js +1046 -0
  32. package/dist/sources/bulma-classes/deps.d.ts +14 -0
  33. package/dist/sources/bulma-classes/deps.d.ts.map +1 -0
  34. package/dist/sources/bulma-classes/deps.js +56 -0
  35. package/dist/sources/bulma-classes/index.d.ts +3 -0
  36. package/dist/sources/bulma-classes/index.d.ts.map +1 -0
  37. package/dist/sources/bulma-classes/index.js +35 -0
  38. package/dist/sources/bulma-classes/plan.d.ts +53 -0
  39. package/dist/sources/bulma-classes/plan.d.ts.map +1 -0
  40. package/dist/sources/bulma-classes/plan.js +231 -0
  41. package/dist/sources/bulma-classes/project.d.ts +40 -0
  42. package/dist/sources/bulma-classes/project.d.ts.map +1 -0
  43. package/dist/sources/bulma-classes/project.js +203 -0
  44. package/dist/sources/bulma-classes/rules.d.ts +16 -0
  45. package/dist/sources/bulma-classes/rules.d.ts.map +1 -0
  46. package/dist/sources/bulma-classes/rules.js +53 -0
  47. package/dist/sources/bulma-classes/styles.d.ts +13 -0
  48. package/dist/sources/bulma-classes/styles.d.ts.map +1 -0
  49. package/dist/sources/bulma-classes/styles.js +18 -0
  50. package/dist/sources/bulma-classes/transform.d.ts +21 -0
  51. package/dist/sources/bulma-classes/transform.d.ts.map +1 -0
  52. package/dist/sources/bulma-classes/transform.js +486 -0
  53. package/dist/sources/rbx/deps.d.ts.map +1 -1
  54. package/dist/sources/rbx/deps.js +29 -117
  55. package/dist/sources/rbx/mapping.d.ts.map +1 -1
  56. package/dist/sources/rbx/mapping.js +9 -1
  57. package/dist/sources/rbx/specials.d.ts.map +1 -1
  58. package/dist/sources/rbx/specials.js +20 -10
  59. package/dist/sources/rbx/transform.d.ts.map +1 -1
  60. package/dist/sources/rbx/transform.js +22 -204
  61. package/dist/sources/react-bulma-components/deps.d.ts.map +1 -1
  62. package/dist/sources/react-bulma-components/deps.js +12 -87
  63. package/dist/sources/react-bulma-components/mapping.js +1 -1
  64. package/dist/sources/react-bulma-components/specials.d.ts.map +1 -1
  65. package/dist/sources/react-bulma-components/specials.js +42 -4
  66. package/dist/sources/react-bulma-components/transform.d.ts.map +1 -1
  67. package/dist/sources/react-bulma-components/transform.js +16 -187
  68. package/dist/sources/registry.d.ts.map +1 -1
  69. package/dist/sources/registry.js +2 -0
  70. package/dist/types.d.ts +25 -0
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +4 -3
@@ -4,28 +4,17 @@
4
4
  * with dart-sass. Pure data-in/data-out — the CLI owns file IO, and no
5
5
  * package manager is ever invoked (the report tells the user to install).
6
6
  */
7
- import { isPreV1 } from '../_shared/semver-range.js';
8
- const BESTAX_RANGE = '^5';
9
- const BULMA_RANGE = '^1.0.4';
10
- // Bulma v1's sass tree uses `color.channel(…)` — needs dart-sass ≥ 1.79.
11
- const SASS_RANGE = '^1.79.0';
12
- const DEP_SECTIONS = ['dependencies', 'devDependencies'];
7
+ import { DEP_SECTIONS, addBestax, moveBulmaToV1, openManifest, replaceNodeSass, reportPeerRanges, } from '../_shared/deps-common.js';
13
8
  export const updateDependencies = (filePath, pkg, collector, options) => {
14
- const changes = [];
15
- const next = pkg;
16
- const section = (name) => (next[name] ?? undefined);
17
- const note = (message) => {
18
- changes.push(message);
19
- collector?.add({ file: filePath, line: null, rule: 'deps', message });
20
- };
9
+ const manifest = openManifest(filePath, pkg, collector);
21
10
  // react-bulma-components goes away entirely.
22
11
  let removedSource = false;
23
12
  for (const name of DEP_SECTIONS) {
24
- const deps = section(name);
13
+ const deps = manifest.section(name);
25
14
  if (deps && 'react-bulma-components' in deps) {
26
15
  delete deps['react-bulma-components'];
27
16
  removedSource = true;
28
- note(`removed react-bulma-components from ${name}`);
17
+ manifest.note(`removed react-bulma-components from ${name}`);
29
18
  }
30
19
  }
31
20
  // The transform deliberately RETAINS imports for components with no bestax
@@ -35,76 +24,12 @@ export const updateDependencies = (filePath, pkg, collector, options) => {
35
24
  // letting them find out at build time. rbx has carried this warning since
36
25
  // it shipped; this source retains imports the same way and did not.
37
26
  if (removedSource && options.sourceStillImported) {
38
- collector?.add({
39
- file: filePath,
40
- line: null,
41
- rule: 'deps',
42
- message: 'react-bulma-components was removed from package.json, but some files still import it for components with no bestax equivalent — resolve those `TODO(bestax-migrate)` imports before installing, or re-add react-bulma-components until you have',
43
- });
44
- }
45
- // @allxsmith/bestax-bulma comes in (runtime dependency).
46
- const dependencies = (next.dependencies ??= {});
47
- if (!dependencies['@allxsmith/bestax-bulma'] &&
48
- !section('devDependencies')?.['@allxsmith/bestax-bulma']) {
49
- dependencies['@allxsmith/bestax-bulma'] = BESTAX_RANGE;
50
- note(`added @allxsmith/bestax-bulma ${BESTAX_RANGE} to dependencies`);
51
- }
52
- // Bulma: bump a pre-1 range; add only when sources still reference bulma/…
53
- // directly (otherwise it arrives transitively via bestax-bulma).
54
- let bulmaDeclared = false;
55
- for (const name of DEP_SECTIONS) {
56
- const deps = section(name);
57
- if (deps?.bulma) {
58
- bulmaDeclared = true;
59
- if (isPreV1(deps.bulma)) {
60
- deps.bulma = BULMA_RANGE;
61
- note(`bumped bulma to ${BULMA_RANGE} in ${name} (was pre-1.0)`);
62
- }
63
- }
64
- }
65
- if (!bulmaDeclared && options.bulmaReferenced) {
66
- dependencies.bulma = BULMA_RANGE;
67
- note(`added bulma ${BULMA_RANGE} to dependencies (sources import bulma/… directly)`);
68
- }
69
- // bestax-bulma requires React 18/19; RBC v4 also ran on 17. Report only —
70
- // a React major upgrade is the app's own migration step.
71
- for (const name of DEP_SECTIONS) {
72
- const range = section(name)?.react;
73
- if (range && /^[~^]?(?:[0-9]|1[0-7])(?:[.x]|$)/.test(range.trim())) {
74
- collector?.add({
75
- file: filePath,
76
- line: null,
77
- rule: 'peer-deps',
78
- message: `react ${range} predates bestax-bulma's peer range (^18 || ^19) — upgrade react and react-dom to 18 or 19 before installing`,
79
- });
80
- }
81
- }
82
- // Font Awesome older than 6 conflicts with bestax-bulma's optional peer
83
- // range and makes `npm install` fail with ERESOLVE. Report only — icon
84
- // names change across FA majors, so upgrading is the app's decision.
85
- for (const name of DEP_SECTIONS) {
86
- const range = section(name)?.['@fortawesome/fontawesome-free'];
87
- if (range && /^[~^]?[0-5](?:[.x]|$)/.test(range.trim())) {
88
- collector?.add({
89
- file: filePath,
90
- line: null,
91
- rule: 'peer-deps',
92
- message: `@fortawesome/fontawesome-free ${range} predates bestax-bulma's optional peer range (^6.7.2 || ^7.0.0) — upgrade it, or install with \`npm install --legacy-peer-deps\``,
93
- });
94
- }
95
- }
96
- // node-sass is dead; dart-sass replaces it in the same section.
97
- for (const name of DEP_SECTIONS) {
98
- const deps = section(name);
99
- if (deps && 'node-sass' in deps) {
100
- delete deps['node-sass'];
101
- note(`removed node-sass from ${name}`);
102
- const sassDeclared = DEP_SECTIONS.some(s => section(s)?.sass);
103
- if (!sassDeclared) {
104
- deps.sass = SASS_RANGE;
105
- note(`added sass ${SASS_RANGE} to ${name} (replaces node-sass)`);
106
- }
107
- }
108
- }
109
- return changes.length > 0 ? next : null;
27
+ manifest.report('deps', 'react-bulma-components was removed from package.json, but some files still import it for components with no bestax equivalent — resolve those `TODO(bestax-migrate)` imports before installing, or re-add react-bulma-components until you have');
28
+ }
29
+ addBestax(manifest);
30
+ moveBulmaToV1(manifest, options.bulmaReferenced);
31
+ // RBC v4 also ran on React 17, which bestax-bulma's peer range excludes.
32
+ reportPeerRanges(manifest);
33
+ replaceNodeSass(manifest);
34
+ return manifest.result();
110
35
  };
@@ -26,7 +26,7 @@ export const UNIVERSAL_PROPS = {
26
26
  todo: 'this bestax component has no `as` prop; restructure the element instead',
27
27
  },
28
28
  domRef: {
29
- todo: 'bestax-bulma has no `domRef`. The form controls and `Button`, `LinkButton`, `Modal`, `Dropdown`, `Navbar` (plus `Navbar.Burger` and `Navbar.Link`), `Dialog`, `Sidebar`, `Toast` and `Carousel` forward a ref — on those, rename `domRef` to `ref`. Mind the `Navbar.Dropdown` collision: the `Navbar.Item` that wrapped your dropdown became bestax `Navbar.Dropdown` and forwards one, while your `Navbar.Dropdown` became `Navbar.DropdownMenu` and does not. The rest forward no ref: put the ref on a DOM child or wrap the component',
29
+ todo: 'bestax-bulma has no `domRef`. The form controls, plus `Avatar`, `Button`, `Carousel`, `CarouselItem`, `Dialog`, `Dropdown`, `Link`, `LinkButton`, `Menu.Item`, `Modal`, `Navbar`, `Navbar.Burger`, `Navbar.Dropdown`, `Navbar.Item`, `Navbar.Link`, `Sidebar` and `Toast` forward a ref — on those, rename `domRef` to `ref`, though not on the `Field`, `Field.Label`, `Field.Body`, `Checkboxes` and `Radios` wrappers, which forward none. Mind the `Navbar.Dropdown` collision: the `Navbar.Item` that wrapped your dropdown became bestax `Navbar.Dropdown` and forwards one, while your `Navbar.Dropdown` became `Navbar.DropdownMenu` and does not. The rest forward no ref: React 18 drops it with a warning, React 19 leaves it wherever the rest props go, so put the ref on a DOM child or wrap the component',
30
30
  },
31
31
  // ColorProps
32
32
  backgroundColor: { rename: 'bgColor' },
@@ -1 +1 @@
1
- {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAQL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AA+yB9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf;AAED,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAGjE,CAAC"}
1
+ {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAQL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AA82B9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf;AAED,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAGjE,CAAC"}
@@ -28,7 +28,7 @@ function keptAttrs(ctx, path, element, where, tag) {
28
28
  const kept = new Set(stripModifierProps(ctx, path, attributesOf(element), where));
29
29
  return (element.openingElement.attributes ?? []).filter((a) => a.type === 'JSXSpreadAttribute' || kept.has(a));
30
30
  }
31
- const stripModifierProps = makeStripModifierProps(UNIVERSAL_PROPS, RESPONSIVE_BREAKPOINTS);
31
+ const stripModifierProps = makeStripModifierProps(UNIVERSAL_PROPS, RESPONSIVE_BREAKPOINTS, 'domRef');
32
32
  const SPECIALS = {
33
33
  /**
34
34
  * RBC defaults Columns `multiline` to TRUE (Bulma and bestax default to
@@ -490,7 +490,6 @@ const SPECIALS = {
490
490
  removeAttr(element, activeAttr);
491
491
  }
492
492
  liClass = mergeClassName(ctx, path, element, liClass, 'Breadcrumb.Item');
493
- const anchorAttrs = keptAttrs(ctx, path, element, 'Breadcrumb.Item', 'a');
494
493
  const children = element.children ?? [];
495
494
  const solidChildren = children.filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
496
495
  // `<Breadcrumb.Item><a href>…</a></Breadcrumb.Item>` already carries its
@@ -501,11 +500,37 @@ const SPECIALS = {
501
500
  solidChildren[0].openingElement.name.name === 'a'
502
501
  ? solidChildren[0]
503
502
  : null;
503
+ // The strip below renames `domRef` to `ref`, so a collision with a `ref`
504
+ // the anchor already sets is settled here, while the prop still carries
505
+ // the name the user wrote.
506
+ const domRefAttr = findAttr(element, 'domRef');
507
+ if (existingAnchor && domRefAttr && findAttr(existingAnchor, 'ref')) {
508
+ removeAttr(element, domRefAttr);
509
+ addTodo(ctx, path, 'prop:domRef', 'Breadcrumb.Item `domRef` was dropped: its <a> child already sets `ref` — reconcile by hand');
510
+ }
511
+ const anchorAttrs = keptAttrs(ctx, path, element, 'Breadcrumb.Item', 'a');
504
512
  if (existingAnchor && anchorAttrs.length > 0) {
505
- existingAnchor.openingElement.attributes = [
513
+ // Merging onto an anchor the user already wrote keeps the rule
514
+ // `collapseOntoChild` does: the item's spreads go FIRST, as one block in
515
+ // source order, so the anchor's explicit props still win over them; and
516
+ // a named prop both set keeps the anchor's value and is flagged, since
517
+ // writing it twice does not compile.
518
+ const spreads = anchorAttrs.filter((a) => a.type === 'JSXSpreadAttribute');
519
+ const merged = [
520
+ ...spreads,
506
521
  ...(existingAnchor.openingElement.attributes ?? []),
507
- ...anchorAttrs,
508
522
  ];
523
+ for (const attr of anchorAttrs) {
524
+ if (attr.type === 'JSXSpreadAttribute')
525
+ continue;
526
+ const name = attr.name.name;
527
+ if (findAttr(existingAnchor, name)) {
528
+ addTodo(ctx, path, `prop:${name}`, `Breadcrumb.Item \`${name}\` was dropped: its <a> child already sets \`${name}\` — reconcile by hand`);
529
+ continue;
530
+ }
531
+ merged.push(attr);
532
+ }
533
+ existingAnchor.openingElement.attributes = merged;
509
534
  }
510
535
  const anchor = existingAnchor ?? plainElement(j, 'a', undefined, anchorAttrs, children);
511
536
  path.replace(plainElement(j, 'li', liClass, [], [anchor]));
@@ -519,6 +544,19 @@ const SPECIALS = {
519
544
  if (findAttr(element, 'className')) {
520
545
  addTodo(ctx, path, 'prop:className', 'Table.Container className; the container folded into `isResponsive` on its Table — re-apply the class by hand');
521
546
  }
547
+ // The container element is gone after the fold. What it carried
548
+ // (`domRef`, `id`, handlers) applied to the wrapping div, and moving it
549
+ // onto the Table would put it on the <table> instead, so it is reported
550
+ // rather than carried. Only `className` used to be reported.
551
+ for (const attr of attributesOf(element)) {
552
+ const name = attr?.name?.name;
553
+ if (!name || name === 'className')
554
+ continue;
555
+ addTodo(ctx, path, `prop:${name}`, `Table.Container \`${name}\` was dropped: the container folded into \`isResponsive\` on its Table, so there is no element left to carry it — re-apply by hand`);
556
+ }
557
+ if ((element.openingElement.attributes ?? []).some((a) => a.type === 'JSXSpreadAttribute')) {
558
+ addTodo(ctx, path, 'prop:spread', 'Table.Container spread props were dropped: the container folded into `isResponsive` on its Table, so there is no element left to carry them — re-apply by hand');
559
+ }
522
560
  const child = children[0];
523
561
  addAttr(child, makeAttr(ctx.j, 'isResponsive'));
524
562
  path.replace(child);
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA6CvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAy8BpB"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAoCvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CA6wBpB"}
@@ -14,32 +14,16 @@
14
14
  * statement plus an entry in the run report.
15
15
  */
16
16
  import { MAPPING, UNIVERSAL_PROPS, resolveMapping } from './mapping.js';
17
- import { addTodo, attributesOf, jsxNameParts, renameElement, } from '../_shared/jsx-utils.js';
17
+ import { addTodo, attributesOf, forgetJsxParens, jsxNameParts, renameElement, reprintDirectives, } from '../_shared/jsx-utils.js';
18
18
  import { applyPropAction, applyUniversalProps } from '../_shared/props.js';
19
19
  import { enforcePolymorphicProps } from '../_shared/polymorphic.js';
20
+ import { bestaxValueLocals, buildBestaxImport, carryPrunedComments, placeBestaxImport, seedBestaxImport, } from '../_shared/bestax-import.js';
21
+ import { rewriteStylesheetImports } from '../_shared/css-imports.js';
20
22
  import { collectBoundNames, makeAliasRegistry, makeReserve, nameOf, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
21
23
  import { flattenResponsiveProps } from './responsive.js';
22
24
  import { RESPONSIVE_KINDS, runSpecial } from './specials.js';
23
25
  /* eslint-disable @typescript-eslint/no-explicit-any */
24
26
  const RBC = 'react-bulma-components';
25
- const BESTAX = '@allxsmith/bestax-bulma';
26
- const BULMA_CSS = 'bulma/css/bulma.min.css';
27
- const BESTAX_CSS = '@allxsmith/bestax-bulma/bestax.css';
28
- const EXTRAS_CSS = '@allxsmith/bestax-bulma/extras.css';
29
- const BULMA_CSS_SPECIFIERS = new Set([
30
- 'bulma/css/bulma.css',
31
- 'bulma/css/bulma.min.css',
32
- ]);
33
- const BESTAX_CSS_SPECIFIERS = new Set([
34
- BESTAX_CSS,
35
- '@allxsmith/bestax-bulma/bestax.min.css',
36
- '@allxsmith/bestax-bulma/dist/bestax.css',
37
- '@allxsmith/bestax-bulma/dist/bestax.min.css',
38
- ]);
39
- const EXTRAS_CSS_SPECIFIERS = new Set([
40
- EXTRAS_CSS,
41
- '@allxsmith/bestax-bulma/dist/extras.css',
42
- ]);
43
27
  export default function transform(fileInfo, api, options = {}) {
44
28
  const j = api.jscodeshift;
45
29
  const root = j(fileInfo.source);
@@ -105,60 +89,12 @@ export default function transform(fileInfo, api, options = {}) {
105
89
  addTodo(ctx, p, 'imports', `\`export * from '${RBC}'\` was not migrated; re-export the bestax components you need by hand`);
106
90
  });
107
91
  // ---- 1a. Stylesheet imports (mode-driven) -----------------------------
108
- // `bestax` (default): everything converges on the recommended combined
109
- // bundle. `bulma`: plain Bulma v1 CSS plus the separate extras file.
110
- // `keep`: only the dead RBC v3 CSS import is touched.
111
- const cssMode = options.cssMode ?? 'bestax';
112
- let sawBestaxCss = root
113
- .find(j.ImportDeclaration)
114
- .paths()
115
- .some(p => BESTAX_CSS_SPECIFIERS.has(String(p.node.source.value)));
116
- root.find(j.ImportDeclaration).forEach(path => {
117
- const source = String(path.node.source.value);
118
- const isRbcCss = source.startsWith(`${RBC}/`) && source.endsWith('.css');
119
- const isBulmaCss = BULMA_CSS_SPECIFIERS.has(source);
120
- const isExtrasCss = EXTRAS_CSS_SPECIFIERS.has(source);
121
- if (!isRbcCss && !isBulmaCss && !isExtrasCss)
122
- return;
123
- if (cssMode === 'bestax') {
124
- if (isRbcCss || isBulmaCss) {
125
- if (sawBestaxCss) {
126
- path.prune(); // bestax.css already imported elsewhere in this file
127
- }
128
- else {
129
- path.node.source = j.stringLiteral(BESTAX_CSS);
130
- sawBestaxCss = true;
131
- }
132
- ctx.dirty = true;
133
- }
134
- else if (isExtrasCss && sawBestaxCss) {
135
- // bestax.css already contains the extras.
136
- path.prune();
137
- ctx.dirty = true;
138
- }
139
- }
140
- else if (cssMode === 'bulma') {
141
- if (isRbcCss) {
142
- path.node.source = j.stringLiteral(BULMA_CSS);
143
- ctx.dirty = true;
144
- }
145
- if ((isRbcCss || isBulmaCss) && !sawBestaxCss) {
146
- const hasExtras = root
147
- .find(j.ImportDeclaration)
148
- .paths()
149
- .some(p => EXTRAS_CSS_SPECIFIERS.has(String(p.node.source.value)));
150
- if (!hasExtras) {
151
- // Themed Radio/Checkbox need the bestax extras next to plain Bulma.
152
- path.insertAfter(j.importDeclaration([], j.stringLiteral(EXTRAS_CSS)));
153
- ctx.dirty = true;
154
- }
155
- }
156
- }
157
- else if (isRbcCss) {
158
- // keep: minimal fix — the v3 bundled CSS no longer exists at all.
159
- path.node.source = j.stringLiteral(BULMA_CSS);
160
- addTodo(ctx, path, 'css', `replaced the react-bulma-components CSS import with '${BULMA_CSS}'; install bulma@^1 (see https://bestax.io/docs/guides/getting-started/installation)`);
161
- }
92
+ // `keep` touches only the dead RBC v3 CSS import.
93
+ rewriteStylesheetImports(ctx, root, options.cssMode ?? 'bestax', {
94
+ sourceCss: {
95
+ is: specifier => specifier.startsWith(`${RBC}/`) && specifier.endsWith('.css'),
96
+ label: 'the react-bulma-components CSS import',
97
+ },
162
98
  });
163
99
  if (imports.size === 0 && !ctx.dirty) {
164
100
  return undefined;
@@ -343,46 +279,7 @@ export default function transform(fileInfo, api, options = {}) {
343
279
  bound.add(head);
344
280
  });
345
281
  ctx.reserve = makeReserve(ctx, bound);
346
- // Names the passes actually asked for. `ctx.needed` is also seeded from an
347
- // existing bestax import's specifiers below (so JSX reuses their locals),
348
- // and that seeding alone must not turn a type-only specifier into a value
349
- // import nobody needed.
350
- const requested = new Set();
351
- const baseReserve = ctx.reserve;
352
- ctx.reserve = root => {
353
- requested.add(root);
354
- return baseReserve(root);
355
- };
356
- // Merge with an existing bestax import: reuse its locals verbatim.
357
- // Only a declaration made entirely of NAMED specifiers can be merged into.
358
- // `import * as Bulma from '@allxsmith/bestax-bulma'` matches the source too,
359
- // and appending named specifiers to it emitted
360
- // `import * as Bulma, { Box } from …` — not valid JavaScript, so the whole
361
- // file failed to parse after a migration that reported success.
362
- const existingBestax = root
363
- .find(j.ImportDeclaration, { source: { value: BESTAX } })
364
- .paths()
365
- .find(path =>
366
- // A type-only declaration cannot take a value specifier: merging a
367
- // component into `import type { … }` erases it at runtime.
368
- path.node.importKind !== 'type' &&
369
- (path.node.specifiers ?? []).every((spec) => spec.type === 'ImportSpecifier'));
370
- const preExistingImports = new Set();
371
- if (existingBestax) {
372
- for (const spec of existingBestax.node.specifiers ?? []) {
373
- if (spec.type === 'ImportSpecifier' && spec.local) {
374
- // The local name is reused either way, so the JSX never needs an
375
- // alias. But an inline `type` specifier (`import { type Box }`) is
376
- // not a value binding: it is not counted as already imported, so the
377
- // component is written as a value below — onto this specifier, which
378
- // keeps its name, rather than as a duplicate.
379
- ctx.needed.set(nameOf(spec.imported), nameOf(spec.local));
380
- if (spec.importKind !== 'type') {
381
- preExistingImports.add(nameOf(spec.imported));
382
- }
383
- }
384
- }
385
- }
282
+ const bestaxImportState = seedBestaxImport(ctx, root);
386
283
  // ---- 2. Transform JSX elements ----------------------------------------
387
284
  root.find(j.JSXElement).forEach(path => {
388
285
  const element = path.node;
@@ -745,62 +642,13 @@ export default function transform(fileInfo, api, options = {}) {
745
642
  // ---- 3. Rewrite imports -----------------------------------------------
746
643
  if (imports.size > 0) {
747
644
  const retainedNames = [...ctx.retained].sort((a, b) => a.localeCompare(b));
748
- const freshNames = [...ctx.needed.entries()]
749
- .filter(([imported]) => requested.has(imported) && !preExistingImports.has(imported))
750
- .sort((a, b) => a[0].localeCompare(b[0]));
751
- const bestaxImport = freshNames.length > 0
752
- ? j.importDeclaration(freshNames.map(([imported, local]) => j.importSpecifier(j.identifier(imported), j.identifier(local))), j.stringLiteral(BESTAX))
753
- : null;
754
- /**
755
-
756
- * A pruned declaration takes its comments with it — a licence header, an
757
-
758
- * eslint directive. Hand them to what replaces it, or to the next statement.
759
-
760
- */
761
- const carryComments = (node, importPath) => {
762
- const comments = node.comments ?? [];
763
- if (comments.length === 0)
764
- return;
765
- const body = importPath.parent?.node?.body ?? [];
766
- const index = body.indexOf(node);
767
- // The fresh specifiers are merged INTO an existing bestax import when there
768
- // is one, and the `bestaxImport` node is then discarded — so the comments
769
- // have to follow the declaration that survives, not the one that does not.
770
- const carrier = (existingBestax ? existingBestax.node : bestaxImport) ??
771
- (index >= 0 ? body[index + 1] : undefined);
772
- if (carrier) {
773
- carrier.comments = [...comments, ...(carrier.comments ?? [])];
774
- }
775
- else if (index > 0) {
776
- // The import was the last statement: the comments stay where they
777
- // were, after what precedes it.
778
- const previous = body[index - 1];
779
- previous.comments = [
780
- ...(previous.comments ?? []),
781
- ...comments.map((c) => ({
782
- ...c,
783
- leading: false,
784
- trailing: true,
785
- })),
786
- ];
787
- }
788
- else {
789
- // The import was the only statement: the comments become the file's.
790
- const program = importPath.parent?.node;
791
- if (program)
792
- program.comments = [...comments, ...(program.comments ?? [])];
793
- }
794
- node.comments = [];
795
- };
645
+ const bestaxImport = buildBestaxImport(ctx, bestaxImportState);
796
646
  let inserted = false;
797
647
  // A retained RBC specifier must never collide with a bestax import local
798
648
  // (possible when one component is both JSX-migrated and value-retained).
799
649
  // Only a VALUE local can collide at runtime: a type-only specifier the
800
650
  // seeding above recorded, and nothing promoted, binds no value.
801
- const bestaxLocals = new Set([...ctx.needed.entries()]
802
- .filter(([imported]) => requested.has(imported) || preExistingImports.has(imported))
803
- .map(([, local]) => local));
651
+ const bestaxLocals = bestaxValueLocals(ctx, bestaxImportState);
804
652
  for (const path of rbcImportPaths) {
805
653
  const node = path.node;
806
654
  const keepSpecifiers = (node.specifiers ?? []).filter((spec) => {
@@ -815,28 +663,7 @@ export default function transform(fileInfo, api, options = {}) {
815
663
  !bestaxLocals.has(nameOf(spec.local)));
816
664
  });
817
665
  if (!inserted) {
818
- if (existingBestax && bestaxImport) {
819
- const current = existingBestax.node.specifiers ?? [];
820
- const appended = [];
821
- for (const fresh of bestaxImport.specifiers) {
822
- // A type-only specifier for the same name becomes the value
823
- // import (a value import carries the type too); appending a
824
- // second `Box` beside `type Box` would be a duplicate identifier.
825
- const typeOnly = current.find((spec) => spec.type === 'ImportSpecifier' &&
826
- spec.importKind === 'type' &&
827
- nameOf(spec.imported) === nameOf(fresh.imported));
828
- if (typeOnly) {
829
- typeOnly.importKind = null;
830
- }
831
- else {
832
- appended.push(fresh);
833
- }
834
- }
835
- existingBestax.node.specifiers = [...current, ...appended];
836
- }
837
- else if (bestaxImport) {
838
- path.insertBefore(bestaxImport);
839
- }
666
+ placeBestaxImport(bestaxImportState, bestaxImport, path);
840
667
  inserted = true;
841
668
  }
842
669
  if (keepSpecifiers.length > 0) {
@@ -848,7 +675,7 @@ export default function transform(fileInfo, api, options = {}) {
848
675
  }
849
676
  }
850
677
  else {
851
- carryComments(node, path);
678
+ carryPrunedComments(node, path, bestaxImportState.existing?.node ?? bestaxImport);
852
679
  path.prune();
853
680
  }
854
681
  ctx.dirty = true;
@@ -856,6 +683,8 @@ export default function transform(fileInfo, api, options = {}) {
856
683
  }
857
684
  if (!ctx.dirty)
858
685
  return undefined;
686
+ forgetJsxParens(j, root);
687
+ reprintDirectives(j, root);
859
688
  // Double quotes match the dominant JSX-attribute convention; users run
860
689
  // their own formatter afterwards anyway. Tab-indented sources keep tabs so
861
690
  // reprinted nodes don't drift from the untouched lines around them.
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/sources/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAKnD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAInD,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEnE;AAED,wBAAgB,WAAW,IAAI,MAAM,EAAE,CAEtC"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/sources/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAMnD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAKnD,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEnE;AAED,wBAAgB,WAAW,IAAI,MAAM,EAAE,CAEtC"}
@@ -5,10 +5,12 @@
5
5
  import { reactBulmaComponents } from './react-bulma-components/index.js';
6
6
  import { rbx } from './rbx/index.js';
7
7
  import { bloomer } from './bloomer/index.js';
8
+ import { bulmaClasses } from './bulma-classes/index.js';
8
9
  export const SOURCES = {
9
10
  [reactBulmaComponents.name]: reactBulmaComponents,
10
11
  [rbx.name]: rbx,
11
12
  [bloomer.name]: bloomer,
13
+ [bulmaClasses.name]: bulmaClasses,
12
14
  };
13
15
  export function getSource(name) {
14
16
  return SOURCES[name];
package/dist/types.d.ts CHANGED
@@ -141,6 +141,31 @@ export interface MigrationSource {
141
141
  name: string;
142
142
  /** Human-readable label for the report header. */
143
143
  label: string;
144
+ /**
145
+ * The npm package this source migrates away from, when it is not `name`.
146
+ * `null` for a source with no package to move off: nothing is looked for
147
+ * in imports, and the manifest pass is never told the source is still
148
+ * imported.
149
+ */
150
+ packageName?: string | null;
151
+ /**
152
+ * Facts about the project, read once from the run's targets and passed to
153
+ * every transform call as options.
154
+ */
155
+ analyzeProject?: (targets: string[]) => object;
156
+ /** The `--css` mode when the run names none; `bestax` when omitted. */
157
+ defaultCssMode?: CssMode;
158
+ /**
159
+ * For a source with no package: the 1-based line of the first thing in an
160
+ * unparseable file (`.mdx`, `.vue`, …) that the source would have converted,
161
+ * or null. Such files are reported, never rewritten.
162
+ */
163
+ findUnsupportedReference?: (text: string) => number | null;
164
+ /**
165
+ * Which of the unparseable formats `findUnsupportedReference` scans; all
166
+ * of them when omitted.
167
+ */
168
+ unsupportedExtensions?: readonly string[];
144
169
  transform: Transform;
145
170
  transformStyles?: StylesTransform;
146
171
  updateDependencies?: DependenciesUpdate;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE1D,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sEAAsE;IACtE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CACzC;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,sEAAsE;AACtE,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAElD,uEAAuE;AACvE,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,mDAAmD;AACnD,MAAM,MAAM,SAAS,GAAG,CACtB,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,gBAAgB,KACtB,MAAM,GAAG,SAAS,CAAC;AAExB,+DAA+D;AAC/D,MAAM,MAAM,eAAe,GAAG,CAC5B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,aAAa,GAAG,SAAS,EACpC,OAAO,EAAE,gBAAgB,KACtB,MAAM,GAAG,IAAI,CAAC;AAEnB,kEAAkE;AAClE,MAAM,MAAM,kBAAkB,GAAG,CAC/B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,SAAS,EAAE,aAAa,GAAG,SAAS,EACpC,OAAO,EAAE,WAAW,KACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;AAEpC,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;IAC/C,uEAAuE;IACvE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC3D;;;OAGG;IACH,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bestax-migrate",
3
- "version": "2.3.8",
3
+ "version": "2.4.0",
4
4
  "description": "Codemods for migrating existing React apps from other Bulma libraries to @allxsmith/bestax-bulma",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  "chalk": "^6.0.0",
39
39
  "commander": "^15.0.0",
40
40
  "jscodeshift": "^17.3.0",
41
- "@allxsmith/bestax-bulma": "^5.16.7"
41
+ "@allxsmith/bestax-bulma": "^5.16.9"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@jest/globals": "^30.0.0",
@@ -67,10 +67,11 @@
67
67
  "validate:corpus": "pnpm run build && node scripts/validate-corpus.mjs",
68
68
  "validate:corpus:rbx": "pnpm run build && node scripts/validate-corpus-rbx.mjs",
69
69
  "validate:corpus:bloomer": "pnpm run build && node scripts/validate-corpus-bloomer.mjs",
70
+ "validate:corpus:bulma-classes": "pnpm run build && node scripts/validate-corpus-bulma-classes.mjs",
70
71
  "format": "prettier --write \"src/**/*.{ts,tsx}\" \"e2e/**/*.ts\"",
71
72
  "format:check": "prettier --check \"src/**/*.{ts,tsx}\" \"e2e/**/*.ts\"",
72
73
  "clean": "rimraf dist",
73
74
  "release": "npx semantic-release"
74
75
  },
75
- "readme": "# bestax-migrate\n\n[![npm version](https://img.shields.io/npm/v/bestax-migrate.svg)](https://www.npmjs.com/package/bestax-migrate)\n[![npm downloads](https://img.shields.io/npm/dm/bestax-migrate.svg)](https://www.npmjs.com/package/bestax-migrate)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Socket Badge](https://badge.socket.dev/npm/package/bestax-migrate/latest)](https://socket.dev/npm/package/bestax-migrate/overview)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/allxsmith/bestax/badge)](https://scorecard.dev/viewer/?uri=github.com/allxsmith/bestax)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14361/badge)](https://www.bestpractices.dev/projects/14361)\n[![npm provenance](https://img.shields.io/badge/npm-provenance-3fb950.svg)](https://www.npmjs.com/package/bestax-migrate#provenance)\n[![Security policy](https://img.shields.io/badge/security-policy-blue.svg)](https://github.com/allxsmith/bestax/blob/main/SECURITY.md)\n\nCodemods that migrate existing React apps to [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) — the actively maintained React component library for **Bulma v1**.\n\nCurrently supported source libraries:\n\n| Source | Status |\n| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |\n| [`react-bulma-components`](https://github.com/couds/react-bulma-components) (v4) | ✅ All 32 components mapped (a few patterns are flagged as TODOs) |\n| [`rbx`](https://github.com/dfee/rbx) (v2) | ✅ Full export surface mapped (a few patterns are flagged as TODOs) |\n| [`bloomer`](https://github.com/AlgusDark/bloomer) (0.6) | ✅ All 108 exports mapped (Font Awesome 4 icons and Bulma 0.4's `Nav` are flagged) |\n\nPass the source library as the first argument: `bestax-migrate <source> <paths…>`.\n\n## Requirements\n\n**Node.js 22 or newer.** Node 18 and 20 are both past end-of-life. On an older runtime the CLI\nexits immediately with an explicit upgrade message.\n\nThis applies only to the Node version the codemod itself runs on. It places no requirement on\nthe app being migrated — the source is read as text and is never executed.\n\n## Usage\n\n```bash\n# Preview what would change (no writes)\npnpm dlx bestax-migrate react-bulma-components src/ --dry\n# …or, for an rbx or bloomer app\npnpm dlx bestax-migrate rbx src/ --dry\npnpm dlx bestax-migrate bloomer src/ --dry\n\n# Apply the migration\npnpm dlx bestax-migrate react-bulma-components src/\n```\n\nnpm and yarn work too: `npx bestax-migrate …` / `yarn dlx bestax-migrate …`.\n\nThe codemod uses [jscodeshift](https://github.com/facebook/jscodeshift) to:\n\n- rewrite the source library's imports to `@allxsmith/bestax-bulma` (including destructuring and namespace imports)\n- rename components and compound sub-components (`Form.Textarea` → `TextArea`, `Card.Footer.Item` → `Card.FooterItem`, `Hero.Footer` → `Hero.Foot`, …) — and, for bloomer, its flat names onto bestax's compounds (`CardHeaderTitle` → `Card.Header.Title`)\n- convert props (`renderAs` → `as`, `loading` → `isLoading`, numeric spacing/text sizes → string unions, `textAlign=\"center\"` → `textAlign=\"centered\"`, …)\n- flatten responsive breakpoint objects (`mobile={{ size: 4 }}` → `sizeMobile={4}`) and bloomer's `isDisplay`/`isHidden` strings, arrays and objects (`isHidden=\"touch\"` → `visibilityTouch=\"hidden\"`)\n- restructure patterns bestax models differently (`Table.Container`, Navbar dropdowns, `Form.Help`, icon-font children → `<Icon name=…>`)\n- migrate stylesheets: CSS imports converge on `@allxsmith/bestax-bulma/bestax.css`, and SCSS files move from Bulma 0.9's `@import` + `$var !default` overrides to `@use 'bulma/sass' with (…)` plus `@use '@allxsmith/bestax-bulma/scss/extras'`\n- update `package.json`: remove the source library, add `@allxsmith/bestax-bulma`, raise a declared pre-1.0 `bulma` to `^1` (adding it only when your sources import `bulma/…` directly — otherwise it arrives transitively), and swap the dead `node-sass` for dart `sass` (no install is ever run). Migrating **rbx** is what frees an app from Bulma 0.7.5: rbx pinned it as a _direct_ dependency, so the app could not choose its own Bulma version at all. The four Bulma extensions rbx also pulled in (`bulma-badge`, `bulma-divider`, `bulma-pageloader`, `bulma-tooltip`) are **reported rather than removed** — bestax replaces all four, but a manifest entry is a deliberate declaration and your own Sass may import them. A **bloomer** app declared its own Bulma 0.6, which is what gets bumped\n\nAnything without a safe automatic conversion is left in place with a `// TODO(bestax-migrate): …` comment, and the run ends with a report of every TODO by file and line. TODOs are expected output, not errors — resolve them with the migration guide ([react-bulma-components](https://bestax.io/docs/guides/getting-started/migration/react-bulma-components), [rbx](https://bestax.io/docs/guides/getting-started/migration/rbx), [bloomer](https://bestax.io/docs/guides/getting-started/migration/bloomer)), or let the [`bestax-migrate` Agent Skill](https://bestax.io/docs/skills/intro) walk them for you:\n\n```bash\nnpx skills add https://github.com/allxsmith/bestax --skill bestax-migrate\n```\n\n## Options\n\n| Flag | Description |\n| -------------------- | -------------------------------------------------------------- |\n| `--dry`, `-d` | Report what would change without writing files |\n| `--print`, `-p` | Print transformed sources to stdout |\n| `--extensions`, `-e` | File extensions to include (default `js,jsx,ts,tsx,scss,sass`) |\n| `--css <mode>` | Stylesheet target: `bestax` (default), `bulma`, or `keep` |\n| `--no-deps` | Skip updating package.json dependencies |\n\n## After the codemod\n\n1. Run your package manager's install (the codemod rewrote `package.json` but never installs)\n2. Search for `TODO(bestax-migrate)` and resolve each comment\n3. Typecheck/build and review the rendered app\n\nFull walkthrough: [react-bulma-components migration guide](https://bestax.io/docs/guides/getting-started/migration/react-bulma-components) · [rbx migration guide](https://bestax.io/docs/guides/getting-started/migration/rbx) · [bloomer migration guide](https://bestax.io/docs/guides/getting-started/migration/bloomer).\n\n## Hardened by default\n\nA codemod rewrites your source in place, so how it is built and published matters:\n\n- **Signed provenance** — every release carries a sigstore attestation linking the tarball to the exact commit and CI run that built it. Check the **Provenance** section on the [npm page](https://www.npmjs.com/package/bestax-migrate#provenance), or run `npm audit signatures`.\n- **npm OIDC trusted publishing** — short-lived, per-run credentials; no long-lived `NPM_TOKEN` exists to be stolen. Release commits and tags are GPG-signed.\n- **Socket.dev scans every PR** for malware, install scripts, obfuscated code, and privilege escalation before it can reach `main`.\n- **The libraries this tool migrates away from are never installed here** — source fixtures are read as text only, so no unmaintained third-party package enters the dependency tree.\n- **Dependencies are a deliberate act** — install scripts are blocked unless individually allow-listed, freshly published versions are refused for 3 days, and CI installs only what the reviewed lockfile resolves.\n- **Every GitHub Action is pinned to a full commit SHA**, and CodeQL, Dependency Review, and Dependabot run continuously alongside a high-severity `pnpm audit` gate.\n- **Layered AI review before merge** — [CodeRabbit](https://coderabbit.ai) plus an independent adversarial Claude review (a different model from the one writing AI-authored changes), on top of required green CI, an approving review, and a human merge.\n\nFull detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)\n\n## Telemetry\n\n`bestax-migrate` can send one **anonymous** usage event after a successful\nrun — only if you opt in when asked (once, on an interactive terminal). The\nevent is just the run's shape — source library, CSS mode, the dry/deps flags, a\ncapped changed-file count, and per-rule TODO counts — plus the CLI version,\nNode major version, and OS platform name. Never file paths, file contents,\ncode, names, IPs, or machine IDs, and no identifier exists that could link two\nevents together. Opt out any time with `--no-telemetry`,\n`BESTAX_TELEMETRY=0`, or `DO_NOT_TRACK=1`.\n\nFull disclosure of every field and control: [Telemetry guide](https://bestax.io/docs/guides/telemetry)\n\n## License\n\nSource code licensed MIT\n"
76
+ "readme": "# bestax-migrate\n\n[![npm version](https://img.shields.io/npm/v/bestax-migrate.svg)](https://www.npmjs.com/package/bestax-migrate)\n[![npm downloads](https://img.shields.io/npm/dm/bestax-migrate.svg)](https://www.npmjs.com/package/bestax-migrate)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Socket Badge](https://badge.socket.dev/npm/package/bestax-migrate/latest)](https://socket.dev/npm/package/bestax-migrate/overview)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/allxsmith/bestax/badge)](https://scorecard.dev/viewer/?uri=github.com/allxsmith/bestax)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14361/badge)](https://www.bestpractices.dev/projects/14361)\n[![npm provenance](https://img.shields.io/badge/npm-provenance-3fb950.svg)](https://www.npmjs.com/package/bestax-migrate#provenance)\n[![Security policy](https://img.shields.io/badge/security-policy-blue.svg)](https://github.com/allxsmith/bestax/blob/main/SECURITY.md)\n\nCodemods that migrate existing React apps to [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) — the actively maintained React component library for **Bulma v1**.\n\nCurrently supported sources:\n\n| Source | Status |\n| -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |\n| [`react-bulma-components`](https://github.com/couds/react-bulma-components) (v4) | ✅ All 32 components mapped (a few patterns are flagged as TODOs) |\n| [`rbx`](https://github.com/dfee/rbx) (v2) | ✅ Full export surface mapped (a few patterns are flagged as TODOs) |\n| [`bloomer`](https://github.com/AlgusDark/bloomer) (0.6) | ✅ All 108 exports mapped (Font Awesome 4 icons and Bulma 0.4's `Nav` are flagged) |\n| `bulma-classes`: Bulma's classes on plain JSX, no library | ✅ Converts an element only when bestax renders the same markup; the rest is flagged |\n\nPass the source as the first argument: `bestax-migrate <source> <paths…>`.\n\n## Requirements\n\n**Node.js 22 or newer.** Node 18 and 20 are both past end-of-life. On an older runtime the CLI\nexits immediately with an explicit upgrade message.\n\nThis applies only to the Node version the codemod itself runs on. It places no requirement on\nthe app being migrated — the source is read as text and is never executed.\n\n## Usage\n\n```bash\n# Preview what would change (no writes)\npnpm dlx bestax-migrate react-bulma-components src/ --dry\n# …or, for an rbx or bloomer app\npnpm dlx bestax-migrate rbx src/ --dry\npnpm dlx bestax-migrate bloomer src/ --dry\n# …or, for an app that writes Bulma's classes on plain JSX\npnpm dlx bestax-migrate bulma-classes src/ --dry\n\n# Apply the migration\npnpm dlx bestax-migrate react-bulma-components src/\n```\n\nnpm and yarn work too: `npx bestax-migrate …` / `yarn dlx bestax-migrate …`.\n\nThe codemod uses [jscodeshift](https://github.com/facebook/jscodeshift) to:\n\n- rewrite the source library's imports to `@allxsmith/bestax-bulma` (including destructuring and namespace imports)\n- rename components and compound sub-components (`Form.Textarea` → `TextArea`, `Card.Footer.Item` → `Card.FooterItem`, `Hero.Footer` → `Hero.Foot`, …) — and, for bloomer, its flat names onto bestax's compounds (`CardHeaderTitle` → `Card.Header.Title`)\n- convert props (`renderAs` → `as`, `loading` → `isLoading`, numeric spacing/text sizes → string unions, `textAlign=\"center\"` → `textAlign=\"centered\"`, …)\n- flatten responsive breakpoint objects (`mobile={{ size: 4 }}` → `sizeMobile={4}`) and bloomer's `isDisplay`/`isHidden` strings, arrays and objects (`isHidden=\"touch\"` → `visibilityTouch=\"hidden\"`)\n- for `bulma-classes`, turn raw Bulma markup into components and props (`<a className=\"button is-primary\">` → `<Button as=\"a\" color=\"primary\">`, `<p className=\"has-text-centered mt-4\">` → `<Paragraph textAlign=\"centered\" mt=\"4\">`), converting an element only when bestax renders exactly the same markup\n- restructure patterns bestax models differently (`Table.Container`, Navbar dropdowns, `Form.Help`, icon-font children → `<Icon name=…>`)\n- migrate stylesheets: CSS imports converge on `@allxsmith/bestax-bulma/bestax.css`, and SCSS files move from Bulma 0.9's `@import` + `$var !default` overrides to `@use 'bulma/sass' with (…)` plus `@use '@allxsmith/bestax-bulma/scss/extras'`\n- update `package.json`: remove the source library, add `@allxsmith/bestax-bulma`, raise a declared pre-1.0 `bulma` to `^1` (adding it only when your sources import `bulma/…` directly — otherwise it arrives transitively), and swap the dead `node-sass` for dart `sass` (no install is ever run). Migrating **rbx** is what frees an app from Bulma 0.7.5: rbx pinned it as a _direct_ dependency, so the app could not choose its own Bulma version at all. The four Bulma extensions rbx also pulled in (`bulma-badge`, `bulma-divider`, `bulma-pageloader`, `bulma-tooltip`) are **reported rather than removed** — bestax replaces all four, but a manifest entry is a deliberate declaration and your own Sass may import them. A **bloomer** app declared its own Bulma 0.6, which is what gets bumped. For **bulma-classes** there is no library to remove, and by default (`--css keep`) the app's Bulma version, Sass and stylesheet imports all stay: its own Bulma already styles every class a converted element renders. `--css bestax` or `--css bulma` moves it to Bulma v1 like the other sources\n\nAnything without a safe automatic conversion is left in place with a `// TODO(bestax-migrate): …` comment, and the run ends with a report of every TODO by file and line. TODOs are expected output, not errors — resolve them with the migration guide ([react-bulma-components](https://bestax.io/docs/guides/getting-started/migration/react-bulma-components), [rbx](https://bestax.io/docs/guides/getting-started/migration/rbx), [bloomer](https://bestax.io/docs/guides/getting-started/migration/bloomer), [Bulma classes](https://bestax.io/docs/guides/getting-started/migration/bulma-classes)), or let the [`bestax-migrate` Agent Skill](https://bestax.io/docs/skills/intro) walk them for you:\n\n```bash\nnpx skills add https://github.com/allxsmith/bestax --skill bestax-migrate\n```\n\n## Options\n\n| Flag | Description |\n| -------------------- | ---------------------------------------------------------------------------------------------- |\n| `--dry`, `-d` | Report what would change without writing files |\n| `--print`, `-p` | Print transformed sources to stdout |\n| `--extensions`, `-e` | File extensions to include (default `js,jsx,ts,tsx,scss,sass`) |\n| `--css <mode>` | Stylesheet target: `bestax`, `bulma`, or `keep` (default `bestax`; `keep` for `bulma-classes`) |\n| `--no-deps` | Skip updating package.json dependencies |\n\n## After the codemod\n\n1. Run your package manager's install (the codemod rewrote `package.json` but never installs)\n2. Search for `TODO(bestax-migrate)` and resolve each comment\n3. Typecheck/build and review the rendered app\n\nFull walkthrough: [react-bulma-components migration guide](https://bestax.io/docs/guides/getting-started/migration/react-bulma-components) · [rbx migration guide](https://bestax.io/docs/guides/getting-started/migration/rbx) · [bloomer migration guide](https://bestax.io/docs/guides/getting-started/migration/bloomer) · [Bulma classes migration guide](https://bestax.io/docs/guides/getting-started/migration/bulma-classes).\n\n## Hardened by default\n\nA codemod rewrites your source in place, so how it is built and published matters:\n\n- **Signed provenance** — every release carries a sigstore attestation linking the tarball to the exact commit and CI run that built it. Check the **Provenance** section on the [npm page](https://www.npmjs.com/package/bestax-migrate#provenance), or run `npm audit signatures`.\n- **npm OIDC trusted publishing** — short-lived, per-run credentials; no long-lived `NPM_TOKEN` exists to be stolen. Release commits and tags are GPG-signed.\n- **Socket.dev scans every PR** for malware, install scripts, obfuscated code, and privilege escalation before it can reach `main`.\n- **The libraries this tool migrates away from are never installed here** — source fixtures are read as text only, so no unmaintained third-party package enters the dependency tree.\n- **Dependencies are a deliberate act** — install scripts are blocked unless individually allow-listed, freshly published versions are refused for 3 days, and CI installs only what the reviewed lockfile resolves.\n- **Every GitHub Action is pinned to a full commit SHA**, and CodeQL, Dependency Review, and Dependabot run continuously alongside a high-severity `pnpm audit` gate.\n- **Layered AI review before merge** — [CodeRabbit](https://coderabbit.ai) plus an independent adversarial Claude review (a different model from the one writing AI-authored changes), on top of required green CI, an approving review, and a human merge.\n\nFull detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)\n\n## Telemetry\n\n`bestax-migrate` can send one **anonymous** usage event after a successful\nrun — only if you opt in when asked (once, on an interactive terminal). The\nevent is just the run's shape — source library, CSS mode, the dry/deps flags, a\ncapped changed-file count, and per-rule TODO counts — plus the CLI version,\nNode major version, and OS platform name. Never file paths, file contents,\ncode, names, IPs, or machine IDs, and no identifier exists that could link two\nevents together. Opt out any time with `--no-telemetry`,\n`BESTAX_TELEMETRY=0`, or `DO_NOT_TRACK=1`.\n\nFull disclosure of every field and control: [Telemetry guide](https://bestax.io/docs/guides/telemetry)\n\n## License\n\nSource code licensed MIT\n"
76
77
  }