Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
"fixed": [
[
"@skillsoft/gamut-agent-tools",
"@skillsoft/gamut-codemods",
"@skillsoft/gamut-icons",
"@skillsoft/gamut-illustrations",
"@skillsoft/gamut-patterns",
"@skillsoft/gamut-styles",
"@skillsoft/gamut-tests",
"@skillsoft/gamut",
"@skillsoft/variance"
]
Expand Down
18 changes: 18 additions & 0 deletions .changeset/consumer-friendly-deps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@skillsoft/gamut': patch
'@skillsoft/gamut-styles': patch
'@skillsoft/gamut-icons': patch
'@skillsoft/gamut-illustrations': patch
'@skillsoft/gamut-patterns': patch
'@skillsoft/gamut-tests': patch
'@skillsoft/variance': patch
---

Fewer packages to install by hand. `@skillsoft/gamut` now needs only `react`, `react-dom`, `@emotion/react`, and `@emotion/styled` alongside it.

- `@skillsoft/gamut-styles`: `stylis`, `lodash`, and `@emotion/cache` are now regular dependencies instead of peer dependencies.
- `@skillsoft/gamut-icons`: dropped an unused `lodash` peer dependency.
- `@skillsoft/variance`: the `typescript` peer dependency is now optional.
- `@skillsoft/*` packages depend on each other with `^` ranges instead of exact versions, so installs share one copy of `gamut-styles` and `variance`.
- `@skillsoft/gamut-tests`: `component-test-setup` is now `^0.3.1` instead of `*`.
- `@skillsoft/gamut`: `@types/marked` moved to devDependencies. The published types don't reference it.
5 changes: 5 additions & 0 deletions .changeset/gamut-codemods-scope-swap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@skillsoft/gamut-codemods': minor
---

Add `@skillsoft/gamut-codemods` with a `scope-swap` preset for moving a repo from `@codecademy/gamut*` to `@skillsoft/gamut*`. Run `npx @skillsoft/gamut-codemods scope-swap .` from a clean working tree. It renames imports, mocks, and dependencies, moves `Video` imports to `@skillsoft/gamut/Video`, rewrites `/dist/` deep imports, replaces `@codecademy/gamut-kit`, and finishes with a list of what's left to fix by hand.
5 changes: 5 additions & 0 deletions .changeset/gamut-form-field-styles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@skillsoft/gamut': minor
---

Export `formFieldStyles`, `formFieldPaddingStyles`, and `conditionalStyles` from the package root, for custom inputs that should look like `Input`, such as third-party hosted payment fields. The rest of `Form/styles` stays internal. Coming from `@codecademy/gamut/dist/Form/styles`, `npx @skillsoft/gamut-codemods scope-swap .` rewrites imports of these three.
15 changes: 15 additions & 0 deletions .changeset/gamut-menu-selectdropdown-renames.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@skillsoft/gamut': minor
---

**Breaking:** export Menu's list elements and SelectDropdown's option component from the package root, under new names that don't collide with the public `List` component and `IconOption` type.

| Old (`dist/Menu/elements`, `dist/Form/SelectDropdown/elements`) | New (`@skillsoft/gamut`) |
| --------------------------------------------------------------- | ----------------------------------- |
| `List`, `ListProps` | `MenuList`, `MenuListProps` |
| `ListItem`, `ListItemProps` | `MenuListItem`, `MenuListItemProps` |
| `ListLink`, `ListLinkProps` | `MenuListLink`, `MenuListLinkProps` |
| `ListButton` | `MenuListButton` |
| `IconOption` (the component) | `IconOptionComponent` |

`MenuToolTipWrapper` is now exported from the root too. The `IconOption` type is unchanged. Coming from `@codecademy/gamut`, `npx @skillsoft/gamut-codemods scope-swap .` rewrites these imports for you.
6 changes: 6 additions & 0 deletions .changeset/gamut-video-node10-and-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@skillsoft/gamut': patch
---

- `@skillsoft/gamut/Video` now resolves under TypeScript's `moduleResolution: "node"` (node10) and in bundlers that ignore `package.json#exports`, through a `Video/package.json` stub.
- Export the `HTMLToReactNode` and `ButtonBaseProps` types from the package root.
12 changes: 12 additions & 0 deletions .changeset/nice-peaches-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@skillsoft/eslint-plugin-gamut': patch
'@skillsoft/gamut': patch
'@skillsoft/gamut-icons': patch
'@skillsoft/gamut-illustrations': patch
'@skillsoft/gamut-patterns': patch
'@skillsoft/gamut-styles': patch
'@skillsoft/gamut-tests': patch
'@skillsoft/variance': patch
---

Swap Emotion's transform to swc/plugin and explicit type exporting
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ packages/gamut-patterns/src/patterns
packages/code-connect
.nx
packages/gamut-agent-tools/skills/*-workspace
packages/gamut-codemods/**/__testfixtures__
5 changes: 4 additions & 1 deletion .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ name: Preview

on:
pull_request:
branches: [main]
# TEMPORARY: cass-gmt-1795 is here so PRs stacked on it (the
# @skillsoft/gamut-codemods PR) publish previews. Revert this before
# either branch ships to main.
branches: [main, cass-gmt-1795]

env:
NODE_OPTIONS: '--max_old_space_size=8196'
Expand Down
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ packages/gamut-patterns/src/patterns
packages/gamut-styles/**/*.d.ts
packages/styleguide/stories/Core/Atoms/Markdown/*.md
packages/code-connect/**/*
# Codemod fixtures are exact expected output, formatting included.
packages/gamut-codemods/**/__testfixtures__

/.nx/cache
/.nx/workspace-data
1 change: 1 addition & 0 deletions packages/gamut-codemods/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
131 changes: 131 additions & 0 deletions packages/gamut-codemods/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# @skillsoft/gamut-codemods

Codemods for moving a repo onto `@skillsoft/gamut` and between its releases.

## Presets

Each preset is one upgrade. Run them in order if you're more than one behind.

| Preset | What it's for |
| ------------ | ------------------------------------------------------- |
| `scope-swap` | Moving from `@codecademy/gamut*` to `@skillsoft/gamut*` |

`npx @skillsoft/gamut-codemods list` shows every preset and the migrations inside it.

## Running scope-swap

Start from a clean working tree. The codemod won't write into uncommitted changes (pass `--force` to override), so `git checkout .` always undoes a run.

```sh
# Preview without writing anything
npx @skillsoft/gamut-codemods scope-swap . --dry

# Run it
npx @skillsoft/gamut-codemods scope-swap .

# Run only some migrations
npx @skillsoft/gamut-codemods scope-swap . --only=moved-exports,scope-rename
```

A run ends with three things:

1. **Warnings**, prefixed with the migration name, e.g. `[moved-exports] src/Player.test.tsx:12 mock of '@codecademy/gamut' stubs Video/VideoProps`. Each one says what to do.
2. **Leftovers**: every line that still names an old package. These are for you to fix: jest `moduleNameMapper` regexes, tsconfig `paths`, flat ESLint configs, comments, and docs.
3. **Next steps**, built from what actually happened in the run.

### Trying it against a preview build

Every skillsoft-gamut pull request publishes installable previews through pkg.pr.new. To test a migration against one:

1. Add the `@skillsoft/*` packages to your root `package.json` first, pointed at the preview URLs. The codemod keeps any `@skillsoft/*` entry that's already there.
2. Preview packages point at each other through commit-pinned URLs, so without help yarn installs a second copy of `gamut-styles`, `variance`, and so on under `@skillsoft/gamut/node_modules`. Pin every `@skillsoft/*` package you use with `resolutions`:

```json
"resolutions": {
"@skillsoft/gamut": "https://pkg.pr.new/@skillsoft/gamut@<pr>",
"@skillsoft/gamut-styles": "https://pkg.pr.new/@skillsoft/gamut-styles@<pr>",
"@skillsoft/variance": "https://pkg.pr.new/@skillsoft/variance@<pr>"
}
```

Published releases don't need this. The packages share one version through the changesets `fixed` group, so their dependencies on each other line up.

3. Commit, then run the codemod (or pass `--force`).

### What scope-swap changes

| Migration | What it does |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deep-imports` | `@codecademy/gamut/dist/PopoverContainer/types` → `@codecademy/gamut`, and the rest of the promoted deep imports. Menu's `List*` and SelectDropdown's `IconOption` component were renamed on the way to the root (`MenuList*`, `IconOptionComponent`); local names are kept, so `import { List }` becomes `import { MenuList as List }`. `Form/styles` is only partly public (`formFieldStyles`, `formFieldPaddingStyles`, `conditionalStyles`), so imports of anything else from it are left alone with a warning. Also warns on deep imports with no public replacement (`ButtonBase/ButtonBase`) and on ones it doesn't know. |
| `moved-exports` | Moves `Video` and `VideoProps` off the root import onto `/Video`. Warns on namespace imports, `export *`, and mocks that stub `Video`. |
| `mf-shared` | Replaces `@codecademy/gamut-kit` in Module Federation `shared` config with the packages it bundled. Gamut becomes a real singleton, so test the host and remotes together. |
| `eslint-comments` | `// eslint-disable-next-line gamut/x` → `@skillsoft/gamut/x`. |
| `scope-rename` | Every `@codecademy/gamut*` module string becomes `@skillsoft/gamut*`: imports, exports, `require`, `import()`, `jest.mock`/`vi.mock`, `declare module`, and `import('x').T`. Also exact package-name strings elsewhere, like `transpilePackages`, with one warning per file. |
| `package-json` | Renames dependencies and sets their versions. A new package that's already listed keeps its value (a preview URL, say), and a `*` peer range stays `*`. Replaces `@codecademy/gamut-kit` with the individual packages. |
| `eslint-config` | `.eslintrc` / `.eslintrc.json`: plugin name, `plugin:` extends, and rule keys. |
| `mdx-imports` | Runs the migrations above over `import`/`export` statements in `.mdx` files. Fenced code blocks are left alone. |
| `yarnrc` | `.yarnrc.yml` list items, like `npmPreapprovedPackages`: renames old package names, or drops them if a glob like `'@skillsoft/*'` already covers the new one. |
| `tsconfig-dom` | Report only. Warns about `tsconfig` files whose `lib` has no `"dom"`, since Video used to supply the DOM types through the root import. |

### Known limits

- Every file is parsed with the `tsx` parser, so Flow-typed JS won't parse.
- Flat ESLint configs (`eslint.config.*`) and `.eslintrc.js` aren't rewritten. In a flat config the rule prefix is whatever key you register the plugin under, so renaming rule keys blindly would break it. They show up in the leftovers.
- The leftovers report uses regex, not an AST, so expect some false positives.

## Contributing

### How it's put together

```
src/
presets/
index.ts # every preset the CLI can run
<preset>/
index.ts # ordered migrations + next-steps checklist
manifest.ts # the data: what changed in this release
migrations/<name>/
index.ts # a reusable migration
__testfixtures__/ # <case>.input.<ext>, .output.<ext>, optional .warnings.json
lib/ # shared types, module-string finder, leftovers, git guard
transform.ts # jscodeshift entry point
cli.ts, bin.ts
```

Two rules hold it together:

- **Manifests hold the data; migrations hold the code.** Most Gamut changes need a manifest row and a fixture, not a new migration.
- **Manifests use the old package names.** In `scope-swap`, `scope-rename` runs last and swaps the scope, so nothing before it needs to know the new one.

### Three kinds of migration

- **`splice`** (the default choice): uses the AST to find ranges, then edits the original text by offset. Use it for anything that swaps one string for another. recast never prints the file, so nothing else in it moves.
- **`ast`**: mutates the AST, and recast prints the file. Only for structural rewrites like splitting an import. recast can reprint more than you changed: mutating one string literal in a semicolon-less file added semicolons and changed JSX text whitespace in neighbouring statements. Presets must list every `ast` migration before any `splice` one, and `validatePreset` enforces it.
- **`file`**: gets raw text for files its `match()` accepts (package.json, .eslintrc).

### Changing a preset's manifest

1. Add the row to `src/presets/<preset>/manifest.ts`.
2. Add a fixture pair under the migration that handles it, e.g. `src/migrations/moved-exports/__testfixtures__/my-case.input.tsx` and `.output.tsx`.
3. If it should warn, add `my-case.warnings.json`: a list of substrings that must appear in the warnings.
4. `yarn nx test gamut-codemods`.

`manifest.test.ts` type-checks every export a manifest points at against the built packages, so a row can't promise an export that doesn't exist.

### Adding a migration

1. Create `src/migrations/<name>/index.ts` exporting an `AstMigration`, `SpliceMigration`, or `FileMigration` (see `src/lib/types.ts`).
2. Add it to the presets that need it. In `scope-swap`, put it before `scope-rename` if it matches on old names.
3. Add fixtures. They're picked up automatically, and every source migration is also checked for idempotence.
4. If you find a new place a module name can appear, teach `lib/module-strings.ts` rather than special-casing it.
5. If it leaves follow-up work, call `note('<key>')` and add the key to the preset's `conditionalChecklist`.

### Adding a preset

Create `src/presets/<name>/` with a manifest and an `index.ts`, register it in `src/presets/index.ts`, and reuse migrations from `src/migrations`.

### recast gotchas already hit

- Rebuild specifiers instead of mutating them. Changing `imported` on `import { List }` drops the alias, because `imported` and `local` share one source range.
- Clone nodes before moving them into a new declaration, or recast's patcher can crash.
- `insertAfter` and `replace(a, b)` copy the gap after the original node, which can leave a stray blank line. `moved-exports.postPrint` closes that specific gap.
19 changes: 19 additions & 0 deletions packages/gamut-codemods/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** @jest-config-loader ts-node */
/* eslint-disable */
import base from '../../jest.config.base';

export default base('gamut-codemods', {
testEnvironment: 'node',
transform: {
'^.+\\.ts$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},
/* Fixtures are inputs, not tests, and may be deliberately odd code. */
testPathIgnorePatterns: ['node_modules', 'dist', '__testfixtures__'],
setupFiles: [],
setupFilesAfterEnv: [],
});
Loading
Loading