Skip to content

feat(gamut-codemods): add @skillsoft/gamut-codemods with a scope-swap preset - #28

Draft
dreamwasp wants to merge 7 commits into
cass-gmt-1795from
cass-gmt-1790
Draft

dreamwasp wants to merge 7 commits into
cass-gmt-1795from
cass-gmt-1790

Conversation

@dreamwasp

@dreamwasp dreamwasp commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #18 (cass-gmt-1795).

Warning

b1b91a74f adds cass-gmt-1795 to preview.yml's pull_request.branches so this PR can publish a pkg.pr.new preview. It's temporary. Revert it before either branch ships to main.

What this adds

@skillsoft/gamut-codemods, a jscodeshift CLI for moving consumers from @codecademy/gamut* to @skillsoft/gamut*:

npx @skillsoft/gamut-codemods scope-swap .

The scope-swap preset:

  • renames every module string: imports, exports, require, import(), jest.mock/vi.mock, declare module, and import('x').T, plus exact package-name strings like transpilePackages, with one warning per file
  • moves Video/VideoProps imports to @skillsoft/gamut/Video
  • maps /dist/ deep imports to public entry points, and warns where there isn't one
  • replaces @codecademy/gamut-kit in package.json and in Module Federation shared config
  • updates the ESLint plugin prefix in directive comments and .eslintrc(.json)

It refuses to run on a dirty tree unless you pass --force. It ends with a leftovers report and a next-steps checklist built from what actually fired.

The package README covers usage and how to contribute.

Structure

  • Presets are one upgrade each. A preset has a data manifest plus an ordered list of reusable migrations, and later releases get their own presets.
  • Most changes are data: a manifest row plus a fixture.
  • String swaps are splice edits on the original text, not recast prints. In the spike, recast reprinted neighbouring statements in semicolon-less files and changed JSX text ({title} - {description} became {title}- {description}).
  • manifest.test.ts type-checks every export a manifest points at against the built packages. It also checks that the target versions match the changesets fixed group.

Also in here

  • gamut-codemods, gamut-illustrations, and gamut-tests are added to the changesets fixed group, so every consumer-facing package shares one version. All of them are currently on 0.0.1.
  • There's a changeset: @skillsoft/gamut-codemods minor. Because of the fixed group, that bumps the whole group.

⚠️ Breaking changes

This branch intentionally includes breaking changes. In d0693b15e, @skillsoft/gamut ships the Menu and SelectDropdown renames that upstream GMT-1740 held back (Codecademy/gamut 009aa43f6, "move Menu/elements and IconOptionComponent renames to a separate PR"):

Old (dist/Menu/elements, dist/Form/SelectDropdown/elements) New (@skillsoft/gamut root)
List, ListProps MenuList, MenuListProps
ListItem, ListItemProps MenuListItem, MenuListItemProps
ListLink, ListLinkProps MenuListLink, MenuListLinkProps
ListButton MenuListButton
IconOption (the component) IconOptionComponent
  • MenuToolTipWrapper also becomes public.
  • The IconOption type is unchanged.
  • The names had to change because List and IconOption would collide with the public List component and IconOption type on the root.
  • The root export snapshot adds exactly 6 runtime names.
  • The changeset is a minor, following CONTRIBUTING's 0.x rule, and includes a migration table.

The scope-swap codemod handles this for consumers. It keeps local names (import { List } becomes import { MenuList as List }), and manifest.test.ts type-checks the new names against the built package.

ButtonBase stays private. That was a separate decision in GMT-1740, not a rename, so the codemod still warns on dist/ButtonBase/ButtonBase deep imports.

Fixes from a type-checked run against mono

These come from a test run of the @28 preview against mono (8f6f3ee5c, 1bebf4340, 658a4df14).

