diff --git a/bestax-mcp/data/skills.json b/bestax-mcp/data/skills.json index 5539fa959..f5ff54069 100644 --- a/bestax-mcp/data/skills.json +++ b/bestax-mcp/data/skills.json @@ -93,7 +93,7 @@ { "id": "css-migration", "file": "references/css-migration.md", - "bytes": 5838 + "bytes": 7999 }, { "id": "prop-map", "file": "references/prop-map.md", "bytes": 6363 }, { diff --git a/bestax-migrate/src/cli.ts b/bestax-migrate/src/cli.ts index 351adf4a5..34c04c1e4 100644 --- a/bestax-migrate/src/cli.ts +++ b/bestax-migrate/src/cli.ts @@ -116,6 +116,7 @@ function migrateFiles( output = source.transformStyles ? source.transformStyles(file, sourceText, collector, { cssMode: options.cssMode, + deps: options.deps, }) : null; } else { diff --git a/bestax-migrate/src/sources/react-bulma-components/__tests__/styles.test.ts b/bestax-migrate/src/sources/react-bulma-components/__tests__/styles.test.ts index fbad6f0a1..7225e67b8 100644 --- a/bestax-migrate/src/sources/react-bulma-components/__tests__/styles.test.ts +++ b/bestax-migrate/src/sources/react-bulma-components/__tests__/styles.test.ts @@ -210,6 +210,265 @@ describe('transformStyles (.scss)', () => { expect(output).toContain('TODO(bestax-migrate)'); expect(todos).toHaveLength(1); }); + + describe("the source library's own stylesheet", () => { + it.each([ + [ + 'bare Sass entry point', + "@import 'react-bulma-components/src/index.sass';\n", + ], + [ + '~-prefixed Sass entry point', + "@import '~react-bulma-components/src/index.sass';\n", + ], + [ + 'bundled v3 CSS', + "@import 'react-bulma-components/dist/react-bulma-components.min.css';\n", + ], + ])( + 'is not mislabeled as a third-party extension (%s)', + (_label, source) => { + const { output, todos } = run('main.scss', source); + expect(output).not.toContain('third-party'); + expect(output).not.toContain('bulma-components'); + // The mislabel we guard against is calling the library's own root a + // third-party `bulma-components` *extension*; a report that names the + // package while rewriting its root is fine. + expect(todos.every(t => !t.message.includes('third-party'))).toBe(true); + expect(todos.every(t => !t.message.includes('extension'))).toBe(true); + } + ); + + it('bestax mode: rewrites the bare Sass entry point to bulma/sass + extras', () => { + const { output } = run( + 'main.scss', + "@import 'react-bulma-components/src/index.sass';\n" + ); + expect(output).toContain("@use 'bulma/sass';"); + expect(output).toContain("@use '@allxsmith/bestax-bulma/scss/extras';"); + // Never the hard-configured bundle: it can't take the user's own theme + // vars and reconfiguring bulma/sass elsewhere in the build is a hard error. + expect(output).not.toContain('scss/bestax'); + expect(output).not.toContain('@import'); + expect(output).not.toContain('TODO'); + }); + + it('bestax mode: rewrites the ~-prefixed Sass entry point the same way', () => { + const { output } = run( + 'main.scss', + "@import '~react-bulma-components/src/index.sass';\n" + ); + expect(output).toContain("@use 'bulma/sass';"); + expect(output).toContain("@use '@allxsmith/bestax-bulma/scss/extras';"); + }); + + it('bestax mode: rewrites the bundled v3 CSS entry point the same way', () => { + const { output } = run( + 'main.scss', + "@import 'react-bulma-components/dist/react-bulma-components.min.css';\n" + ); + expect(output).toContain("@use 'bulma/sass';"); + expect(output).toContain("@use '@allxsmith/bestax-bulma/scss/extras';"); + }); + + it('folds leading $var overrides above the RBC stylesheet into with (…)', () => { + const source = [ + '$primary: #ff6b35;', + "@import '~react-bulma-components/src/index.sass';", + ].join('\n'); + const { output } = run('theme.scss', source); + expect(output).toContain("@use 'bulma/sass' with ("); + expect(output).toContain('$primary: #ff6b35'); + expect(output).toContain("@use '@allxsmith/bestax-bulma/scss/extras';"); + expect(output).not.toContain('scss/bestax'); + }); + + it('parenthesizes a comma-valued override folded through the RBC root', () => { + // The seam between the two fixes: the RBC path folds vars through the + // same emission as the @import root, so a bare Sass list must come out + // parenthesized here too — Dart Sass otherwise reads the comma as an + // extra with (…) argument. + const source = [ + "$family-primary: 'Nunito', sans-serif;", + "@import '~react-bulma-components/src/index.sass';", + ].join('\n'); + const { output } = run('theme.scss', source); + expect(output).toContain("$family-primary: ('Nunito', sans-serif)"); + }); + + it.each([ + [ + 'relative node_modules Sass path', + "@import '../../node_modules/react-bulma-components/src/index.sass';\n", + ], + [ + 'extensionless Sass entry point', + "@import 'react-bulma-components/src/index';\n", + ], + [ + 'deep component partial', + "@import 'react-bulma-components/src/components/navbar.sass';\n", + ], + ])( + 'rewrites the RBC stylesheet regardless of specifier shape (%s)', + (_l, source) => { + const { output } = run('main.scss', source); + expect(output).toContain('bulma/sass'); + expect(output).not.toContain('react-bulma-components'); + expect(output).not.toContain('convert to @use by hand'); + } + ); + + it('preserves the relative node_modules prefix on the rewritten root', () => { + const { output } = run( + 'deep/nested.scss', + "@import '../../node_modules/react-bulma-components/src/index.sass';\n" + ); + expect(output).toContain("@use '../../node_modules/bulma/sass';"); + expect(output).toContain( + "@use '../../node_modules/@allxsmith/bestax-bulma/src/scss/extras';" + ); + }); + + it('adds only the extras when the file already has its own @use bulma root', () => { + const source = [ + "@use 'bulma/sass';", + "@import 'react-bulma-components/src/index.sass';", + ].join('\n'); + const { output } = run('main.scss', source); + const bulmaRoots = (output ?? '').match(/@use '[^']*bulma\/sass'/g) ?? []; + expect(bulmaRoots).toHaveLength(1); // the existing one, not a second + expect(output).toContain("@use '@allxsmith/bestax-bulma/scss/extras';"); + expect(output).not.toContain('scss/bestax'); + expect(output).not.toContain('react-bulma-components'); + }); + + it('drops the RBC line entirely (no second root) with an existing @use root in bulma mode', () => { + const source = [ + "@use 'bulma/sass';", + "@import 'react-bulma-components/src/index.sass';", + ].join('\n'); + const { output } = run('main.scss', source, 'bulma'); + const bulmaRoots = (output ?? '').match(/@use '[^']*bulma\/sass'/g) ?? []; + expect(bulmaRoots).toHaveLength(1); + expect(output).not.toContain('extras'); + expect(output).not.toContain('react-bulma-components'); + }); + + it('keep mode under --no-deps does not claim the package was removed', () => { + const todos: TodoEntry[] = []; + const output = transformStyles( + 'main.scss', + "@import 'react-bulma-components/src/index.sass';\n", + { add: entry => todos.push(entry) }, + { cssMode: 'keep', deps: false } + ); + expect(output).toContain("@use 'bulma/sass';"); + expect(output).not.toContain('is removed'); + expect(output).not.toContain('entry is removed'); + expect(todos[0].message).not.toContain('removed from dependencies'); + expect(todos[0].message).toContain('--no-deps'); + }); + + it('bulma mode: rewrites to plain bulma/sass with no extras', () => { + const { output } = run( + 'main.scss', + "@import 'react-bulma-components/src/index.sass';\n", + 'bulma' + ); + expect(output).toContain("@use 'bulma/sass';"); + expect(output).not.toContain('extras'); + }); + + it('keep mode: still replaces the dead import, with an accurate TODO', () => { + const { output, todos } = run( + 'main.scss', + "@import 'react-bulma-components/src/index.sass';\n", + 'keep' + ); + expect(output).toContain("@use 'bulma/sass';"); + expect(output).toContain('TODO(bestax-migrate)'); + expect(output).not.toContain('@import'); + expect(todos[0].message).toContain('removed from dependencies'); + expect(todos[0].message).not.toContain('third-party'); + }); + + it('drops the now-redundant import when a real bulma root import already exists', () => { + const source = [ + "@import 'bulma/bulma';", + "@import 'react-bulma-components/src/index.sass';", + ].join('\n'); + const { output } = run('main.scss', source); + const bulmaUses = (output ?? '').match(/@use '[^']*bulma\/sass'/g) ?? []; + expect(bulmaUses).toHaveLength(1); + expect(output).not.toContain('react-bulma-components'); + }); + + it('flags a dropped RBC deep partial instead of losing its CSS (bulma mode)', () => { + const source = [ + "@import 'bulma/bulma.sass';", + "@import 'react-bulma-components/src/components/navbar.sass';", + ].join('\n'); + const { output, todos } = run('main.scss', source, 'bulma'); + const bulmaRoots = (output ?? '').match(/@use '[^']*bulma\/sass'/g) ?? []; + expect(bulmaRoots).toHaveLength(1); + expect(output).not.toContain('react-bulma-components/src/components'); + expect(output).toContain('stylesheet partial'); + expect(todos.some(t => t.message.includes('partial dropped'))).toBe(true); + }); + + it('flags a second RBC deep partial in bestax mode (root + extras, partial not lost)', () => { + const source = [ + "@import 'react-bulma-components/src/index.sass';", + "@import 'react-bulma-components/src/components/tooltip.sass';", + ].join('\n'); + const { output, todos } = run('main.scss', source); + const bulmaRoots = (output ?? '').match(/@use '[^']*bulma\/sass'/g) ?? []; + expect(bulmaRoots).toHaveLength(1); + const extras = (output ?? '').match(/bestax-bulma\/scss\/extras/g) ?? []; + expect(extras).toHaveLength(1); + expect(output).not.toContain('react-bulma-components/src/components'); + expect(output).toContain('stylesheet partial'); + expect(todos.some(t => t.message.includes('partial dropped'))).toBe(true); + }); + + it('flags an RBC deep partial dropped beside the file’s own @use root (bestax mode)', () => { + const source = [ + "@use 'bulma/sass';", + "@import 'react-bulma-components/src/components/navbar.sass';", + ].join('\n'); + const { output, todos } = run('main.scss', source); + const bulmaRoots = (output ?? '').match(/@use '[^']*bulma\/sass'/g) ?? []; + expect(bulmaRoots).toHaveLength(1); + expect(output).toContain("@use '@allxsmith/bestax-bulma/scss/extras';"); + expect(output).toContain('stylesheet partial'); + expect(todos.some(t => t.message.includes('partial dropped'))).toBe(true); + }); + + it('does not flag the RBC root/index import when it is dropped as redundant', () => { + const source = [ + "@import 'bulma/bulma';", + "@import 'react-bulma-components/src/index.sass';", + ].join('\n'); + const { output, todos } = run('main.scss', source, 'bulma'); + expect(output).not.toContain('stylesheet partial'); + expect(todos.some(t => t.message.includes('partial dropped'))).toBe( + false + ); + }); + + it('still detects a genuine bulma-* extension alongside the RBC stylesheet', () => { + const source = [ + "@import 'react-bulma-components/src/index.sass';", + "@import 'bulma-checkradio/dist/css/bulma-checkradio.min.css';", + ].join('\n'); + const { output, todos } = run('main.scss', source); + expect(output).toContain('bulma-checkradio is a Bulma 0.9-era extension'); + expect(todos.some(t => t.message.includes('bulma-checkradio'))).toBe( + true + ); + }); + }); }); describe('transformStyles (.sass indented syntax)', () => { diff --git a/bestax-migrate/src/sources/react-bulma-components/styles.ts b/bestax-migrate/src/sources/react-bulma-components/styles.ts index 223c79f4b..be21ffe6b 100644 --- a/bestax-migrate/src/sources/react-bulma-components/styles.ts +++ b/bestax-migrate/src/sources/react-bulma-components/styles.ts @@ -68,10 +68,50 @@ const OTHER_BULMA_IMPORT = /^\s*@import\s+['"][^'"]*bulma[^'"]*['"]/; /** * Third-party Bulma extension packages (`bulma-checkradio`, `bulma-switch`, * …) — 0.9-era add-ons whose v1 compatibility varies; several are covered - * by the bestax extras. + * by the bestax extras. The `bulma-` package name must start at a specifier + * segment boundary (right after the quote/`~`, or after a `/`) so this does + * not also match a name that merely *contains* `bulma-`, like + * `react-bulma-components` (see EXTENSION_IMPORT's own regression test). */ const EXTENSION_IMPORT = - /^\s*@import\s+['"][^'"]*\bbulma-([\w-]+?)(?:\/|\.|['"])/; + /^\s*@import\s+['"](?:~|[^'"]*\/)?bulma-([\w-]+?)(?:\/|\.|['"])/; + +/** + * The source library's own stylesheet. This is not a third-party extension: + * it is the library being migrated away from, and `deps.ts` removes its + * package.json entry in the same run, so leaving the import in place breaks + * the Sass build. Any `react-bulma-components/…` specifier is dead the same + * way — the documented v3 setup paths (the bundled CSS, the "advanced" Sass + * entry point), but also deep partials and extensionless forms — so, like + * `transform.ts`, we match on the package prefix rather than enumerating + * shapes. The prefix may be bare, `~`-prefixed, or a relative node_modules + * path (the Parcel form `ROOT_IMPORT` also supports). + */ +const RBC_STYLE_IMPORT = + /^(\s*)@import\s+(['"])((?:\.\.?\/)+node_modules\/|~)?react-bulma-components\/[^'"]*\2\s*;?\s*$/; + +/** + * The subset of RBC stylesheet imports that are the library's own *root*: the + * bare package specifier, the documented Sass entry point (`src/index`), and + * the bundled v3 CSS (`dist/react-bulma-components(.min).css`). These pull in + * the whole library, which `bulma/sass` supersedes — so when the file already + * has a Bulma root, dropping one of these loses nothing. A deep RBC partial + * (`src/components/navbar.sass`) is NOT a root: `bulma/sass` doesn't carry a + * given partial's styles, so removing one silently would drop CSS. We flag + * those instead (the package's "never a silent skip" rule). + */ +const RBC_ROOT_STYLESHEET = + /^\s*@import\s+(['"])(?:(?:\.\.?\/)+node_modules\/|~)?react-bulma-components(?:\/src\/index(?:\.s[ac]ss)?|\/dist\/react-bulma-components(?:\.min)?\.css)?\1\s*;?\s*$/; + +/** + * A Bulma module root already pulled in via `@use` — the file's own + * `@use 'bulma/sass'` (any prefix, configured or not) or the bestax bundle + * (`scss/bestax`, which itself loads `bulma/sass with (…)`). When one is + * present we must not emit a second root: a duplicate `bulma/sass` namespace, + * or reconfiguring an already-loaded module, is a hard Sass error. + */ +const USE_BULMA_ROOT = + /^\s*@use\s+(['"])(?:~|(?:\.\.?\/)+node_modules\/)?(?:bulma\/sass|@allxsmith\/bestax-bulma\/(?:src\/)?scss\/bestax)\1/; /** Any `$name: value;` declaration (with or without `!default`). */ const VAR_DECL = /^\s*\$([\w-]+)\s*:\s*(.+?)\s*(!default)?\s*;\s*$/; @@ -204,13 +244,26 @@ export const transformStyles: StylesTransform = ( let changed = false; let extrasAdded = source.includes('@allxsmith/bestax-bulma/scss'); - // Pass 1: find safe leading variable overrides and the first root import. + // Pass 1: locate the file's Bulma root and the safe leading variable + // overrides that fold into it. A real `bulma/…` @import root wins; otherwise + // the RBC stylesheet, which we rewrite into a root the same way — so a file + // that starts from RBC and one that starts from a bulma @import converge on + // the identical shape. A pre-existing `@use` root means we create no new one. const rootImportIndex = lines.findIndex(line => ROOT_IMPORT.test(line)); + const rbcRootIndex = lines.findIndex( + line => RBC_STYLE_IMPORT.test(line) && !line.includes(TODO) + ); + const hasUseBulmaRoot = lines.some(line => USE_BULMA_ROOT.test(line)); + const foldTargetIndex = hasUseBulmaRoot + ? -1 + : rootImportIndex !== -1 + ? rootImportIndex + : rbcRootIndex; const foldableVars: Array<{ index: number; name: string; value: string }> = []; const unsafeVarLines: number[] = []; - if (rootImportIndex !== -1) { - for (let i = 0; i < rootImportIndex; i += 1) { + if (foldTargetIndex !== -1) { + for (let i = 0; i < foldTargetIndex; i += 1) { const line = lines[i]; const match = line.match(VAR_DECL); if (match && isFoldableValue(match[2])) { @@ -222,46 +275,58 @@ export const transformStyles: StylesTransform = ( } const folded = new Set(foldableVars.map(v => v.index)); + /** + * Emit a Bulma module root at `indent` (folding the leading var overrides + * gathered above into `with (…)`, flagging any unsafe ones), then, in + * bestax mode, the extras. `prefix` is a preserved relative-path prefix + * (raw-file toolchains resolve paths, not package specifiers) or ''. + */ + const emitBulmaRoot = ( + indent: string, + prefix: string, + lineNo: number + ): void => { + const bulmaSass = `${prefix}bulma/sass`; + if (foldableVars.length > 0) { + out.push(`${indent}@use '${bulmaSass}' with (`); + foldableVars.forEach(({ name, value }, index) => { + const comma = index < foldableVars.length - 1 ? ',' : ''; + out.push(`${indent} $${name}: ${formatFoldedValue(value)}${comma}`); + }); + out.push(`${indent});`); + } else { + out.push(`${indent}@use '${bulmaSass}';`); + } + if (unsafeVarLines.length > 0) { + out.push( + `${indent}// ${TODO}: the variable override(s) above use computed values; move them into the @use "bulma/sass" with (…) configuration by hand` + ); + report( + collector, + filePath, + lineNo, + 'sass', + 'variable overrides with computed values could not be folded into `with (…)`' + ); + } + if (cssMode === 'bestax' && !extrasAdded) { + out.push( + prefix + ? `${indent}@use '${prefix}@allxsmith/bestax-bulma/src/scss/extras';` + : `${indent}${EXTRAS_USE}` + ); + extrasAdded = true; + } + }; + for (let i = 0; i < lines.length; i += 1) { const line = lines[i]; if (i === rootImportIndex) { const rootMatch = line.match(ROOT_IMPORT); - const indent = rootMatch?.[1] ?? ''; - const bulmaSass = `${keptPrefix(rootMatch?.[3])}bulma/sass`; - if (foldableVars.length > 0) { - out.push(`${indent}@use '${bulmaSass}' with (`); - foldableVars.forEach(({ name, value }, index) => { - const comma = index < foldableVars.length - 1 ? ',' : ''; - out.push(`${indent} $${name}: ${formatFoldedValue(value)}${comma}`); - }); - out.push(`${indent});`); - } else { - out.push(`${indent}@use '${bulmaSass}';`); - } - if (unsafeVarLines.length > 0) { - out.push( - `${indent}// ${TODO}: the variable override(s) above use computed values; move them into the @use "bulma/sass" with (…) configuration by hand` - ); - report( - collector, - filePath, - i + 1, - 'sass', - 'variable overrides with computed values could not be folded into `with (…)`' - ); - } - if (cssMode === 'bestax' && !extrasAdded) { - // A path-prefixed root import means the toolchain resolves raw file - // paths, not package specifiers — point at the shipped file directly. - const extrasPrefix = keptPrefix(rootMatch?.[3]); - out.push( - extrasPrefix - ? `${indent}@use '${extrasPrefix}@allxsmith/bestax-bulma/src/scss/extras';` - : `${indent}${EXTRAS_USE}` - ); - extrasAdded = true; - } + // A path-prefixed root import means the toolchain resolves raw file + // paths, not package specifiers — point at the shipped files directly. + emitBulmaRoot(rootMatch?.[1] ?? '', keptPrefix(rootMatch?.[3]), i + 1); changed = true; continue; } @@ -320,6 +385,85 @@ export const transformStyles: StylesTransform = ( continue; } + const rbcStyle = line.match(RBC_STYLE_IMPORT); + if (rbcStyle && !line.includes(TODO)) { + const indent = rbcStyle[1]; + const prefix = keptPrefix(rbcStyle[3]); + if (i === rbcRootIndex && rootImportIndex === -1 && !hasUseBulmaRoot) { + // The library's own stylesheet never resolves post-migration (deps.ts + // removes react-bulma-components in the same run), and it is this + // file's only Bulma root, so rewrite it into one — the same shape the + // @import root path emits, folding any leading var overrides. Emitting + // a real `bulma/sass` (+ extras) rather than the hard-configured + // bestax bundle keeps this convergent with the @import path and avoids + // reconfiguring `bulma/sass` if a themed root exists elsewhere. + emitBulmaRoot(indent, prefix, i + 1); + if (cssMode === 'keep') { + // The replacement is unavoidable even in keep mode: RBC's own + // stylesheet is a v3 (Bulma 0.9) asset the migrated v1 components + // can't use. What changed depends on whether the manifest step ran. + const depsRan = options.deps !== false; + const detail = depsRan + ? 'its package.json entry is removed, so the old import no longer resolves; install bulma@^1' + : 'it targets Bulma 0.9, not the v1 your components now use; --no-deps kept the dependency, so install bulma@^1 alongside it'; + out.push( + `${indent}// ${TODO}: replaced react-bulma-components's own stylesheet with @use 'bulma/sass' — ${detail} — see ${GUIDE}` + ); + report( + collector, + filePath, + i + 1, + 'sass', + depsRan + ? "replaced react-bulma-components's stylesheet import with bulma/sass; the package is removed from dependencies, so the old import would not resolve" + : "replaced react-bulma-components's stylesheet import with bulma/sass; it is a Bulma 0.9 asset the migrated v1 components can't use (--no-deps left the package in place)" + ); + } else { + // bestax/bulma modes rewrite the file's root without a TODO — still + // report it so the migration summary reflects the restructured root. + report( + collector, + filePath, + i + 1, + 'sass', + "replaced react-bulma-components's stylesheet import with a Bulma v1 @use root" + ); + } + } else { + // A Bulma root already covers this file (a `bulma/…` @import we + // convert, the file's own `@use` root, or an earlier RBC line we + // rewrote). In bestax mode keep that root and add only the extras — + // never a second root, never the configured bundle that would + // reconfigure an already-loaded module. + if (cssMode === 'bestax' && !extrasAdded) { + out.push( + prefix + ? `${indent}@use '${prefix}@allxsmith/bestax-bulma/src/scss/extras';` + : `${indent}${EXTRAS_USE}` + ); + extrasAdded = true; + } + // Dropping the RBC line is only safe for its root/index stylesheet, + // which `bulma/sass` supersedes. A deep RBC partial can carry styles + // `bulma/sass` doesn't, so removing it silently would lose CSS — flag + // it with a TODO + report entry instead (never a silent skip). + if (!RBC_ROOT_STYLESHEET.test(line)) { + out.push( + `${indent}// ${TODO}: dropped a react-bulma-components stylesheet partial that has no Bulma v1 root equivalent; port any styles it carried beyond Bulma's own by hand — see ${GUIDE}` + ); + report( + collector, + filePath, + i + 1, + 'sass', + 'react-bulma-components stylesheet partial dropped; port any styles it carried beyond Bulma’s own by hand' + ); + } + } + changed = true; + continue; + } + if (OTHER_BULMA_IMPORT.test(line) && !line.includes(TODO)) { const extension = line.match(EXTENSION_IMPORT); if (extension) { diff --git a/bestax-migrate/src/types.ts b/bestax-migrate/src/types.ts index 703cffdb2..4a04bd3fd 100644 --- a/bestax-migrate/src/types.ts +++ b/bestax-migrate/src/types.ts @@ -101,6 +101,13 @@ export type CssMode = 'bestax' | 'bulma' | 'keep'; export interface TransformOptions { collector?: TodoCollector; cssMode?: CssMode; + /** + * Whether the run also updates package.json (false under `--no-deps`). + * The stylesheet transform's messaging depends on it: it may only claim a + * dependency was removed when the manifest step actually runs. Undefined is + * treated as the CLI default (deps on). + */ + deps?: boolean; [key: string]: unknown; } diff --git a/docs/docs/guides/getting-started/migration/react-bulma-components.md b/docs/docs/guides/getting-started/migration/react-bulma-components.md index ea0c57bca..355761436 100644 --- a/docs/docs/guides/getting-started/migration/react-bulma-components.md +++ b/docs/docs/guides/getting-started/migration/react-bulma-components.md @@ -62,8 +62,16 @@ in place: `@allxsmith/bestax-bulma/bestax.css` bundle (Bulma v1 + the bestax extras); SCSS files move from Bulma 0.9's `@import 'bulma/bulma'` + `$var !default` overrides to `@use 'bulma/sass' with ($var: …)` plus `@use '@allxsmith/bestax-bulma/scss/extras'`, and - the dead `_all` partial paths map onto the v1 module tree. Computed variables and - indented-syntax `.sass` files are flagged instead of guessed at. + the dead `_all` partial paths map onto the v1 module tree. react-bulma-components' own + stylesheet (any `react-bulma-components/…` specifier — the v3 entry points bare, `~`- or + relative-`node_modules`-prefixed, plus deep partials) gets the same treatment — it's the + library being removed, not a third-party extension, so it is rewritten into that identical + `@use 'bulma/sass'` (+ extras) root, folding any leading `$var` overrides, rather than the + hard-configured `scss/bestax` bundle that couldn't take them. If the file already has its + own Bulma root the redundant root/index line is dropped (the extras still added once); a + dropped RBC **deep partial** is flagged with a TODO instead, since `bulma/sass` may not + carry its styles. Computed variables and indented-syntax `.sass` files are flagged instead + of guessed at. - **Dependencies** — the nearest `package.json` drops `react-bulma-components`, gains `@allxsmith/bestax-bulma`, moves `bulma` to `^1.0.4`, and swaps the dead `node-sass` for dart `sass`. The codemod never runs an install — do that yourself afterwards. @@ -125,8 +133,10 @@ npx skills add https://github.com/allxsmith/bestax --skill bestax-migrate The codemod maps the react-bulma-components **v4** API. v3-era patterns are detected and flagged rather than converted: bundled-CSS imports (`react-bulma-components/dist/react-bulma-components.min.css`) are rewritten to the Bulma v1 -stylesheet, and deep `react-bulma-components/lib/components/*` imports get a TODO pointing you -at the v4 named-import style first. +stylesheet, its Sass equivalent (`react-bulma-components/src/index.sass`, bare or +`~`-prefixed) is rewritten the same way in SCSS files, and deep +`react-bulma-components/lib/components/*` imports get a TODO pointing you at the v4 +named-import style first. ## Coming from a different library? diff --git a/skills/bestax-migrate/references/css-migration.md b/skills/bestax-migrate/references/css-migration.md index 86bdaed15..6882ea802 100644 --- a/skills/bestax-migrate/references/css-migration.md +++ b/skills/bestax-migrate/references/css-migration.md @@ -43,6 +43,31 @@ finish what it flagged. **dart-sass ≥ 1.79** — the codemod's node-sass replacement installs that, but check bundler-pinned older versions (Parcel's sass transformer pins 1.66). + react-bulma-components' own stylesheet — any `react-bulma-components/…` specifier, + bare, `~`-prefixed, or a relative `node_modules/` path, covering the documented v3 + entry points (`src/index.sass`, `dist/react-bulma-components(.min).css`) as well as + deep partials and extensionless forms — is not a third-party extension; it's the + library being migrated away from. It targets Bulma 0.9, not the v1 your components now + use, and (unless `--no-deps` is passed) `package.json` no longer lists it, so the + import can never resolve once installed. Every `--css` mode rewrites it into a real + Bulma root rather than leaving a known-broken import — the **same shape the + `@import 'bulma/…'` root path emits**, so a file that starts from RBC and one that + starts from a Bulma import converge: `bestax` (default) emits + `@use 'bulma/sass';` (folding any leading `$var` overrides above the import into + `with (…)`) plus `@use '@allxsmith/bestax-bulma/scss/extras';`; `bulma` emits plain + `@use 'bulma/sass';`; `keep` does the same with a TODO explaining the replacement + (worded to match whether `--no-deps` kept the package). It deliberately does **not** + emit the hard-configured `scss/bestax` bundle here — that bundle can't carry the + user's own theme vars, and reconfiguring `bulma/sass` when another file already + configures it is a hard Sass error. If the file already has its own `bulma/…` root + (a `@use 'bulma/sass'` of its own, or a Bulma `@import` the codemod converts), the RBC + **root/index** line (`src/index`, the bundled `dist/*.css`, or the bare specifier) is + dropped instead of emitting a second root; in `bestax` mode the extras are added once + alongside that existing root. A dropped RBC **deep partial** (e.g. + `src/components/navbar.sass`) is different — `bulma/sass` doesn't necessarily carry a + given partial's styles, so it gets a `// TODO(bestax-migrate)` and a report entry + ("port any styles it carried beyond Bulma's own by hand") rather than vanishing silently. + - **package.json**: `react-bulma-components` removed, `@allxsmith/bestax-bulma` added, `bulma` bumped to `^1.0.4` (or added when sources still import `bulma/…` directly), and dead `node-sass` replaced with dart `sass`. Run the package manager's install @@ -68,6 +93,8 @@ elements,form,components,grid,layout,helpers,themes}` with leaf partials like compatibility. Class-based usage (`className="is-checkradio"`) keeps needing the extension; usage that migrated to bestax components (Radio, Checkbox, the advanced form controls) is already styled by the bestax extras, so the import can go. + react-bulma-components' own stylesheet is never flagged this way (see above) — only + packages actually named `bulma-*` are. ## Choosing a CSS flavor (optional)