bestax-migrate 0.0.1

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 (47) hide show
  1. package/README.md +59 -0
  2. package/dist/cli.d.ts +22 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +225 -0
  5. package/dist/index.d.ts +7 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +12 -0
  8. package/dist/report.d.ts +24 -0
  9. package/dist/report.d.ts.map +1 -0
  10. package/dist/report.js +66 -0
  11. package/dist/runner.d.ts +16 -0
  12. package/dist/runner.d.ts.map +1 -0
  13. package/dist/runner.js +69 -0
  14. package/dist/sources/react-bulma-components/deps.d.ts +9 -0
  15. package/dist/sources/react-bulma-components/deps.d.ts.map +1 -0
  16. package/dist/sources/react-bulma-components/deps.js +96 -0
  17. package/dist/sources/react-bulma-components/index.d.ts +3 -0
  18. package/dist/sources/react-bulma-components/index.d.ts.map +1 -0
  19. package/dist/sources/react-bulma-components/index.js +10 -0
  20. package/dist/sources/react-bulma-components/jsx-utils.d.ts +69 -0
  21. package/dist/sources/react-bulma-components/jsx-utils.d.ts.map +1 -0
  22. package/dist/sources/react-bulma-components/jsx-utils.js +130 -0
  23. package/dist/sources/react-bulma-components/mapping.d.ts +31 -0
  24. package/dist/sources/react-bulma-components/mapping.d.ts.map +1 -0
  25. package/dist/sources/react-bulma-components/mapping.js +852 -0
  26. package/dist/sources/react-bulma-components/props.d.ts +15 -0
  27. package/dist/sources/react-bulma-components/props.d.ts.map +1 -0
  28. package/dist/sources/react-bulma-components/props.js +124 -0
  29. package/dist/sources/react-bulma-components/responsive.d.ts +17 -0
  30. package/dist/sources/react-bulma-components/responsive.d.ts.map +1 -0
  31. package/dist/sources/react-bulma-components/responsive.js +113 -0
  32. package/dist/sources/react-bulma-components/specials.d.ts +23 -0
  33. package/dist/sources/react-bulma-components/specials.d.ts.map +1 -0
  34. package/dist/sources/react-bulma-components/specials.js +546 -0
  35. package/dist/sources/react-bulma-components/styles.d.ts +13 -0
  36. package/dist/sources/react-bulma-components/styles.d.ts.map +1 -0
  37. package/dist/sources/react-bulma-components/styles.js +199 -0
  38. package/dist/sources/react-bulma-components/transform.d.ts +19 -0
  39. package/dist/sources/react-bulma-components/transform.d.ts.map +1 -0
  40. package/dist/sources/react-bulma-components/transform.js +495 -0
  41. package/dist/sources/registry.d.ts +9 -0
  42. package/dist/sources/registry.d.ts.map +1 -0
  43. package/dist/sources/registry.js +14 -0
  44. package/dist/types.d.ts +122 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +9 -0
  47. package/package.json +74 -0
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # bestax-migrate
2
+
3
+ Codemods 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**.
4
+
5
+ Currently supported source libraries:
6
+
7
+ | Source | Status |
8
+ | -------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
9
+ | [`react-bulma-components`](https://github.com/couds/react-bulma-components) (v4) | ✅ All 32 components mapped (a few patterns are flagged as TODOs) |
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Preview what would change (no writes)
15
+ pnpm dlx bestax-migrate react-bulma-components src/ --dry
16
+
17
+ # Apply the migration
18
+ pnpm dlx bestax-migrate react-bulma-components src/
19
+ ```
20
+
21
+ npm and yarn work too: `npx bestax-migrate …` / `yarn dlx bestax-migrate …`.
22
+
23
+ The codemod uses [jscodeshift](https://github.com/facebook/jscodeshift) to:
24
+
25
+ - rewrite `react-bulma-components` imports to `@allxsmith/bestax-bulma` (including `const { Input } = Form` destructuring and namespace imports)
26
+ - rename components and compound sub-components (`Form.Textarea` → `TextArea`, `Card.Footer.Item` → `Card.FooterItem`, `Hero.Footer` → `Hero.Foot`, …)
27
+ - convert props (`renderAs` → `as`, `loading` → `isLoading`, numeric spacing/text sizes → string unions, `textAlign="center"` → `textAlign="centered"`, …)
28
+ - flatten responsive breakpoint objects (`mobile={{ size: 4 }}` → `sizeMobile={4}`)
29
+ - restructure patterns bestax models differently (`Table.Container`, Navbar dropdowns, `Form.Help`, icon-font children → `<Icon name=…>`)
30
+ - 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'`
31
+ - update `package.json`: remove `react-bulma-components`, add `@allxsmith/bestax-bulma`, bump `bulma` to `^1`, and swap the dead `node-sass` for dart `sass` (no install is ever run)
32
+
33
+ Anything 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](https://bestax.io/docs/guides/getting-started/migration/react-bulma-components), or let the [`bestax-migrate` Agent Skill](https://bestax.io/docs/skills/intro) walk them for you:
34
+
35
+ ```bash
36
+ npx skills add https://github.com/allxsmith/bestax --skill bestax-migrate
37
+ ```
38
+
39
+ ## Options
40
+
41
+ | Flag | Description |
42
+ | -------------------- | -------------------------------------------------------------- |
43
+ | `--dry`, `-d` | Report what would change without writing files |
44
+ | `--print`, `-p` | Print transformed sources to stdout |
45
+ | `--extensions`, `-e` | File extensions to include (default `js,jsx,ts,tsx,scss,sass`) |
46
+ | `--css <mode>` | Stylesheet target: `bestax` (default), `bulma`, or `keep` |
47
+ | `--no-deps` | Skip updating package.json dependencies |
48
+
49
+ ## After the codemod
50
+
51
+ 1. Run your package manager's install (the codemod rewrote `package.json` but never installs)
52
+ 2. Search for `TODO(bestax-migrate)` and resolve each comment
53
+ 3. Typecheck/build and review the rendered app
54
+
55
+ Full walkthrough: [react-bulma-components migration guide](https://bestax.io/docs/guides/getting-started/migration/react-bulma-components).
56
+
57
+ ## License
58
+
59
+ MIT © Alex Smith
package/dist/cli.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The bestax-migrate CLI:
3
+ *
4
+ * bestax-migrate <source> <paths...> [--dry] [--print] [--extensions ...]
5
+ * [--css bestax|bulma|keep] [--no-deps]
6
+ *
7
+ * Walks the given files/directories, routes each file by type (JS/TSX →
8
+ * jscodeshift transform, .scss/.sass → the source's stylesheet transform),
9
+ * updates the nearest package.json (unless --no-deps), writes results back
10
+ * (unless --dry), and prints the run report. TODO annotations are expected
11
+ * output — the exit code is 0 whenever the run itself succeeds.
12
+ */
13
+ import { Command } from 'commander';
14
+ export declare function collectFiles(targets: string[], extensions: string[]): string[];
15
+ /** Nearest package.json for each target, walking up to the filesystem root. */
16
+ export declare function findPackageJsons(targets: string[]): string[];
17
+ export interface CliIo {
18
+ log: (message: string) => void;
19
+ error: (message: string) => void;
20
+ }
21
+ export declare function createCLI(io?: CliIo): Command;
22
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,EAAE,MAAM,EAAE,GACnB,MAAM,EAAE,CAkBV;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAkB5D;AAED,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AA+HD,wBAAgB,SAAS,CACvB,EAAE,GAAE,KAAkD,GACrD,OAAO,CA0FT"}
package/dist/cli.js ADDED
@@ -0,0 +1,225 @@
1
+ /**
2
+ * The bestax-migrate CLI:
3
+ *
4
+ * bestax-migrate <source> <paths...> [--dry] [--print] [--extensions ...]
5
+ * [--css bestax|bulma|keep] [--no-deps]
6
+ *
7
+ * Walks the given files/directories, routes each file by type (JS/TSX →
8
+ * jscodeshift transform, .scss/.sass → the source's stylesheet transform),
9
+ * updates the nearest package.json (unless --no-deps), writes results back
10
+ * (unless --dry), and prints the run report. TODO annotations are expected
11
+ * output — the exit code is 0 whenever the run itself succeeds.
12
+ */
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import chalk from 'chalk';
16
+ import { Command } from 'commander';
17
+ import { Reporter } from './report.js';
18
+ import { runTransform } from './runner.js';
19
+ import { getSource, sourceNames } from './sources/registry.js';
20
+ const SKIP_DIRS = new Set([
21
+ 'node_modules',
22
+ '.git',
23
+ 'dist',
24
+ 'build',
25
+ 'coverage',
26
+ '.next',
27
+ ]);
28
+ const STYLE_EXTENSIONS = new Set(['scss', 'sass']);
29
+ const CSS_MODES = ['bestax', 'bulma', 'keep'];
30
+ /**
31
+ * Component formats the codemod cannot parse (no jscodeshift grammar) but
32
+ * that can still import the source library — flagged, never rewritten.
33
+ */
34
+ const UNSUPPORTED_EXTENSIONS = ['astro', 'vue', 'svelte', 'mdx'];
35
+ export function collectFiles(targets, extensions) {
36
+ const files = [];
37
+ const visit = (target) => {
38
+ const stat = fs.statSync(target, { throwIfNoEntry: false });
39
+ if (!stat) {
40
+ throw new Error(`no such file or directory: ${target}`);
41
+ }
42
+ if (stat.isDirectory()) {
43
+ if (SKIP_DIRS.has(path.basename(target)))
44
+ return;
45
+ for (const entry of fs.readdirSync(target).sort()) {
46
+ visit(path.join(target, entry));
47
+ }
48
+ }
49
+ else if (extensions.includes(path.extname(target).replace(/^\./, ''))) {
50
+ files.push(target);
51
+ }
52
+ };
53
+ for (const target of targets)
54
+ visit(target);
55
+ return files;
56
+ }
57
+ /** Nearest package.json for each target, walking up to the filesystem root. */
58
+ export function findPackageJsons(targets) {
59
+ const found = new Set();
60
+ for (const target of targets) {
61
+ const stat = fs.statSync(target, { throwIfNoEntry: false });
62
+ if (!stat)
63
+ continue;
64
+ let dir = path.resolve(stat.isDirectory() ? target : path.dirname(target));
65
+ for (;;) {
66
+ const candidate = path.join(dir, 'package.json');
67
+ if (fs.existsSync(candidate)) {
68
+ found.add(candidate);
69
+ break;
70
+ }
71
+ const parent = path.dirname(dir);
72
+ if (parent === dir)
73
+ break;
74
+ dir = parent;
75
+ }
76
+ }
77
+ return [...found].sort();
78
+ }
79
+ function migrateFiles(source, files, reporter, io, options) {
80
+ let bulmaReferenced = false;
81
+ for (const file of files) {
82
+ const sourceText = fs.readFileSync(file, 'utf8');
83
+ const collector = reporter.startFile();
84
+ const extension = path.extname(file).replace(/^\./, '');
85
+ let output;
86
+ try {
87
+ if (STYLE_EXTENSIONS.has(extension)) {
88
+ output = source.transformStyles
89
+ ? source.transformStyles(file, sourceText, collector, {
90
+ cssMode: options.cssMode,
91
+ })
92
+ : null;
93
+ }
94
+ else {
95
+ output = runTransform(source.transform, file, sourceText, collector, {
96
+ cssMode: options.cssMode,
97
+ }).output;
98
+ }
99
+ }
100
+ catch (error) {
101
+ io.error(chalk.red(`✖ ${file}: ${error.message}`));
102
+ reporter.finishFile(file, false, collector.entries);
103
+ continue;
104
+ }
105
+ if (/['"](?:~?bulma\/)/.test(output ?? sourceText)) {
106
+ bulmaReferenced = true;
107
+ }
108
+ if (output !== null) {
109
+ if (options.print)
110
+ io.log(output);
111
+ if (!options.dry)
112
+ fs.writeFileSync(file, output);
113
+ }
114
+ reporter.finishFile(file, output !== null, collector.entries);
115
+ }
116
+ return bulmaReferenced;
117
+ }
118
+ /**
119
+ * Surface files in unparseable formats (.astro, .vue, …) that import the
120
+ * source library, so a migration never silently skips them.
121
+ */
122
+ function reportUnsupportedFiles(source, targets, reporter, extensions) {
123
+ const unsupported = UNSUPPORTED_EXTENSIONS.filter(ext => !extensions.includes(ext));
124
+ if (unsupported.length === 0)
125
+ return;
126
+ for (const file of collectFiles(targets, unsupported)) {
127
+ const text = fs.readFileSync(file, 'utf8');
128
+ if (!text.includes(source.name))
129
+ continue;
130
+ const collector = reporter.startFile();
131
+ const line = text.split('\n').findIndex(l => l.includes(source.name)) + 1 || null;
132
+ collector.add({
133
+ file,
134
+ line,
135
+ rule: 'unsupported-file',
136
+ message: `imports ${source.name}, but ${path.extname(file)} files cannot be parsed by the codemod — migrate this file by hand`,
137
+ });
138
+ reporter.finishFile(file, false, collector.entries);
139
+ }
140
+ }
141
+ function migrateDependencies(source, targets, reporter, io, options, bulmaReferenced) {
142
+ if (!options.deps || !source.updateDependencies)
143
+ return;
144
+ for (const pkgPath of findPackageJsons(targets)) {
145
+ const collector = reporter.startFile();
146
+ let changed = false;
147
+ try {
148
+ const raw = fs.readFileSync(pkgPath, 'utf8');
149
+ const pkg = JSON.parse(raw);
150
+ const next = source.updateDependencies(pkgPath, pkg, collector, {
151
+ cssMode: options.cssMode,
152
+ bulmaReferenced,
153
+ });
154
+ if (next !== null) {
155
+ changed = true;
156
+ if (!options.dry) {
157
+ // Keep the manifest's own indentation (tabs, 4-space, …) so the
158
+ // diff stays limited to the dependency changes.
159
+ const indent = raw.match(/^([ \t]+)"/m)?.[1] ?? ' ';
160
+ fs.writeFileSync(pkgPath, `${JSON.stringify(next, null, indent)}\n`);
161
+ }
162
+ }
163
+ }
164
+ catch (error) {
165
+ io.error(chalk.red(`✖ ${pkgPath}: ${error.message}`));
166
+ }
167
+ reporter.finishFile(pkgPath, changed, collector.entries);
168
+ if (changed) {
169
+ io.log(chalk.yellow(options.dry
170
+ ? `Would update ${pkgPath}.`
171
+ : `Updated ${pkgPath} — run your package manager's install to apply.`));
172
+ }
173
+ }
174
+ }
175
+ export function createCLI(io = { log: console.log, error: console.error }) {
176
+ const program = new Command();
177
+ program
178
+ .name('bestax-migrate')
179
+ .description('Codemods that migrate existing apps to @allxsmith/bestax-bulma')
180
+ .argument('<source>', `library to migrate from (${sourceNames().join(', ')})`)
181
+ .argument('<paths...>', 'files or directories to transform')
182
+ .option('-d, --dry', 'dry run — report what would change without writing', false)
183
+ .option('-p, --print', 'print transformed sources to stdout', false)
184
+ .option('-e, --extensions <list>', 'comma-separated file extensions to include', 'js,jsx,ts,tsx,scss,sass')
185
+ .option('--css <mode>', `stylesheet target: ${CSS_MODES.join(', ')}`, 'bestax')
186
+ .option('--no-deps', 'skip updating package.json dependencies')
187
+ .action((sourceName, targets, options) => {
188
+ const source = getSource(sourceName);
189
+ if (!source) {
190
+ io.error(`${chalk.red('Unknown source')} "${sourceName}". Available sources: ${sourceNames().join(', ')}`);
191
+ program.error('', { exitCode: 1 });
192
+ return;
193
+ }
194
+ if (!CSS_MODES.includes(options.css)) {
195
+ io.error(`${chalk.red('Unknown --css mode')} "${options.css}". Valid modes: ${CSS_MODES.join(', ')}`);
196
+ program.error('', { exitCode: 1 });
197
+ return;
198
+ }
199
+ const extensions = String(options.extensions)
200
+ .split(',')
201
+ .map(ext => ext.trim())
202
+ .filter(Boolean);
203
+ let files;
204
+ try {
205
+ files = collectFiles(targets, extensions);
206
+ }
207
+ catch (error) {
208
+ io.error(chalk.red(error.message));
209
+ program.error('', { exitCode: 1 });
210
+ return;
211
+ }
212
+ const runOptions = {
213
+ dry: Boolean(options.dry),
214
+ print: Boolean(options.print),
215
+ deps: options.deps !== false,
216
+ cssMode: options.css,
217
+ };
218
+ const reporter = new Reporter();
219
+ const bulmaReferenced = migrateFiles(source, files, reporter, io, runOptions);
220
+ reportUnsupportedFiles(source, targets, reporter, extensions);
221
+ migrateDependencies(source, targets, reporter, io, runOptions, bulmaReferenced);
222
+ io.log(reporter.render(source.label + (runOptions.dry ? ' (dry run)' : '')));
223
+ });
224
+ return program;
225
+ }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin entry for bestax-migrate. Keep this thin — the program lives in cli.ts
4
+ * so tests can drive it without spawning a process.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;GAGG"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin entry for bestax-migrate. Keep this thin — the program lives in cli.ts
4
+ * so tests can drive it without spawning a process.
5
+ */
6
+ import { createCLI } from './cli.js';
7
+ const [major] = process.versions.node.split('.').map(Number);
8
+ if (major < 18) {
9
+ console.error(`bestax-migrate requires Node 18 or newer (found ${process.versions.node}).`);
10
+ process.exit(1);
11
+ }
12
+ createCLI().parse();
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Run reporting: collects per-file results and prints the end-of-run summary.
3
+ * TODO annotations are expected output, not failures — the CLI exits 0 when
4
+ * files were transformed and TODOs remain.
5
+ */
6
+ import type { FileResult, TodoCollector, TodoEntry } from './types.js';
7
+ export declare class Reporter {
8
+ private results;
9
+ /** Returns a collector for one file's run; call `finishFile` when done. */
10
+ startFile(): TodoCollector & {
11
+ entries: TodoEntry[];
12
+ };
13
+ finishFile(file: string, changed: boolean, todos: TodoEntry[]): void;
14
+ get files(): FileResult[];
15
+ get changedCount(): number;
16
+ get todos(): TodoEntry[];
17
+ /** TODO counts grouped by rule, highest first. */
18
+ todosByRule(): Array<{
19
+ rule: string;
20
+ entries: TodoEntry[];
21
+ }>;
22
+ render(label: string): string;
23
+ }
24
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvE,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAoB;IAEnC,2EAA2E;IAC3E,SAAS,IAAI,aAAa,GAAG;QAAE,OAAO,EAAE,SAAS,EAAE,CAAA;KAAE;IAUrD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI;IAIpE,IAAI,KAAK,IAAI,UAAU,EAAE,CAExB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,KAAK,IAAI,SAAS,EAAE,CAEvB;IAED,kDAAkD;IAClD,WAAW,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,SAAS,EAAE,CAAA;KAAE,CAAC;IAY5D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CA8B9B"}
package/dist/report.js ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Run reporting: collects per-file results and prints the end-of-run summary.
3
+ * TODO annotations are expected output, not failures — the CLI exits 0 when
4
+ * files were transformed and TODOs remain.
5
+ */
6
+ import chalk from 'chalk';
7
+ export class Reporter {
8
+ results = [];
9
+ /** Returns a collector for one file's run; call `finishFile` when done. */
10
+ startFile() {
11
+ const entries = [];
12
+ return {
13
+ entries,
14
+ add(entry) {
15
+ entries.push(entry);
16
+ },
17
+ };
18
+ }
19
+ finishFile(file, changed, todos) {
20
+ this.results.push({ file, changed, todos });
21
+ }
22
+ get files() {
23
+ return this.results;
24
+ }
25
+ get changedCount() {
26
+ return this.results.filter(r => r.changed).length;
27
+ }
28
+ get todos() {
29
+ return this.results.flatMap(r => r.todos);
30
+ }
31
+ /** TODO counts grouped by rule, highest first. */
32
+ todosByRule() {
33
+ const byRule = new Map();
34
+ for (const todo of this.todos) {
35
+ const list = byRule.get(todo.rule) ?? [];
36
+ list.push(todo);
37
+ byRule.set(todo.rule, list);
38
+ }
39
+ return [...byRule.entries()]
40
+ .map(([rule, entries]) => ({ rule, entries }))
41
+ .sort((a, b) => b.entries.length - a.entries.length);
42
+ }
43
+ render(label) {
44
+ const lines = [];
45
+ lines.push('');
46
+ lines.push(chalk.bold(`bestax-migrate — ${label}`));
47
+ lines.push(`${this.results.length} file(s) scanned, ` +
48
+ `${chalk.green(String(this.changedCount))} transformed, ` +
49
+ `${chalk.yellow(String(this.todos.length))} TODO(s) left`);
50
+ for (const { rule, entries } of this.todosByRule()) {
51
+ lines.push('');
52
+ lines.push(chalk.yellow(` ${rule} (${entries.length})`));
53
+ for (const entry of entries) {
54
+ const location = entry.line === null ? entry.file : `${entry.file}:${entry.line}`;
55
+ lines.push(` ${location} — ${entry.message}`);
56
+ }
57
+ }
58
+ if (this.todos.length > 0) {
59
+ lines.push('');
60
+ lines.push(`Search for ${chalk.bold('TODO(bestax-migrate)')} comments to finish the migration by hand,`);
61
+ lines.push('or let the bestax-migrate skill walk them: https://bestax.io/docs/skills/intro');
62
+ }
63
+ lines.push('');
64
+ return lines.join('\n');
65
+ }
66
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * In-process transform runner shared by the CLI, the fixture tests, and the
3
+ * kitchen-sink e2e. jscodeshift's worker-based Runner API is deliberately not
4
+ * used: it spawns CJS child processes (fragile from an ESM package) and hides
5
+ * per-file results, which the report needs.
6
+ */
7
+ import type { API } from 'jscodeshift';
8
+ import type { TodoCollector, Transform } from './types.js';
9
+ /** A jscodeshift `api` object for direct transform invocation. */
10
+ export declare function makeApi(): API;
11
+ export interface RunResult {
12
+ /** Transformed source, or null when the file needed no changes. */
13
+ output: string | null;
14
+ }
15
+ export declare function runTransform(transform: Transform, path: string, source: string, collector?: TodoCollector, options?: Record<string, unknown>): RunResult;
16
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA8C3D,kEAAkE;AAClE,wBAAgB,OAAO,IAAI,GAAG,CAO7B;AAED,MAAM,WAAW,SAAS;IACxB,mEAAmE;IACnE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,aAAa,EACzB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,SAAS,CASX"}
package/dist/runner.js ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * In-process transform runner shared by the CLI, the fixture tests, and the
3
+ * kitchen-sink e2e. jscodeshift's worker-based Runner API is deliberately not
4
+ * used: it spawns CJS child processes (fragile from an ESM package) and hides
5
+ * per-file results, which the report needs.
6
+ */
7
+ import { parse } from '@babel/parser';
8
+ import jscodeshift from 'jscodeshift';
9
+ /**
10
+ * jscodeshift's stock `tsx` parser options plus `deprecatedImportAssert`,
11
+ * so files using the legacy `import x from 'y' assert { type: 'json' }`
12
+ * syntax (still emitted by many tools) parse instead of crashing the run.
13
+ */
14
+ const PARSER_OPTIONS = {
15
+ sourceType: 'module',
16
+ allowImportExportEverywhere: true,
17
+ allowReturnOutsideFunction: true,
18
+ startLine: 1,
19
+ tokens: true,
20
+ plugins: [
21
+ 'jsx',
22
+ 'asyncGenerators',
23
+ 'decoratorAutoAccessors',
24
+ 'bigInt',
25
+ 'classPrivateMethods',
26
+ 'classPrivateProperties',
27
+ 'classProperties',
28
+ 'decorators-legacy',
29
+ 'doExpressions',
30
+ 'dynamicImport',
31
+ 'exportDefaultFrom',
32
+ 'exportNamespaceFrom',
33
+ 'functionBind',
34
+ 'functionSent',
35
+ 'importAttributes',
36
+ 'deprecatedImportAssert',
37
+ 'importMeta',
38
+ 'nullishCoalescingOperator',
39
+ 'numericSeparator',
40
+ 'objectRestSpread',
41
+ 'optionalCatchBinding',
42
+ 'optionalChaining',
43
+ ['pipelineOperator', { proposal: 'minimal' }],
44
+ 'throwExpressions',
45
+ 'typescript',
46
+ ],
47
+ };
48
+ const j = jscodeshift.withParser({
49
+ parse: (code) => parse(code, PARSER_OPTIONS),
50
+ });
51
+ /** A jscodeshift `api` object for direct transform invocation. */
52
+ export function makeApi() {
53
+ return {
54
+ j,
55
+ jscodeshift: j,
56
+ stats: () => { },
57
+ report: () => { },
58
+ };
59
+ }
60
+ export function runTransform(transform, path, source, collector, options = {}) {
61
+ const result = transform({ path, source }, makeApi(), {
62
+ collector,
63
+ ...options,
64
+ });
65
+ if (result === undefined || result === null || result === source) {
66
+ return { output: null };
67
+ }
68
+ return { output: result };
69
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * package.json migration: swap react-bulma-components for
3
+ * @allxsmith/bestax-bulma, move Bulma to v1, and replace the dead node-sass
4
+ * with dart-sass. Pure data-in/data-out — the CLI owns file IO, and no
5
+ * package manager is ever invoked (the report tells the user to install).
6
+ */
7
+ import type { DependenciesUpdate } from '../../types.js';
8
+ export declare const updateDependencies: DependenciesUpdate;
9
+ //# sourceMappingURL=deps.d.ts.map
@@ -0,0 +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;AAazD,eAAO,MAAM,kBAAkB,EAAE,kBAmGhC,CAAC"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * package.json migration: swap react-bulma-components for
3
+ * @allxsmith/bestax-bulma, move Bulma to v1, and replace the dead node-sass
4
+ * with dart-sass. Pure data-in/data-out — the CLI owns file IO, and no
5
+ * package manager is ever invoked (the report tells the user to install).
6
+ */
7
+ const BESTAX_RANGE = '^5';
8
+ const BULMA_RANGE = '^1.0.4';
9
+ // Bulma v1's sass tree uses `color.channel(…)` — needs dart-sass ≥ 1.79.
10
+ const SASS_RANGE = '^1.79.0';
11
+ const DEP_SECTIONS = ['dependencies', 'devDependencies'];
12
+ function isPreV1(range) {
13
+ return /^[~^]?0[.x]/.test(range.trim());
14
+ }
15
+ export const updateDependencies = (filePath, pkg, collector, options) => {
16
+ const changes = [];
17
+ const next = pkg;
18
+ const section = (name) => (next[name] ?? undefined);
19
+ const note = (message) => {
20
+ changes.push(message);
21
+ collector?.add({ file: filePath, line: null, rule: 'deps', message });
22
+ };
23
+ // react-bulma-components goes away entirely.
24
+ for (const name of DEP_SECTIONS) {
25
+ const deps = section(name);
26
+ if (deps && 'react-bulma-components' in deps) {
27
+ delete deps['react-bulma-components'];
28
+ note(`removed react-bulma-components from ${name}`);
29
+ }
30
+ }
31
+ // @allxsmith/bestax-bulma comes in (runtime dependency).
32
+ const dependencies = (next.dependencies ??= {});
33
+ if (!dependencies['@allxsmith/bestax-bulma'] &&
34
+ !section('devDependencies')?.['@allxsmith/bestax-bulma']) {
35
+ dependencies['@allxsmith/bestax-bulma'] = BESTAX_RANGE;
36
+ note(`added @allxsmith/bestax-bulma ${BESTAX_RANGE} to dependencies`);
37
+ }
38
+ // Bulma: bump a pre-1 range; add only when sources still reference bulma/…
39
+ // directly (otherwise it arrives transitively via bestax-bulma).
40
+ let bulmaDeclared = false;
41
+ for (const name of DEP_SECTIONS) {
42
+ const deps = section(name);
43
+ if (deps?.bulma) {
44
+ bulmaDeclared = true;
45
+ if (isPreV1(deps.bulma)) {
46
+ deps.bulma = BULMA_RANGE;
47
+ note(`bumped bulma to ${BULMA_RANGE} in ${name} (was pre-1.0)`);
48
+ }
49
+ }
50
+ }
51
+ if (!bulmaDeclared && options.bulmaReferenced) {
52
+ dependencies.bulma = BULMA_RANGE;
53
+ note(`added bulma ${BULMA_RANGE} to dependencies (sources import bulma/… directly)`);
54
+ }
55
+ // bestax-bulma requires React 18/19; RBC v4 also ran on 17. Report only —
56
+ // a React major upgrade is the app's own migration step.
57
+ for (const name of DEP_SECTIONS) {
58
+ const range = section(name)?.react;
59
+ if (range && /^[~^]?(?:[0-9]|1[0-7])(?:[.x]|$)/.test(range.trim())) {
60
+ collector?.add({
61
+ file: filePath,
62
+ line: null,
63
+ rule: 'peer-deps',
64
+ message: `react ${range} predates bestax-bulma's peer range (^18 || ^19) — upgrade react and react-dom to 18 or 19 before installing`,
65
+ });
66
+ }
67
+ }
68
+ // Font Awesome older than 6 conflicts with bestax-bulma's optional peer
69
+ // range and makes `npm install` fail with ERESOLVE. Report only — icon
70
+ // names change across FA majors, so upgrading is the app's decision.
71
+ for (const name of DEP_SECTIONS) {
72
+ const range = section(name)?.['@fortawesome/fontawesome-free'];
73
+ if (range && /^[~^]?[0-5](?:[.x]|$)/.test(range.trim())) {
74
+ collector?.add({
75
+ file: filePath,
76
+ line: null,
77
+ rule: 'peer-deps',
78
+ 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\``,
79
+ });
80
+ }
81
+ }
82
+ // node-sass is dead; dart-sass replaces it in the same section.
83
+ for (const name of DEP_SECTIONS) {
84
+ const deps = section(name);
85
+ if (deps && 'node-sass' in deps) {
86
+ delete deps['node-sass'];
87
+ note(`removed node-sass from ${name}`);
88
+ const sassDeclared = DEP_SECTIONS.some(s => section(s)?.sass);
89
+ if (!sassDeclared) {
90
+ deps.sass = SASS_RANGE;
91
+ note(`added sass ${SASS_RANGE} to ${name} (replaces node-sass)`);
92
+ }
93
+ }
94
+ }
95
+ return changes.length > 0 ? next : null;
96
+ };
@@ -0,0 +1,3 @@
1
+ import type { MigrationSource } from '../../types.js';
2
+ export declare const reactBulmaComponents: MigrationSource;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,eAAO,MAAM,oBAAoB,EAAE,eAMlC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import transform from './transform.js';
2
+ import { transformStyles } from './styles.js';
3
+ import { updateDependencies } from './deps.js';
4
+ export const reactBulmaComponents = {
5
+ name: 'react-bulma-components',
6
+ label: 'react-bulma-components (v4) → @allxsmith/bestax-bulma',
7
+ transform,
8
+ transformStyles,
9
+ updateDependencies,
10
+ };