@skillsoft/gamut:

  • @skillsoft/gamut/Video failed under TypeScript's moduleResolution: "node". A Video/package.json stub fixes it, the same approach upstream uses. attw's no-resolution ignore, which was hiding the problem, is removed.
  • New root exports: formFieldStyles, formFieldPaddingStyles, and conditionalStyles (mono's Recurly fields use them), plus the HTMLToReactNode and ButtonBaseProps types. The rest of Form/styles stays internal.

Codemod:

  • package-json no longer overwrites @skillsoft/* entries that are already listed, such as preview URLs. It had been writing ^0.0.1, which 404s. * peer ranges now stay *.
  • Deep-import rows that are only partly public now list their public names (onlyNames). A new test fails if any row could rewrite a name its target doesn't export.
  • New migrations:
    • mdx-imports: import/export statements in Storybook .mdx files.
    • yarnrc: .yarnrc.yml package lists.
    • tsconfig-dom: a warning when lib has no "dom". Video used to supply the DOM types through the root import.
  • The README explains how to test against preview builds, using resolutions to avoid duplicate copies.

Easier install (46ad288ed)

Consumers now need only react, react-dom, @emotion/react, and @emotion/styled alongside @skillsoft/gamut. Before, they also had to add @emotion/cache, lodash, stylis, and typescript by hand.

I checked this by installing packed tarballs into a fresh yarn project. The only peer warning left is yarn's implicit @types/react.

What changed:

  • gamut-styles: stylis, lodash, and @emotion/cache are dependencies instead of peers.
  • gamut-icons: the unused lodash peer is gone.
  • variance: the typescript peer is optional.
  • @skillsoft packages depend on each other with ^ ranges, so installs share one gamut-styles (one theme context).
  • gamut-tests: component-test-setup is ^0.3.1 instead of *.
  • gamut: @types/marked moved to devDependencies.

Third-party exact pins stay as they are. Loosening them re-resolved react-aria-components from 1.7.1 to 1.21.1, and that upgrade should be tested separately.

Verification

  • yarn nx test gamut: 1,276 tests pass, with the export snapshot updated.
  • yarn nx test gamut-codemods: 33 tests pass, including fixture-driven tests with an idempotence check and the manifest type-check against built dist.
  • nx verify (tsc), verify-package (publint), prettier, and eslint pass.
  • yarn dedupe --check still fails, but that's already true on the base branch: it reports 129 packages there. I deduped the 13 that jscodeshift added, so this PR is back to that same 129.
  • I ran the built CLI against git archive snapshots of mono (3,443 files changed) and platform (1,056), with 0 parse errors. The dirty-tree guard refused a dirty snapshot. platform's rspack.config.ts came out with six real singletons.
  • Not verified: that consumer repos compile afterwards. I didn't install @skillsoft/* into the snapshots or run tsc there.

🤖 Generated with Claude Code

dreamwasp and others added 2 commits September 24, 2026 14:13
Adds @skillsoft/gamut-codemods, a jscodeshift-based CLI for moving
consumers from @Codecademy/gamut* to @skillsoft/gamut*:

  npx @skillsoft/gamut-codemods scope-swap .

The scope-swap preset rewrites imports, mocks, `declare module`, and
package-name strings; moves Video/VideoProps to @skillsoft/gamut/Video;
maps /dist/ deep imports to public entry points; replaces gamut-kit in
package.json and Module Federation shared config; and updates the ESLint
plugin prefix. It refuses to run on a dirty tree, and ends with a
leftovers report plus a checklist built from what fired.

Structure: presets (one per upgrade) hold a data manifest and an ordered
list of reusable migrations. String swaps are splice edits on the
original text rather than recast prints, since recast reprinted
neighbouring code in semicolon-less files and changed JSX whitespace.

Also adds gamut-codemods, gamut-illustrations, and gamut-tests to the
changesets fixed group so every consumer-facing package shares a
version, which the preset uses for its target versions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lets the @skillsoft/gamut-codemods PR, which targets cass-gmt-1795,
publish installable pkg.pr.new previews. Revert this commit before
either branch ships to main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@skillsoft/eslint-plugin-gamut

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/eslint-plugin-gamut@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/eslint-plugin-gamut@28.tgz

@skillsoft/gamut

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut@28.tgz

@skillsoft/gamut-agent-tools

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-agent-tools@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-agent-tools@28.tgz

@skillsoft/gamut-codemods

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-codemods@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-codemods@28.tgz

@skillsoft/gamut-icons

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-icons@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-icons@28.tgz

@skillsoft/gamut-illustrations

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-illustrations@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-illustrations@28.tgz

@skillsoft/gamut-patterns

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-patterns@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-patterns@28.tgz

@skillsoft/gamut-styles

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-styles@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-styles@28.tgz

@skillsoft/gamut-tests

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-tests@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/gamut-tests@28.tgz

@skillsoft/variance

npm i https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/variance@28
yarn add https://pkg.pr.new/Codecademy/skillsoft-gamut/@skillsoft/variance@28.tgz

commit: 46ad288

dreamwasp and others added 5 commits September 24, 2026 14:42
…the root

Ships the rename upstream GMT-1740 held back (Codecademy/gamut
009aa43, "move Menu/elements and IconOptionComponent renames to a
separate PR"):

- Menu/elements: List* -> MenuList* (List, ListProps, ListItem,
  ListItemProps, ListLink, ListLinkProps, ListButton), and Menu/index
  now re-exports './elements'. MenuToolTipWrapper becomes public too.
- Form/SelectDropdown: the IconOption component -> IconOptionComponent,
  exported from the barrel. The IconOption type is unchanged.

The names changed because List and IconOption collide with the public
List component and IconOption type once they're on the root.

The scope-swap codemod maps both deep imports to the root with the new
names, keeping local bindings (`import { MenuList as List }`), and its
manifest test now type-checks those names against the built package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd .yarnrc.yml

From a scope-swap run against mono:

- package-json no longer overwrites a new package that's already listed.
  Expanding gamut-kit re-added @codecademy/* names and the rename step
  then replaced hand-added @skillsoft/* preview URLs with ^0.0.1, which
  isn't published for most packages, so yarn install 404'd. Expansion
  now checks for the new name too, and existing entries win.
- A `*` peer range stays `*` instead of narrowing to the target version.
- New mdx-imports migration: runs the source migrations over ESM
  import/export statements in .mdx files, skipping fenced code. Stories
  get the scope rename, Video split, and deep-import rewrites.
- New yarnrc migration: renames old names in .yarnrc.yml list items, or
  drops them when a scope glob like '@skillsoft/*' covers the new name.
  Quotes the result, since a YAML plain scalar can't start with `@`.

The source-migration runner moves to lib/run-source.ts so file
migrations can reuse it through a `transformSource` helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nalStyles

For custom inputs that should look like Input, such as third-party
hosted payment fields; mono's Recurly fields import all three from
dist/Form/styles today. The rest of Form/styles stays internal.

The scope-swap codemod now rewrites Form/styles deep imports to the root
when every imported name is one of the three, via a new `onlyNames`
manifest field, and warns otherwise. Deep-import rewrites inside mocks
now always warn, since mocking the root covers the whole package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arget exports

From a type-checked scope-swap run against mono (15 errors left after
the consumer-side fixes, all from these):

gamut
- Add a Video/package.json stub (and ship it) so `@skillsoft/gamut/Video`
  resolves under TypeScript's node10 resolution and bundlers that ignore
  `exports`, the same way upstream does. Drop attw's `no-resolution`
  ignore, which was hiding exactly this.
- Export the HTMLToReactNode and ButtonBaseProps types from the root;
  mono deep-imports both.

gamut-codemods
- Five deep-import rows were sending every name to the root even though
  only some are public there (Box/props, Button/shared,
  Markdown/libs/overrides, SelectDropdown/elements, gamut-icons/props).
  They now list their public names in `onlyNames`, and manifest.test.ts
  fails if any row could send a name its target doesn't export.
- New tsconfig-dom report: warns on `lib` without "dom", which Video used
  to supply through the root import, with a matching next-steps item.
- The gamut-kit next step mentions gamut-styles' stylis peer dependency.
- README: how to test against pkg.pr.new previews (resolutions to avoid
  duplicate copies).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A consumer now needs react, react-dom, @emotion/react, and
@emotion/styled alongside @skillsoft/gamut, down from also installing
@emotion/cache, lodash, stylis, and typescript by hand. Verified by
installing packed tarballs into a fresh yarn project: the only peer
warning left is yarn's implicit @types/react.

- gamut-styles: stylis, lodash, @emotion/cache peer -> dependency.
  stylis only feeds the cache's prefixer, and @emotion/cache already
  ships its own copy, so nothing needs to be shared with the host.
- gamut-icons: drop the lodash peer; nothing imports it.
- variance: mark the typescript peer optional; no runtime import.
- Sibling @SkillSoft deps use ^ ranges instead of exact pins, so a
  consumer on a slightly different patch shares one gamut-styles
  (one theme context) instead of getting two.
- gamut-tests: component-test-setup * -> ^0.3.1.
- gamut: @types/marked -> devDependencies; no published .d.ts uses it.

Third-party exact pins (react-aria-components and friends) stay exact:
loosening them re-resolved react-aria-components 1.7.1 -> 1.21.1, which
is an upgrade to test on its own. The scope-swap checklist drops its
now-unneeded stylis note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant