Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/gamut-codemods-scope-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@skillsoft/gamut-codemods': minor
---

`scope-swap` now renames every `@codecademy/gamut*` package and `eslint-plugin-gamut` to `@skillsoft/*`: imports, exports, `require`, `import()`, `jest.mock`/`vi.mock`, `declare module`, import types, and package names in comments, `package.json`, `.mdx` ESM blocks, and `.yarnrc.yml` lists. `@codecademy/gamut-kit` has no `@skillsoft` replacement, so it's replaced with the packages it bundled, in `package.json` and in Module Federation `shared` config. That makes Gamut a real singleton across host and remotes, so the run ends with a reminder to deploy them together. Dependencies on a newer upstream release than `@skillsoft` has caught up to get a warning.
9 changes: 7 additions & 2 deletions packages/gamut-codemods/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,13 @@ Every skillsoft-gamut pull request publishes installable previews through pkg.pr

### What scope-swap changes

| Migration | What it does |
| --------- | ------------ |
| Migration | What it does |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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. |
| `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, and package names in comments (`/dist` paths and removed packages are left for the leftovers). |
| `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. |
| `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. |

### Known limits

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Box } from '@codecademy/gamut';
import { theme } from '@codecademy/gamut-styles';

export const Thing = () => <Box color={theme.colors.text} />;
export const shared = { '@codecademy/gamut-kit': { singleton: true } };
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Box } from '@skillsoft/gamut';
import { theme } from '@skillsoft/gamut-styles';

export const Thing = () => <Box color={theme.colors.text} />;
export const shared = { '@skillsoft/gamut': { singleton: true }, '@skillsoft/gamut-icons': { singleton: true }, '@skillsoft/gamut-illustrations': { singleton: true }, '@skillsoft/gamut-patterns': { singleton: true }, '@skillsoft/gamut-styles': { singleton: true }, '@skillsoft/variance': { singleton: true } };
31 changes: 27 additions & 4 deletions packages/gamut-codemods/src/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,26 +142,34 @@ describe('main: refusing to write', () => {
});

describe('main: full run', () => {
it('lists leftovers and prints the checklist', async () => {
it('rewrites sources, manifests, and docs, then prints the checklist', async () => {
const dir = project({
'src/Post.tsx': POST,
'docs/story.mdx': "import { Box } from '@codecademy/gamut';\n\n# Hi\n",
'package.json': JSON.stringify({
name: 'app',
dependencies: { '@codecademy/gamut': '^73.0.0' },
dependencies: {
'@codecademy/gamut': '^73.0.0',
'@codecademy/gamut-kit': '^73.0.0',
},
}),
'jest.config.js':
"module.exports = { moduleNameMapper: { '^@codecademy/gamut/(.*)$': '<rootDir>/x/$1' } };\n",
});

expect(await main(['scope-swap', dir, '--force'])).toBe(0);

expect(read(dir, 'src/Post.tsx')).toContain("from '@skillsoft/gamut'");
expect(read(dir, 'docs/story.mdx')).toContain("'@skillsoft/gamut'");
expect(read(dir, 'package.json')).toContain('"@skillsoft/gamut"');
expect(read(dir, 'package.json')).not.toContain('gamut-kit');

const out = output();
expect(out).toMatch(/file\(s\) changed/);
expect(out).toMatch(/leftovers \(\d+\)/);
expect(out).toMatch(/Post\.tsx:1/);
expect(out).toMatch(/package\.json:1/);
expect(out).toMatch(/jest\.config\.js:1/);
expect(out).toMatch(/next steps/);
expect(out).toMatch(/gamut-kit is gone/);
expect(out).toMatch(/Reinstall \(yarn\)/);
});

Expand All @@ -173,6 +181,7 @@ describe('main: full run', () => {
expect(await main(['scope-swap', dir, '--dry'])).toBe(0);
expect(read(dir, 'src/Post.tsx')).toBe(POST);
expect(read(dir, 'package.json')).toBe(pkg);
expect(output()).toMatch(/would update .*package\.json/);
expect(output()).toMatch(/skipped in --dry/);
expect(output()).not.toMatch(/next steps/);
});
Expand All @@ -184,6 +193,20 @@ describe('main: full run', () => {
expect(output()).not.toMatch(/leftovers skipped/);
});

it('runs only the named migrations with --only', async () => {
const dir = project({
'src/Post.tsx': POST,
'package.json': JSON.stringify({
dependencies: { '@codecademy/gamut': '^73.0.0' },
}),
});
expect(
await main(['scope-swap', dir, '--force', '--only=package-json'])
).toBe(0);
expect(read(dir, 'src/Post.tsx')).toBe(POST);
expect(read(dir, 'package.json')).toContain('"@skillsoft/gamut"');
});

it('returns 1 when the source runner reports errors', async () => {
mockRun.mockResolvedValueOnce({ error: 1 });
const dir = project({ 'src/Post.tsx': POST });
Expand Down
7 changes: 4 additions & 3 deletions packages/gamut-codemods/src/__tests__/lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,13 @@ describe('findLeftovers', () => {
"moduleNameMapper: { '^@codecademy/gamut(.*)$': 'x' }",
"'@skillsoft/eslint-plugin-gamut'",
"'@skillsoft/gamut-styles'",
"'@codecademy/gamut-kit'",
].join('\n')
);
expect(findLeftovers([dir], manifest).map((h) => h.line)).toEqual([1]);
expect(findLeftovers([dir], manifest).map((h) => h.line)).toEqual([1, 4]);
});

it('tags every old package the manifest replaces', () => {
it('tags every old package the manifest replaces, including gamut-kit', () => {
const dir = tmp();
const old = [
...Object.keys(manifest.packages),
Expand Down Expand Up @@ -119,7 +120,7 @@ describe('findLeftovers', () => {

it('reads editor config and extensionless scripts', () => {
const dir = tmp();
const line = "require('@codecademy/gamut/package.json');";
const line = "require('@codecademy/gamut-kit/package.json');";
fs.writeFileSync(
path.join(dir, 'sync-deps'),
`#!/usr/bin/env node\n${line}`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Canvas, Meta } from '@storybook/blocks';
import { Box } from '@codecademy/gamut';
import { theme } from '@codecademy/gamut-styles';
import * as Stories from './Card.stories';

