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
@@ -13,11 +13,8 @@
13
13
  * see the note on that below.
14
14
  * says so by name.
15
15
  */
16
- import { isPreV1, isRecognisedRange } from '../_shared/semver-range.js';
17
- const BESTAX_RANGE = '^5';
18
- const BULMA_RANGE = '^1.0.4';
19
- // Bulma v1's sass tree uses `color.channel(…)` — needs dart-sass ≥ 1.79.
20
- const SASS_RANGE = '^1.79.0';
16
+ import { BULMA_RANGE, DEP_SECTIONS, addBestax, moveBulmaToV1, openManifest, replaceNodeSass, reportPeerRanges, } from '../_shared/deps-common.js';
17
+ import { isRecognisedRange } from '../_shared/semver-range.js';
21
18
  /**
22
19
  * The Bulma extensions rbx depends on directly. Bulma v1 and bestax cover all
23
20
  * four: badge and tooltip became bestax components, the page loader became
@@ -29,23 +26,16 @@ const RBX_STYLE_DEPS = [
29
26
  'bulma-pageloader',
30
27
  'bulma-tooltip',
31
28
  ];
32
- const DEP_SECTIONS = ['dependencies', 'devDependencies'];
33
29
  export const updateDependencies = (filePath, pkg, collector, options) => {
34
- const changes = [];
35
- const next = pkg;
36
- const section = (name) => (next[name] ?? undefined);
37
- const note = (message) => {
38
- changes.push(message);
39
- collector?.add({ file: filePath, line: null, rule: 'deps', message });
40
- };
30
+ const manifest = openManifest(filePath, pkg, collector);
41
31
  // rbx goes away entirely.
42
32
  const removed = [];
43
33
  for (const name of DEP_SECTIONS) {
44
- const deps = section(name);
34
+ const deps = manifest.section(name);
45
35
  if (deps && 'rbx' in deps) {
46
36
  delete deps.rbx;
47
37
  removed.push('rbx');
48
- note(`removed rbx from ${name}`);
38
+ manifest.note(`removed rbx from ${name}`);
49
39
  }
50
40
  }
51
41
  // The four Bulma extensions are REPORTED, not removed.
@@ -60,7 +50,7 @@ export const updateDependencies = (filePath, pkg, collector, options) => {
60
50
  const extensions = [];
61
51
  if (removed.includes('rbx')) {
62
52
  for (const name of DEP_SECTIONS) {
63
- const deps = section(name);
53
+ const deps = manifest.section(name);
64
54
  if (!deps)
65
55
  continue;
66
56
  for (const extension of RBX_STYLE_DEPS) {
@@ -70,56 +60,19 @@ export const updateDependencies = (filePath, pkg, collector, options) => {
70
60
  }
71
61
  }
72
62
  if (extensions.length > 0) {
73
- collector?.add({
74
- file: filePath,
75
- line: null,
76
- rule: 'deps',
77
- message: `${extensions.join(', ')} ${extensions.length === 1 ? 'is a Bulma extension' : 'are Bulma extensions'} rbx depended on, and bestax ships ${extensions.length === 1 ? 'its' : 'their'} equivalent${extensions.length === 1 ? '' : 's'} (Badge, Divider, Loading, Tooltip). Declared in this manifest, so removing ${extensions.length === 1 ? 'it' : 'them'} is your call — drop ${extensions.length === 1 ? 'it' : 'them'} unless your own Sass imports ${extensions.length === 1 ? 'it' : 'them'} directly`,
78
- });
79
- }
80
- // @allxsmith/bestax-bulma comes in (runtime dependency).
81
- const dependencies = (next.dependencies ??= {});
82
- if (!dependencies['@allxsmith/bestax-bulma'] &&
83
- !section('devDependencies')?.['@allxsmith/bestax-bulma']) {
84
- dependencies['@allxsmith/bestax-bulma'] = BESTAX_RANGE;
85
- note(`added @allxsmith/bestax-bulma ${BESTAX_RANGE} to dependencies`);
86
- }
87
- // Bulma: rbx pinned 0.7.5 as a direct dependency, so this almost always
88
- // fires. Add it back only when sources still reference bulma/… directly —
89
- // otherwise it arrives transitively via bestax-bulma.
90
- let bulmaDeclared = false;
91
- let declaredBulma = '';
92
- let bulmaBumped = false;
93
- let bulmaAdded = false;
94
- for (const name of DEP_SECTIONS) {
95
- const deps = section(name);
96
- if (deps?.bulma) {
97
- bulmaDeclared = true;
98
- declaredBulma = deps.bulma;
99
- if (isPreV1(deps.bulma)) {
100
- deps.bulma = BULMA_RANGE;
101
- bulmaBumped = true;
102
- note(`bumped bulma to ${BULMA_RANGE} in ${name} (was pre-1.0)`);
103
- }
104
- }
105
- }
106
- if (!bulmaDeclared && options.bulmaReferenced) {
107
- dependencies.bulma = BULMA_RANGE;
108
- bulmaAdded = true;
109
- note(`added bulma ${BULMA_RANGE} to dependencies (sources import bulma/… directly)`);
63
+ manifest.report('deps', `${extensions.join(', ')} ${extensions.length === 1 ? 'is a Bulma extension' : 'are Bulma extensions'} rbx depended on, and bestax ships ${extensions.length === 1 ? 'its' : 'their'} equivalent${extensions.length === 1 ? '' : 's'} (Badge, Divider, Loading, Tooltip). Declared in this manifest, so removing ${extensions.length === 1 ? 'it' : 'them'} is your call — drop ${extensions.length === 1 ? 'it' : 'them'} unless your own Sass imports ${extensions.length === 1 ? 'it' : 'them'} directly`);
110
64
  }
65
+ addBestax(manifest);
66
+ // rbx pinned Bulma 0.7.5 as a direct dependency, so the bump almost always
67
+ // fires.
68
+ const bulma = moveBulmaToV1(manifest, options.bulmaReferenced);
111
69
  // The transform deliberately keeps a trimmed rbx import for components
112
70
  // with no bestax equivalent (Tile, Generic, List, …) so a partially
113
71
  // migrated app still runs. Removing the package from the manifest strands
114
72
  // exactly those imports once the user runs the install the report asks for,
115
73
  // so say so rather than letting them find out at build time.
116
74
  if (removed.includes('rbx') && options.sourceStillImported) {
117
- collector?.add({
118
- file: filePath,
119
- line: null,
120
- rule: 'deps',
121
- message: 'rbx 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 rbx until you have',
122
- });
75
+ manifest.report('deps', 'rbx 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 rbx until you have');
123
76
  }
124
77
  // The headline result: rbx pinned Bulma 0.7.5 as a DIRECT dependency, so
125
78
  // removing rbx is what frees the app to choose its own Bulma version.
@@ -130,63 +83,22 @@ export const updateDependencies = (filePath, pkg, collector, options) => {
130
83
  // arrives transitively via bestax-bulma. Saying "bumped bulma" there
131
84
  // described a pin the user would not find in their manifest.
132
85
  if (removed.includes('rbx')) {
133
- collector?.add({
134
- file: filePath,
135
- line: null,
136
- rule: 'deps',
137
- message: `removed rbx ${bulmaBumped
138
- ? `and bumped bulma to ${BULMA_RANGE}`
139
- : bulmaAdded
140
- ? `and added bulma ${BULMA_RANGE}`
141
- : bulmaDeclared
142
- ? isRecognisedRange(declaredBulma)
143
- ? 'and left your declared bulma range alone (already v1)'
144
- : `and left your declared bulma specifier alone (${JSON.stringify(declaredBulma)} is not a version range this tool can read; make sure it resolves to Bulma 1.x)`
145
- : '— bulma now arrives transitively via @allxsmith/bestax-bulma'} — rbx pinned Bulma 0.7.5 as a direct dependency, so the app can now choose its own Bulma version${extensions.length > 0 ? `; ${extensions.length} Bulma extension(s) are reported above for you to remove` : ''}`,
146
- });
147
- }
148
- // bestax-bulma requires React 18/19; rbx peer-depends on ^16.8.6, and React
149
- // 19 removed the `defaultProps` its forwardRefAs base is built on — so this
150
- // is the other half of why an rbx app is stuck. Report only: a React major
151
- // upgrade is the app's own migration step.
152
- for (const name of DEP_SECTIONS) {
153
- const range = section(name)?.react;
154
- if (range && /^[~^]?(?:[0-9]|1[0-7])(?:[.x]|$)/.test(range.trim())) {
155
- collector?.add({
156
- file: filePath,
157
- line: null,
158
- rule: 'peer-deps',
159
- message: `react ${range} predates bestax-bulma's peer range (^18 || ^19) — upgrade react and react-dom to 18 or 19 before installing`,
160
- });
161
- }
162
- }
163
- // Font Awesome older than 6 conflicts with bestax-bulma's optional peer
164
- // range and makes `npm install` fail with ERESOLVE. Report only — icon
165
- // names change across FA majors, so upgrading is the app's decision.
166
- for (const name of DEP_SECTIONS) {
167
- const range = section(name)?.['@fortawesome/fontawesome-free'];
168
- if (range && /^[~^]?[0-5](?:[.x]|$)/.test(range.trim())) {
169
- collector?.add({
170
- file: filePath,
171
- line: null,
172
- rule: 'peer-deps',
173
- 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\``,
174
- });
175
- }
176
- }
177
- // node-sass is dead; dart-sass replaces it in the same section. rbx's own
178
- // customisation guide told people to install node-sass, so this is common.
179
- for (const name of DEP_SECTIONS) {
180
- const deps = section(name);
181
- if (deps && 'node-sass' in deps) {
182
- delete deps['node-sass'];
183
- note(`removed node-sass from ${name}`);
184
- const sassDeclared = DEP_SECTIONS.some(s => section(s)?.sass);
185
- if (!sassDeclared) {
186
- deps.sass = SASS_RANGE;
187
- note(`added sass ${SASS_RANGE} to ${name} (replaces node-sass)`);
188
- }
189
- }
86
+ manifest.report('deps', `removed rbx ${bulma.bumped
87
+ ? `and bumped bulma to ${BULMA_RANGE}`
88
+ : bulma.added
89
+ ? `and added bulma ${BULMA_RANGE}`
90
+ : bulma.declared !== null
91
+ ? isRecognisedRange(bulma.declared)
92
+ ? 'and left your declared bulma range alone (already v1)'
93
+ : `and left your declared bulma specifier alone (${JSON.stringify(bulma.declared)} is not a version range this tool can read; make sure it resolves to Bulma 1.x)`
94
+ : '— bulma now arrives transitively via @allxsmith/bestax-bulma'} — rbx pinned Bulma 0.7.5 as a direct dependency, so the app can now choose its own Bulma version${extensions.length > 0 ? `; ${extensions.length} Bulma extension(s) are reported above for you to remove` : ''}`);
190
95
  }
191
- return changes.length > 0 ? next : null;
96
+ // rbx peer-depends on React ^16.8.6, and React 19 removed the
97
+ // `defaultProps` its forwardRefAs base is built on — so an old React is the
98
+ // other half of why an rbx app is stuck.
99
+ reportPeerRanges(manifest);
100
+ // rbx's own customisation guide told people to install node-sass, so this
101
+ // is common.
102
+ replaceNodeSass(manifest);
103
+ return manifest.result();
192
104
  };
@@ -1 +1 @@
1
- {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AA8BnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAoCtD,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAOrD,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQvD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAShE,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAktBpD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CA2FhD,CAAC;AAEF,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAM3E"}
1
+ {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AA8BnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CA6CtD,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAOrD,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQvD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAShE,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAktBpD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CA2FhD,CAAC;AAEF,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAM3E"}
@@ -78,6 +78,14 @@ export const UNIVERSAL_PROPS = {
78
78
  relative: {}, // boolean on both sides
79
79
  // Per-component maps override this with AS_OK where bestax has an `as`.
80
80
  as: AS_TODO,
81
+ // Same shape: the per-component maps override this with `rename: 'ref'` on
82
+ // the targets that forward one, and the `navbar-item` special handles the
83
+ // two it chooses between. Whatever is left reaches here, so no `innerRef`
84
+ // migrates in silence onto a target that cannot take it — the RBC sibling
85
+ // `domRef` has been flagged universally all along.
86
+ innerRef: {
87
+ todo: `\`innerRef\` has no counterpart; rename it to \`ref\` if this target forwards one, otherwise move it onto a wrapping element you control (${DOCS}/api)`,
88
+ },
81
89
  };
82
90
  /** rbx badge helper props → bestax `<Badge>` props. */
83
91
  export const BADGE_PROPS = {
@@ -121,7 +129,7 @@ export const MAPPING = {
121
129
  },
122
130
  forwardRefAs: {
123
131
  status: 'todo',
124
- todo: '`forwardRefAs` is rbx-internal and has no counterpart. bestax forwards a ref from the form controls and from `Button`, `LinkButton`, `Modal`, `Dropdown`, `Navbar` (plus `Navbar.Burger` and `Navbar.Link`), `Dialog`, `Sidebar`, `Toast` and `Carousel` — pass `ref` directly on those. Mind the `Navbar.Dropdown` collision: your rbx `Navbar.Dropdown` is the menu and becomes `Navbar.DropdownMenu`, which forwards no ref, while bestax `Navbar.Dropdown` — the container your `<Navbar.Item dropdown>` becomes — does. The rest, `Box` included, are plain function components that forward no ref; put the ref on a wrapping element you control',
132
+ todo: '`forwardRefAs` is rbx-internal and has no counterpart. bestax forwards a ref from 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` — pass `ref` directly on those, though not on the `Field`, `Field.Label`, `Field.Body`, `Checkboxes` and `Radios` wrappers, which forward none. Mind the `Navbar.Dropdown` collision: your rbx `Navbar.Dropdown` is the menu and becomes `Navbar.DropdownMenu`, which forwards no ref, while bestax `Navbar.Dropdown` — the container your `<Navbar.Item dropdown>` becomes — does. The rest, `Box` included, 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 wrapping element you control',
125
133
  },
126
134
  // ---- elements -----------------------------------------------------------
127
135
  Block: { status: 'mapped', target: 'Block' },
@@ -1 +1 @@
1
- {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAO3C,OAAO,EAYL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAUL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAs2B9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf"}
1
+ {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAO3C,OAAO,EAYL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAUL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAq3B9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf"}
@@ -17,7 +17,7 @@ import { BADGE_PROPS, RESPONSIVE_BREAKPOINTS, TOOLTIP_PROPS, UNIVERSAL_PROPS, }
17
17
  import { addAttr, addTodo, buildJsxName, attributesOf, findAttr, jsxNameParts, literalValueOf, makeAttr, plainElement, removeAttr, resolveBooleanish, } from '../_shared/jsx-utils.js';
18
18
  import { alignTarget, applyIconProps, makeStripModifierProps, makeStructuralHelpers, mergeClassName, modifierClass, parseIconClasses, restrictAsToTargets, } from '../_shared/specials-utils.js';
19
19
  /* eslint-disable @typescript-eslint/no-explicit-any */
20
- const stripModifierProps = makeStripModifierProps(UNIVERSAL_PROPS, RESPONSIVE_BREAKPOINTS);
20
+ const stripModifierProps = makeStripModifierProps(UNIVERSAL_PROPS, RESPONSIVE_BREAKPOINTS, 'innerRef');
21
21
  /**
22
22
  * Also strips the universal `responsive` object: responsive.ts would have
23
23
  * consumed it, but a `replaced: true` special skips that pass too, and it is
@@ -513,17 +513,27 @@ const SPECIALS = {
513
513
  // bestax's `Navbar.Dropdown` forwards a ref, but it is the one such target
514
514
  // the `innerRef: { rename: 'ref' }` entries in mapping.ts cannot reach:
515
515
  // that table is keyed on the rbx name (`Navbar.Item`), and only this
516
- // handler knows which of the two targets was picked. A plain
517
- // `Navbar.Item` is still a function component, so the rename is
518
- // conditional — there, `innerRef` is left alone.
519
- const renamedInnerRef = [];
520
- if (target === 'Navbar.Dropdown') {
521
- const innerRefAttr = findAttr(element, 'innerRef');
522
- if (innerRefAttr) {
516
+ // handler knows which of the two targets was picked. The condition is
517
+ // about that, not about ref support: a plain `Navbar.Item` forwards one
518
+ // too since #661, so leaving `innerRef` alone there is a rename this
519
+ // handler declines rather than one the target cannot take. Performing it
520
+ // changes emitted output on code that migrates today, so it stays #734 —
521
+ // but declining silently is not an option either. `innerRef` is in no
522
+ // rbx prop table, so nothing downstream flags it and it used to reach the
523
+ // DOM as an unknown attribute with the report saying nothing, which is
524
+ // the silent skip bestax-migrate/CLAUDE.md rules out. It is flagged here
525
+ // instead, and the rename stays the user's to make.
526
+ const handledInnerRef = [];
527
+ const innerRefAttr = findAttr(element, 'innerRef');
528
+ if (innerRefAttr) {
529
+ handledInnerRef.push('innerRef');
530
+ if (target === 'Navbar.Dropdown') {
523
531
  innerRefAttr.name = ctx.j.jsxIdentifier('ref');
524
- renamedInnerRef.push('innerRef');
525
532
  ctx.dirty = true;
526
533
  }
534
+ else {
535
+ addTodo(ctx, path, 'prop:innerRef', 'bestax `Navbar.Item` forwards a ref; rename `innerRef` to `ref` by hand');
536
+ }
527
537
  }
528
538
  restrictAsToTargets(ctx, path, element, target ?? 'Navbar.Item', [
529
539
  'Navbar.Item',
@@ -538,7 +548,7 @@ const SPECIALS = {
538
548
  'expanded',
539
549
  'hoverable',
540
550
  'managed',
541
- ...renamedInnerRef,
551
+ ...handledInnerRef,
542
552
  ],
543
553
  };
544
554
  },
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA4DvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CA0oCpB"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAmDvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CA47BpB"}
@@ -20,32 +20,16 @@
20
20
  * not a rename.
21
21
  */
22
22
  import { BADGE_PROPS, MAPPING, TOOLTIP_PROPS, UNIVERSAL_PROPS, resolveMapping, } from './mapping.js';
23
- import { addTodo, attributesOf, findAttr, jsxNameParts, literalValueOf, removeAttr, renameElement, } from '../_shared/jsx-utils.js';
23
+ import { addTodo, attributesOf, findAttr, forgetJsxParens, jsxNameParts, literalValueOf, removeAttr, renameElement, reprintDirectives, } from '../_shared/jsx-utils.js';
24
24
  import { applyPropAction, applyUniversalProps } from '../_shared/props.js';
25
25
  import { enforcePolymorphicProps } from '../_shared/polymorphic.js';
26
+ import { bestaxValueLocals, buildBestaxImport, carryPrunedComments, placeBestaxImport, seedBestaxImport, } from '../_shared/bestax-import.js';
27
+ import { rewriteStylesheetImports } from '../_shared/css-imports.js';
26
28
  import { collectBoundNames, makeAliasRegistry, makeReserve, nameOf, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
27
29
  import { flattenResponsiveProps, RESPONSIVE_KINDS } from './responsive.js';
28
30
  import { runSpecial } from './specials.js';
29
31
  /* eslint-disable @typescript-eslint/no-explicit-any */
30
32
  const RBX = 'rbx';
31
- const BESTAX = '@allxsmith/bestax-bulma';
32
- const BULMA_CSS = 'bulma/css/bulma.min.css';
33
- const BESTAX_CSS = '@allxsmith/bestax-bulma/bestax.css';
34
- const EXTRAS_CSS = '@allxsmith/bestax-bulma/extras.css';
35
- const BULMA_CSS_SPECIFIERS = new Set([
36
- 'bulma/css/bulma.css',
37
- 'bulma/css/bulma.min.css',
38
- ]);
39
- const BESTAX_CSS_SPECIFIERS = new Set([
40
- BESTAX_CSS,
41
- '@allxsmith/bestax-bulma/bestax.min.css',
42
- '@allxsmith/bestax-bulma/dist/bestax.css',
43
- '@allxsmith/bestax-bulma/dist/bestax.min.css',
44
- ]);
45
- const EXTRAS_CSS_SPECIFIERS = new Set([
46
- EXTRAS_CSS,
47
- '@allxsmith/bestax-bulma/dist/extras.css',
48
- ]);
49
33
  /**
50
34
  * The four Bulma extensions rbx pinned. An app that imported their CSS
51
35
  * directly no longer needs to: bestax ships Badge/Tooltip/Loading/Divider.
@@ -125,35 +109,17 @@ export default function transform(fileInfo, api, options = {}) {
125
109
  addTodo(ctx, p, 'imports', `\`export * from '${RBX}'\` was not migrated; re-export the bestax components you need by hand`);
126
110
  });
127
111
  // ---- 1a. Stylesheet imports (mode-driven) -----------------------------
128
- // `bestax` (default): everything converges on the recommended combined
129
- // bundle. `bulma`: plain Bulma v1 CSS plus the separate extras file.
130
- // `keep`: only the dead rbx CSS import is retargeted.
131
- const cssMode = options.cssMode ?? 'bestax';
132
- let sawBestaxCss = root
133
- .find(j.ImportDeclaration)
134
- .paths()
135
- .some(p => BESTAX_CSS_SPECIFIERS.has(String(p.node.source.value)));
136
- // Whether some import in this file will become bestax.css, regardless of
137
- // where it sits relative to an existing extras import.
138
- const willAdoptBestaxCss = cssMode === 'bestax' &&
139
- root
140
- .find(j.ImportDeclaration)
141
- .paths()
142
- .some(p => {
143
- const v = String(p.node.source.value);
144
- return ((v.startsWith(`${RBX}/`) && v.endsWith('.css')) ||
145
- BULMA_CSS_SPECIFIERS.has(v));
146
- });
147
- root.find(j.ImportDeclaration).forEach(path => {
148
- const source = String(path.node.source.value);
149
- const isRbxCss = source.startsWith(`${RBX}/`) && source.endsWith('.css');
150
- const isBulmaCss = BULMA_CSS_SPECIFIERS.has(source);
151
- const isExtrasCss = EXTRAS_CSS_SPECIFIERS.has(source);
112
+ // `keep` retargets only the dead rbx CSS import, which carries Bulma 0.7.5.
113
+ rewriteStylesheetImports(ctx, root, options.cssMode ?? 'bestax', {
114
+ sourceCss: {
115
+ is: specifier => specifier.startsWith(`${RBX}/`) && specifier.endsWith('.css'),
116
+ label: "rbx's CSS import",
117
+ },
152
118
  // The four extensions rbx pinned; bestax covers all of them.
153
- const isExtensionCss = RBX_EXTENSION_CSS.test(source) && source.endsWith('.css');
154
- if (!isRbxCss && !isBulmaCss && !isExtrasCss && !isExtensionCss)
155
- return;
156
- if (isExtensionCss) {
119
+ handleOwn: (path, source) => {
120
+ if (!RBX_EXTENSION_CSS.test(source) || !source.endsWith('.css')) {
121
+ return false;
122
+ }
157
123
  // Kept, with a TODO, rather than dropped. The rbx JSX that needed this
158
124
  // stylesheet becomes bestax components that carry their own styles, so
159
125
  // for THAT markup it is dead -- but an app can also use the extension's
@@ -164,50 +130,8 @@ export default function transform(fileInfo, api, options = {}) {
164
130
  // manifest pass, which reports the same extensions rather than removing
165
131
  // them for exactly this reason.
166
132
  addTodo(ctx, path, 'css', `kept \`${source}\`: bestax ships Badge, Tooltip, Loading and Divider with their own styles, so this is only still needed by markup outside rbx that uses the extension's classes directly — drop the import once nothing does`);
167
- return;
168
- }
169
- if (cssMode === 'bestax') {
170
- if (isRbxCss || isBulmaCss) {
171
- if (sawBestaxCss) {
172
- path.prune(); // bestax.css already imported elsewhere in this file
173
- }
174
- else {
175
- path.node.source = j.stringLiteral(BESTAX_CSS);
176
- sawBestaxCss = true;
177
- }
178
- ctx.dirty = true;
179
- }
180
- else if (isExtrasCss && (sawBestaxCss || willAdoptBestaxCss)) {
181
- // bestax.css already contains the extras. `willAdoptBestaxCss` covers
182
- // the case where the extras import comes FIRST in the file and the
183
- // bulma/rbx import that becomes bestax.css has not been visited yet —
184
- // previously the extras survived alongside it and double-loaded.
185
- path.prune();
186
- ctx.dirty = true;
187
- }
188
- }
189
- else if (cssMode === 'bulma') {
190
- if (isRbxCss) {
191
- path.node.source = j.stringLiteral(BULMA_CSS);
192
- ctx.dirty = true;
193
- }
194
- if ((isRbxCss || isBulmaCss) && !sawBestaxCss) {
195
- const hasExtras = root
196
- .find(j.ImportDeclaration)
197
- .paths()
198
- .some(p => EXTRAS_CSS_SPECIFIERS.has(String(p.node.source.value)));
199
- if (!hasExtras) {
200
- // Themed Radio/Checkbox need the bestax extras next to plain Bulma.
201
- path.insertAfter(j.importDeclaration([], j.stringLiteral(EXTRAS_CSS)));
202
- ctx.dirty = true;
203
- }
204
- }
205
- }
206
- else if (isRbxCss) {
207
- // keep: minimal fix — rbx's CSS carries Bulma 0.7.5.
208
- path.node.source = j.stringLiteral(BULMA_CSS);
209
- addTodo(ctx, path, 'css', `replaced rbx's CSS import with '${BULMA_CSS}'; install bulma@^1 (see https://bestax.io/docs/guides/getting-started/installation)`);
210
- }
133
+ return true;
134
+ },
211
135
  });
212
136
  if (imports.size === 0 && !ctx.dirty) {
213
137
  return undefined;
@@ -392,45 +316,7 @@ export default function transform(fileInfo, api, options = {}) {
392
316
  bound.add(head);
393
317
  });
394
318
  ctx.reserve = makeReserve(ctx, bound);
395
- // Names the passes actually asked for. `ctx.needed` is also seeded from an
396
- // existing bestax import's specifiers below (so JSX reuses their locals),
397
- // and that seeding alone must not turn a type-only specifier into a value
398
- // import nobody needed.
399
- const requested = new Set();
400
- const baseReserve = ctx.reserve;
401
- ctx.reserve = root => {
402
- requested.add(root);
403
- return baseReserve(root);
404
- };
405
- // Merge with an existing bestax import: reuse its locals verbatim.
406
- // Only a declaration that already uses NAMED specifiers can absorb more of
407
- // them. `import * as Bestax from '…'` cannot: a namespace specifier may not
408
- // share a declaration with named ones, and appending to it emits a file
409
- // that does not parse.
410
- const existingBestax = root
411
- .find(j.ImportDeclaration, { source: { value: BESTAX } })
412
- .paths()
413
- .find(p =>
414
- // A type-only declaration cannot take a value specifier: merging a
415
- // component into `import type { … }` erases it at runtime.
416
- p.node.importKind !== 'type' &&
417
- (p.node.specifiers ?? []).every((spec) => spec.type === 'ImportSpecifier'));
418
- const preExistingImports = new Set();
419
- if (existingBestax) {
420
- for (const spec of existingBestax.node.specifiers ?? []) {
421
- if (spec.type === 'ImportSpecifier' && spec.local) {
422
- // The local name is reused either way, so the JSX never needs an
423
- // alias. But an inline `type` specifier (`import { type Box }`) is
424
- // not a value binding: it is not counted as already imported, so the
425
- // component is written as a value below — onto this specifier, which
426
- // keeps its name, rather than as a duplicate.
427
- ctx.needed.set(nameOf(spec.imported), nameOf(spec.local));
428
- if (spec.importKind !== 'type') {
429
- preExistingImports.add(nameOf(spec.imported));
430
- }
431
- }
432
- }
433
- }
319
+ const bestaxImportState = seedBestaxImport(ctx, root);
434
320
  /**
435
321
  * Move one family of rbx helper props onto a wrapping bestax component.
436
322
  * Returns the wrapper element, or null when the element carries none of
@@ -871,62 +757,13 @@ export default function transform(fileInfo, api, options = {}) {
871
757
  // ---- 3. Rewrite imports -----------------------------------------------
872
758
  if (imports.size > 0) {
873
759
  const retainedNames = [...ctx.retained].sort((a, b) => a.localeCompare(b));
874
- const freshNames = [...ctx.needed.entries()]
875
- .filter(([imported]) => requested.has(imported) && !preExistingImports.has(imported))
876
- .sort((a, b) => a[0].localeCompare(b[0]));
877
- const bestaxImport = freshNames.length > 0
878
- ? j.importDeclaration(freshNames.map(([imported, local]) => j.importSpecifier(j.identifier(imported), j.identifier(local))), j.stringLiteral(BESTAX))
879
- : null;
880
- /**
881
-
882
- * A pruned declaration takes its comments with it — a licence header, an
883
-
884
- * eslint directive. Hand them to what replaces it, or to the next statement.
885
-
886
- */
887
- const carryComments = (node, importPath) => {
888
- const comments = node.comments ?? [];
889
- if (comments.length === 0)
890
- return;
891
- const body = importPath.parent?.node?.body ?? [];
892
- const index = body.indexOf(node);
893
- // The fresh specifiers are merged INTO an existing bestax import when there
894
- // is one, and the `bestaxImport` node is then discarded — so the comments
895
- // have to follow the declaration that survives, not the one that does not.
896
- const carrier = (existingBestax ? existingBestax.node : bestaxImport) ??
897
- (index >= 0 ? body[index + 1] : undefined);
898
- if (carrier) {
899
- carrier.comments = [...comments, ...(carrier.comments ?? [])];
900
- }
901
- else if (index > 0) {
902
- // The import was the last statement: the comments stay where they
903
- // were, after what precedes it.
904
- const previous = body[index - 1];
905
- previous.comments = [
906
- ...(previous.comments ?? []),
907
- ...comments.map((c) => ({
908
- ...c,
909
- leading: false,
910
- trailing: true,
911
- })),
912
- ];
913
- }
914
- else {
915
- // The import was the only statement: the comments become the file's.
916
- const program = importPath.parent?.node;
917
- if (program)
918
- program.comments = [...comments, ...(program.comments ?? [])];
919
- }
920
- node.comments = [];
921
- };
760
+ const bestaxImport = buildBestaxImport(ctx, bestaxImportState);
922
761
  let inserted = false;
923
762
  // A retained rbx specifier must never collide with a bestax import local
924
763
  // (possible when one component is both JSX-migrated and value-retained).
925
764
  // Only a VALUE local can collide at runtime: a type-only specifier the
926
765
  // seeding above recorded, and nothing promoted, binds no value.
927
- const bestaxLocals = new Set([...ctx.needed.entries()]
928
- .filter(([imported]) => requested.has(imported) || preExistingImports.has(imported))
929
- .map(([, local]) => local));
766
+ const bestaxLocals = bestaxValueLocals(ctx, bestaxImportState);
930
767
  for (const path of rbxImportPaths) {
931
768
  const node = path.node;
932
769
  const keepSpecifiers = (node.specifiers ?? []).filter((spec) => {
@@ -944,28 +781,7 @@ export default function transform(fileInfo, api, options = {}) {
944
781
  !bestaxLocals.has(nameOf(spec.local)));
945
782
  });
946
783
  if (!inserted) {
947
- if (existingBestax && bestaxImport) {
948
- const current = existingBestax.node.specifiers ?? [];
949
- const appended = [];
950
- for (const fresh of bestaxImport.specifiers) {
951
- // A type-only specifier for the same name becomes the value
952
- // import (a value import carries the type too); appending a
953
- // second `Box` beside `type Box` would be a duplicate identifier.
954
- const typeOnly = current.find((spec) => spec.type === 'ImportSpecifier' &&
955
- spec.importKind === 'type' &&
956
- nameOf(spec.imported) === nameOf(fresh.imported));
957
- if (typeOnly) {
958
- typeOnly.importKind = null;
959
- }
960
- else {
961
- appended.push(fresh);
962
- }
963
- }
964
- existingBestax.node.specifiers = [...current, ...appended];
965
- }
966
- else if (bestaxImport) {
967
- path.insertBefore(bestaxImport);
968
- }
784
+ placeBestaxImport(bestaxImportState, bestaxImport, path);
969
785
  inserted = true;
970
786
  }
971
787
  const droppedOnCollision = (node.specifiers ?? []).filter((spec) => spec.type === 'ImportSpecifier' &&
@@ -988,7 +804,7 @@ export default function transform(fileInfo, api, options = {}) {
988
804
  }
989
805
  }
990
806
  else {
991
- carryComments(node, path);
807
+ carryPrunedComments(node, path, bestaxImportState.existing?.node ?? bestaxImport);
992
808
  path.prune();
993
809
  }
994
810
  ctx.dirty = true;
@@ -998,6 +814,8 @@ export default function transform(fileInfo, api, options = {}) {
998
814
  // import rewrite, so the drop is visible in the file and not only the report.
999
815
  if (!ctx.dirty)
1000
816
  return undefined;
817
+ forgetJsxParens(j, root);
818
+ reprintDirectives(j, root);
1001
819
  // Double quotes match the dominant JSX-attribute convention; users run
1002
820
  // their own formatter afterwards anyway. Tab-indented sources keep tabs so
1003
821
  // reprinted nodes don't drift from the untouched lines around them.
@@ -1 +1 @@
1
- {"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/deps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAUzD,eAAO,MAAM,kBAAkB,EAAE,kBAqHhC,CAAC"}
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/deps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAUzD,eAAO,MAAM,kBAAkB,EAAE,kBAuChC,CAAC"}