Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4728ba1
feat(gamut-codemods): add the codemods CLI and an empty scope-swap pr…
dreamwasp Oct 20, 2026
5a91398
feat(gamut-codemods): rename packages and replace gamut-kit in scope-…
dreamwasp Oct 20, 2026
b3a27cd
feat(gamut-codemods): move Video imports and add Markdown video overr…
dreamwasp Oct 20, 2026
195e6d6
feat(gamut)!: promote deep imports to the root, renaming Menu and Sel…
dreamwasp Oct 20, 2026
161242f
feat(gamut-codemods): rename the ESLint plugin prefix in scope-swap
dreamwasp Oct 20, 2026
b39bd0c
feat(gamut-codemods): warn about esmExternals: false in next.config
dreamwasp Oct 20, 2026
3f2e1bd
Merge branch 'cass-gmt-1790-deep-imports' into cass-gmt-1790-scope-sw…
dreamwasp Oct 20, 2026
c44953d
Merge branch 'cass-gmt-1790-eslint' into cass-gmt-1790-scope-swap-skill
dreamwasp Oct 20, 2026
199d30f
Merge branch 'cass-gmt-1790-next-config' into cass-gmt-1790-scope-swa…
dreamwasp Oct 20, 2026
4a7dfb6
feat(gamut-agent-tools): add the gamut-scope-swap skill
dreamwasp Oct 20, 2026
e6923f5
fix(gamut-codemods): rename FileMigration.match to appliesTo
dreamwasp Oct 20, 2026
961c09b
Merge branch 'cass-gmt-1790-scaffold' into cass-gmt-1790-scope-rename
dreamwasp Oct 20, 2026
97d492c
fix(gamut-codemods): rename match to appliesTo in this branch's file …
dreamwasp Oct 20, 2026
6673ca8
Merge branch 'cass-gmt-1790-scope-rename' into cass-gmt-1790-video-ma…
dreamwasp Oct 20, 2026
34ad6a1
fix(gamut-codemods): rename match to appliesTo in this branch's file …
dreamwasp Oct 20, 2026
1436118
Merge branch 'cass-gmt-1790-scope-rename' into cass-gmt-1790-deep-imp…
dreamwasp Oct 20, 2026
7d0ce3d
Merge branch 'cass-gmt-1790-scope-rename' into cass-gmt-1790-eslint
dreamwasp Oct 20, 2026
b00216c
fix(gamut-codemods): rename match to appliesTo in this branch's file …
dreamwasp Oct 20, 2026
82bd085
Merge branch 'cass-gmt-1790-scope-rename' into cass-gmt-1790-next-config
dreamwasp Oct 20, 2026
84758da
fix(gamut-codemods): rename match to appliesTo in this branch's file …
dreamwasp Oct 20, 2026
6c622a1
Merge branch 'cass-gmt-1790-video-markdown' into cass-gmt-1790-scope-…
dreamwasp Oct 20, 2026
125cfa7
Merge branch 'cass-gmt-1790-deep-imports' into cass-gmt-1790-scope-sw…
dreamwasp Oct 20, 2026
4b86b7f
Merge branch 'cass-gmt-1790-eslint' into cass-gmt-1790-scope-swap-skill
dreamwasp Oct 20, 2026
e41c57d
Merge branch 'cass-gmt-1790-next-config' into cass-gmt-1790-scope-swa…
dreamwasp Oct 20, 2026
7cf5c68
refactor(gamut-codemods): drop the newer-than-source checklist item
dreamwasp Oct 20, 2026
1afc987
Merge branch 'cass-gmt-1790-scope-rename' into cass-gmt-1790-video-ma…
dreamwasp Oct 20, 2026
6670c23
Merge branch 'cass-gmt-1790-scope-rename' into cass-gmt-1790-deep-imp…
dreamwasp Oct 20, 2026
f14f0ae
Merge branch 'cass-gmt-1790-scope-rename' into cass-gmt-1790-eslint
dreamwasp Oct 20, 2026
413c601
Merge branch 'cass-gmt-1790-scope-rename' into cass-gmt-1790-next-config
dreamwasp Oct 20, 2026
fad1c46
Merge branch 'cass-gmt-1790-video-markdown' into cass-gmt-1790-scope-…
dreamwasp Oct 20, 2026
1d4aa4b
Merge branch 'cass-gmt-1790-deep-imports' into cass-gmt-1790-scope-sw…
dreamwasp Oct 20, 2026
8af554b
Merge branch 'cass-gmt-1790-eslint' into cass-gmt-1790-scope-swap-skill
dreamwasp Oct 20, 2026
8d639e5
Merge branch 'cass-gmt-1790-next-config' into cass-gmt-1790-scope-swa…
dreamwasp Oct 20, 2026
8c0b958
chore(changeset): one changeset per package for Video and Markdown
dreamwasp Oct 20, 2026
34cf28e
Merge branch 'cass-gmt-1790-video-markdown' into cass-gmt-1790-scope-…
dreamwasp Oct 20, 2026
79fe678
chore(changeset): one changeset for the new gamut root exports
dreamwasp Oct 20, 2026
4cd726f
Merge branch 'cass-gmt-1790-deep-imports' into cass-gmt-1790-scope-sw…
dreamwasp Oct 20, 2026
da715ae
Merge branch 'main' into cass-gmt-1790-scaffold
dreamwasp Oct 26, 2026
6d427e3
Merge branch 'cass-gmt-1790-scaffold' into cass-gmt-1790-scope-rename
dreamwasp Oct 26, 2026
f1d2a29
Merge branch 'cass-gmt-1790-scope-rename' into cass-gmt-1790-scope-sw…
dreamwasp Oct 26, 2026
0ac40b1
Merge branch 'main' into cass-gmt-1790-scope-swap-skill
dreamwasp Oct 28, 2026
8b47b2d
chore(changeset): fold per-PR gamut-codemods changesets into one
dreamwasp Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .changeset/gamut-codemods-deep-imports.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/gamut-codemods-eslint.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/gamut-codemods-next-config.md

This file was deleted.

6 changes: 4 additions & 2 deletions .changeset/gamut-codemods-scope-rename.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Add `@skillsoft/gamut-codemods`, a CLI for moving a repo from `@codecademy/gamut

- Renames every `@codecademy/gamut*` package and `eslint-plugin-gamut` to `@skillsoft/*` in imports, exports, `require`, `import()`, `jest.mock`/`vi.mock`, `declare module`, import types, comments, `package.json`, `.mdx` ESM blocks, and `.yarnrc.yml` lists.
- Replaces `@codecademy/gamut-kit`, which has no `@skillsoft` replacement, with the packages it bundled, in `package.json` and Module Federation `shared` config. Gamut is now a singleton across host and remotes, so the run ends with a reminder to deploy them together.
- Moves `Video` and `VideoProps` onto `@skillsoft/gamut/Video`, and adds `iframeOverride` / `videoOverride` to each `<Markdown>` that didn't opt out.
- Rewrites `/dist/` deep imports that have a public replacement to the package root, merging them into an existing import from the same package. Renamed exports keep their local name, so `import { List } from '@codecademy/gamut/dist/Menu/elements'` becomes `import { MenuList as List } from '@skillsoft/gamut'`. Imports with no public replacement, and deep paths in mocks or `require()`, get a warning.
- Moves `Video` and `VideoProps` onto `@skillsoft/gamut/Video`, splitting mixed imports, and adds `iframeOverride` / `videoOverride` to each `<Markdown>` that didn't opt out. Namespace imports, `export *`, and mocks that stub `Video` get a warning instead. A second run is a no-op.
- Warns about `experimental.esmExternals: false` in `next.config.*`. With that flag, Next `require()`s server externals, so `@emotion/styled` and `react-select` default imports fail while Next collects page data. The run ends with a checklist item to remove the flag and any aliases that were working around it.
- Warns about tsconfig files whose `lib` has no `"dom"`, and about dependencies on a newer upstream release than `@skillsoft` has caught up to.
- Updates the ESLint plugin prefix from `gamut/` to `@skillsoft/gamut/` in legacy `.eslintrc` files. Flat configs are left alone, and the run says why.
- Updates the ESLint plugin prefix from `gamut/` to `@skillsoft/gamut/` in legacy `.eslintrc` / `.eslintrc.json` files. Flat configs are left alone, and the run says why.
5 changes: 0 additions & 5 deletions .changeset/gamut-codemods-scope-swap.md

This file was deleted.

6 changes: 6 additions & 0 deletions .changeset/gamut-scope-swap-skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@skillsoft/gamut-agent-tools': minor
'@skillsoft/gamut-codemods': patch
---

Add the `gamut-scope-swap` agent skill. It helps with the `@codecademy/gamut*` to `@skillsoft/gamut*` move alongside the `scope-swap` codemod: it reads the warnings and leftovers, and fixes what the codemod can't rewrite. The codemod README now points to it.
6 changes: 6 additions & 0 deletions packages/gamut-agent-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ The first time you run `gamut plugin install` or `update`, it installs `@skillso

Before large PRs, or when onboarding an existing codebase, run the `gamut-review` skill to audit for Gamut usage — dependencies, setup, import patterns, `styled()` wrapping that bypasses system props, hardcoded colors, bespoke component duplication, and test conventions.

### Moving from `@codecademy/gamut`

If your app still depends on `@codecademy/gamut*`, the `gamut-scope-swap` skill helps with the move to `@skillsoft/gamut*`. It works alongside the [`@skillsoft/gamut-codemods`](../gamut-codemods/README.md) `scope-swap` preset. The codemod does the mechanical rewrite. The skill reads what it reports, including warnings, leftovers, and next steps, and helps with the parts it can't rewrite: deep imports, `Video`, renamed Menu exports, tsconfig `dom`, jest mocks, ESLint plugin keys, Module Federation config, and preview builds. It doesn't run the codemod unless you ask it to.

Install the plugin after the swap. The `gamut` CLI comes from `@skillsoft/gamut`, so that package has to be in the app first.

## The CLI lives here, not in `@skillsoft/gamut`

`@skillsoft/gamut`'s `bin/gamut.mjs` is a thin bootstrap shim: it only knows how to check whether `@skillsoft/gamut-agent-tools` is installed, auto-install it if not (for `install`/`update`), and print help. Once that's resolved, it dynamically imports the actual `install`/`update`/`list`/`remove` command bodies from `cli/commands/plugin/` in *this* package and hands off execution to them.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Breaking changes: `@codecademy/gamut*` → `@skillsoft/gamut*`

Short list of what changed. The codemod handles most of these. Entries marked **manual** need a person. Source: `packages/gamut-codemods/src/presets/scope-swap/manifest.ts`.

## Packages

- Every `@codecademy/*` Gamut package is now `@skillsoft/*`: `gamut`, `gamut-styles`, `gamut-icons`, `gamut-illustrations`, `gamut-patterns`, `gamut-tests`, and `variance`.
- `eslint-plugin-gamut` is now `@skillsoft/eslint-plugin-gamut`.
- `@codecademy/gamut-kit` is removed, with no replacement. Depend on the individual packages it bundled.

## Imports

- `Video` and `VideoProps` moved off the root. Import them from `@skillsoft/gamut/Video`. **Manual** for namespace imports and `export *`.
- Menu's `List`, `ListProps`, `ListItem`, `ListItemProps`, `ListLink`, `ListLinkProps`, and `ListButton` are renamed `MenuList*` and `MenuListButton`. The codemod keeps local names with an alias, e.g. `MenuList as List`.
- SelectDropdown's `IconOption` is renamed `IconOptionComponent`.
- `ButtonBase` is not public. Use `FillButton`, `StrokeButton`, `TextButton`, `IconButton`, or `CTAButton`. **Manual.**
- From `Form/styles`, only `formFieldStyles`, `formFieldPaddingStyles`, and `conditionalStyles` are public. Anything else there is internal. **Manual.**
- Deep `/dist/` imports for these are promoted to the package root: form and select types, `Box`, `Tip`, `Button`, `Markdown` overrides, `AssetProvider`, the icon `types` and `props`, and the variance config types. Any `dist` path not on that list is unmapped. **Manual.**

## Runtime

- Gamut is now a real singleton. Module Federation host and remotes must share the same version, so update each remote's `shared` config and deploy them together. **Manual.**
- Next.js apps that set `experimental.esmExternals: false` can fail to build or prerender on `@emotion/*` or `react-select` default imports. Remove `esmExternals: false` and any `@emotion/*` aliases. If you can't yet, see `NEXT_EMOTION_INTEROP.md` for the fallback. **Manual.**

## Build and tooling

- `Video` used to bring DOM types in through the root import. Add `"dom"` to a tsconfig's `lib` if type-checking fails after the move. **Manual.**
- ESLint rule prefix `gamut/` becomes `@skillsoft/gamut/` in directives and legacy configs. Rule names don't change. Flat configs need the plugin registered under the matching key. **Manual** for flat configs.
- Jest mocks of the root package that stub `Video` should mock `@skillsoft/gamut/Video`. **Manual.**
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Next.js and Emotion default imports

Use this if a Next.js app fails after moving to `@skillsoft/gamut*`.

## Symptoms

- Production build or prerender fails with `Element type is invalid: expected a string ... but got: object`. This often comes from `SelectDropdown`, which uses `react-select`.
- The server fails in `_app` with `c(...) is not a function`.

## Cause

With `experimental.esmExternals: false`, Next `require()`s every server external package. `@skillsoft/gamut` and `@skillsoft/gamut-styles` ship strict ESM `.mjs` files. A default import of a CJS package that sets `__esModule` (`react-select`, `@emotion/styled`, `@emotion/cache`, `@emotion/is-prop-valid`) gets the whole module object instead of the default export.

## Fix: use native ESM externals

1. Remove `experimental.esmExternals: false` from `next.config.js`.
2. Remove any `resolve.alias` entries for `@emotion/is-prop-valid`, `@emotion/styled`, or `@emotion/cache`.
3. Remove `@emotion/*` and `react-select` from `transpilePackages`. Keep `@skillsoft/*` packages there if they ship CSS imports.

Next then loads externals with `import()`, so Node resolves each package's `import` entry and default imports work. To confirm, check that the server bundle emits `import("react-select")` instead of `require("react-select")`.

## Fallback: keep `esmExternals: false`

If the app can't drop `esmExternals: false` yet, point the Emotion packages at their ESM builds and transpile them:

```js
const path = require('path');

const emotionEsm = (pkg, file) =>
path.join(path.dirname(require.resolve(`${pkg}/package.json`)), 'dist', file);

// Inside webpack(config, { isServer }), merge into resolve.alias:
'@emotion/is-prop-valid$': emotionEsm(
'@emotion/is-prop-valid',
'emotion-is-prop-valid.esm.js'
),
'@emotion/styled$': emotionEsm(
'@emotion/styled',
isServer ? 'emotion-styled.esm.js' : 'emotion-styled.browser.esm.js'
),
'@emotion/cache$': emotionEsm(
'@emotion/cache',
isServer ? 'emotion-cache.esm.js' : 'emotion-cache.browser.esm.js'
),
```

Add `@emotion/is-prop-valid`, `@emotion/styled`, `@emotion/cache`, and `react-select` to `transpilePackages`.

Plan to remove this fallback. It only exists to work around the `esmExternals` setting.
Loading
Loading