<Meta of={Stories} />

# Card

Built on `@codecademy/gamut`'s Box.

```tsx
import { Card } from '@codecademy/gamut';
```

<Canvas of={Stories.Default} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Canvas, Meta } from '@storybook/blocks';
import { Box } from '@skillsoft/gamut';
import { theme } from '@skillsoft/gamut-styles';
import * as Stories from './Card.stories';

<Meta of={Stories} />

# Card

Built on `@codecademy/gamut`'s Box.

```tsx
import { Card } from '@codecademy/gamut';
```

<Canvas of={Stories.Default} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {
Text,

Some prose that never closes the import.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {
Text,

Some prose that never closes the import.
63 changes: 63 additions & 0 deletions packages/gamut-codemods/src/migrations/mdx-imports/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { FileMigration } from '../../lib/types';

/*
MDX files (Storybook docs, mostly) import from Gamut like any module, but
jscodeshift can't parse MDX. This pulls out the top-level ESM statements
(`import ... from '...'`, `export ... from '...'`) and runs each one
through the preset's source migrations, so stories get the same scope
rename, Video split, and deep-import rewrites as .tsx files.

Fenced code blocks are left alone. They're documentation, and a code
sample that still says @codecademy is the leftovers report's job.
*/

const FENCE = /^\s*(```|~~~)/;
const STATEMENT_START = /^(import\s|export\s+(\*|\{|type\s+\{))/;
const STATEMENT_END =
/(from\s*(['"])[^'"\n]*\2|^import\s*(['"])[^'"\n]*\3)\s*;?\s*$/;
/* A statement that runs longer than this isn't one we understand. */
const MAX_STATEMENT_LINES = 50;

export const mdxImports: FileMigration = {
name: 'mdx-imports',
kind: 'file',
description:
'Run the source migrations over import/export statements in .mdx files.',
appliesTo: (file) => file.endsWith('.mdx'),
run({ source, transformSource }) {
const lines = source.split('\n');
const out: string[] = [];
let inFence = false;
let changed = false;

for (let i = 0; i < lines.length; i += 1) {
const line = lines[i];
if (FENCE.test(line)) inFence = !inFence;
if (inFence || !STATEMENT_START.test(line)) {
out.push(line);
continue;
}

let end = i;
while (
end < lines.length - 1 &&
end - i < MAX_STATEMENT_LINES &&
!STATEMENT_END.test(lines.slice(i, end + 1).join('\n'))
) {
end += 1;
}
const statement = lines.slice(i, end + 1).join('\n');
if (!STATEMENT_END.test(statement)) {
out.push(line);
continue;
}

const next = transformSource(statement);
if (next !== statement) changed = true;
out.push(next);
i = end;
}

return changed ? out.join('\n') : null;
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const shared = {
'@codecademy/gamut': { singleton: true, requiredVersion: '^73.0.0' },
'@codecademy/gamut-kit': { singleton: true },
};
export const config = { shared };
export const inline = {
shared: {
'@codecademy/gamut-kit': { eager: true },
'@codecademy/gamut': { singleton: true },
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const shared = {
'@codecademy/gamut': { singleton: true, requiredVersion: '^73.0.0' },
'@codecademy/gamut-icons': { singleton: true },
'@codecademy/gamut-illustrations': { singleton: true },
'@codecademy/gamut-patterns': { singleton: true },
'@codecademy/gamut-styles': { singleton: true },
'@codecademy/variance': { singleton: true },
};
export const config = { shared };
export const inline = {
shared: {
'@codecademy/gamut-icons': { eager: true },
'@codecademy/gamut-illustrations': { eager: true },
'@codecademy/gamut-patterns': { eager: true },
'@codecademy/gamut-styles': { eager: true },
'@codecademy/variance': { eager: true },
'@codecademy/gamut': { singleton: true },
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const key = '@codecademy/gamut';
export const shared = {
[key]: { singleton: true },
123: { singleton: true },
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const key = '@codecademy/gamut';
export const shared = {
[key]: { singleton: true },
123: { singleton: true },
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
plugins: [
new ModuleFederationPlugin({
shared: {
react: { singleton: true },
'@codecademy/gamut-kit': { singleton: true },
'@emotion/react': { singleton: true },
},
}),
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default {
plugins: [
new ModuleFederationPlugin({
shared: {
react: { singleton: true },
'@codecademy/gamut': { singleton: true },
'@codecademy/gamut-icons': { singleton: true },
'@codecademy/gamut-illustrations': { singleton: true },
'@codecademy/gamut-patterns': { singleton: true },
'@codecademy/gamut-styles': { singleton: true },
'@codecademy/variance': { singleton: true },
'@emotion/react': { singleton: true },
},
}),
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["replaced '@codecademy/gamut-kit' in Module Federation shared config"]
Loading