Skip to content

Commit 109a0e5

Browse files
Jeremiah Clothierbooc0mtaco
Jeremiah Clothier
authored andcommitted
feat: add eds-migrate script for running codemods on major version upgrades (#1951)
Adding a new script which can be executed using `npx eds-migrate --help` to help automatically migrate components with new major version releases.
1 parent bac3594 commit 109a0e5

16 files changed

+1844
-5
lines changed

CHANGELOG.md

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
## [15.0.0-alpha.12](https://github.com/chanzuckerberg/edu-design-system/compare/v15.0.0-alpha.11...v15.0.0-alpha.12) (2024-05-23)
6+
7+
8+
### Bug Fixes
9+
10+
* handle more cases ([a7e5830](https://github.com/chanzuckerberg/edu-design-system/commit/a7e5830cf1a831e6df3f79a42e8c49003f11a538))
11+
12+
## [15.0.0-alpha.11](https://github.com/chanzuckerberg/edu-design-system/compare/v15.0.0-alpha.10...v15.0.0-alpha.11) (2024-05-23)
13+
14+
15+
### Features
16+
17+
* add eds-migrate command ([b45061e](https://github.com/chanzuckerberg/edu-design-system/commit/b45061ec8dfb322ac569b842a4b24da91e741c3c))
18+
* **tokens:** add border-utility-inteactive-secondary tokens ([#1959](https://github.com/chanzuckerberg/edu-design-system/issues/1959)) ([72daa0b](https://github.com/chanzuckerberg/edu-design-system/commit/72daa0b137b674272fe14a23c49bacc0700b2187))
19+
520
## [15.0.0-alpha.10](https://github.com/chanzuckerberg/edu-design-system/compare/v15.0.0-alpha.9...v15.0.0-alpha.10) (2024-05-22)
621

722

bin/eds-migrate.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env node
2+
3+
// eslint-disable-next-line import/extensions
4+
require('../lib/bin/eds-migrate.js')
5+
.run()
6+
.then(() => {
7+
process.exit(0);
8+
})
9+
.catch((error) => {
10+
if (error) {
11+
console.log(error);
12+
}
13+
process.exit(1);
14+
});

package.json

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@chanzuckerberg/eds",
3-
"version": "15.0.0-alpha.10",
3+
"version": "15.0.0-alpha.12",
44
"description": "The React-powered design system library for Chan Zuckerberg Initiative education web applications",
55
"author": "CZI <[email protected]>",
66
"homepage": "https://github.com/chanzuckerberg/edu-design-system",
@@ -35,10 +35,12 @@
3535
"bin": {
3636
"eds-apply-theme": "bin/eds-apply-theme.js",
3737
"eds-import-from-figma": "bin/eds-import-from-figma.js",
38-
"eds-init-theme": "bin/eds-init.js"
38+
"eds-init-theme": "bin/eds-init.js",
39+
"eds-migrate": "bin/eds-migrate.js"
3940
},
4041
"scripts": {
41-
"build": "yarn build:clean && yarn build:tokens && yarn build:js && yarn copy-fonts-to-lib",
42+
"build": "yarn build:clean && yarn build:tokens && yarn build:js && yarn build:bin && yarn copy-fonts-to-lib",
43+
"build:bin": "tsc -p src/bin/tsconfig.json",
4244
"build:clean": "rm -rf lib/",
4345
"build:tokens": "rm -rf src/tokens-dist/ && node ./style-dictionary.config.js && yarn prettier-tokens-dist",
4446
"build:js": "rollup --config",
@@ -67,7 +69,7 @@
6769
"plop": "plop component",
6870
"test": "jest",
6971
"test:ci": "yarn run test --ci --coverage && cat ./coverage/lcov.info | codecov",
70-
"types": "tsc --noEmit"
72+
"types": "tsc --noEmit && npm run build:bin -- --noEmit"
7173
},
7274
"size-limit": [
7375
{
@@ -105,6 +107,8 @@
105107
"react-uid": "^2.3.3",
106108
"style-dictionary": "^3.9.2",
107109
"svg4everybody": "^2.1.9",
110+
"ts-dedent": "^2.2.0",
111+
"ts-morph": "^22.0.0",
108112
"yargs": "^17.7.2"
109113
},
110114
"devDependencies": {

src/bin/eds-migrate.ts

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import chalk from 'chalk';
2+
import { hideBin } from 'yargs/helpers'; // eslint-disable-line import/extensions
3+
import yargs from 'yargs/yargs';
4+
import runMigration, { listMigrations } from './migrate';
5+
6+
export async function run() {
7+
// Set up the command
8+
const args = yargs(hideBin(process.argv))
9+
.command(
10+
['$0 [options]'],
11+
'Run an EDS codemod migration on your source files',
12+
)
13+
.options({
14+
list: {
15+
describe: 'List available migrations',
16+
type: 'boolean',
17+
},
18+
name: {
19+
describe: 'The migration to run',
20+
type: 'string',
21+
},
22+
verbose: {
23+
describe: 'Print additional details for debugging purposes',
24+
type: 'boolean',
25+
},
26+
}).argv;
27+
28+
// @ts-expect-error Typing for args isn't as good as we'd like them to be
29+
const { name, list, verbose: isVerbose } = args;
30+
31+
if (list) {
32+
listMigrations().forEach((migration) => {
33+
console.log(` ${migration}`);
34+
});
35+
} else if (name) {
36+
const migrations = listMigrations();
37+
if (!migrations.includes(name)) {
38+
console.warn(
39+
chalk.red(`Migrate: Oops we do not have a migration named "${name}"!`),
40+
);
41+
console.log('Here is a list of migrations available');
42+
listMigrations().forEach((migration) => {
43+
console.log(` ${migration}`);
44+
});
45+
return;
46+
}
47+
await runMigration(name, { isVerbose });
48+
} else {
49+
console.warn(
50+
chalk.red(
51+
'Migrate: please use --name to specify a migration name or use --list to see the list of available migrations',
52+
),
53+
);
54+
}
55+
}

src/bin/migrate/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# EDS Migrate
2+
3+
EDS Migrate is a collection of codemods written with [ts-morph](https://ts-morph.com/). It will help you migrate breaking changes & deprecations.
4+
5+
## CLI Integration
6+
7+
The preferred way to run these codemods is via the CLI's `eds-migrate` command.
8+
9+
```
10+
npx eds-migrate --help
11+
```
12+
13+
## Additional Resources
14+
15+
Below are some helpful resources when writing codemodes with ts-morph
16+
17+
- [ts-morph documentation](https://ts-morph.com/)
18+
- [TypeScript AST Viewer](https://ts-ast-viewer.com/#)
19+
- [AST Explorer](https://astexplorer.net/)

src/bin/migrate/helpers.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
InMemoryFileSystemHost,
3+
Project,
4+
type ImportDeclaration,
5+
} from 'ts-morph';
6+
7+
/**
8+
* Checks if the import declaration is for the design system.
9+
* @returns {boolean} - True if the import is from the design system, false otherwise.
10+
*/
11+
export function isDesignSystemImport(node: ImportDeclaration) {
12+
return node.getModuleSpecifierValue() === '@chanzuckerberg/eds';
13+
}
14+
15+
/**
16+
* Creates an in-memory source file for testing
17+
*/
18+
export function createTestSourceFile(sourceFileText: string) {
19+
const host = new InMemoryFileSystemHost();
20+
const project = new Project({
21+
compilerOptions: undefined,
22+
fileSystem: host,
23+
skipLoadingLibFiles: true,
24+
});
25+
26+
return project.createSourceFile('testFile.tsx', sourceFileText);
27+
}

src/bin/migrate/index.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import { Project } from 'ts-morph';
4+
5+
const MIGRATION_DIR = `${__dirname}/migrations`;
6+
7+
/**
8+
* Lists all the available migrations.
9+
*
10+
* @returns {string[]} Array of migration names.
11+
*/
12+
export function listMigrations() {
13+
return fs
14+
.readdirSync(MIGRATION_DIR)
15+
.filter((fname) => fname.endsWith('.js'))
16+
.map((fname) => fname.slice(0, -3));
17+
}
18+
19+
/**
20+
* Runs the migration specified by name with given options.
21+
*
22+
* @param {string} name - The name of the migration.
23+
* @param {Options} options - Options for the migration.
24+
* @returns {Promise<void>} A Promise that resolves when the migration is complete.
25+
*/
26+
export default async function runMigration(
27+
name: string,
28+
options: { isVerbose?: boolean },
29+
): Promise<void> {
30+
const { isVerbose } = options;
31+
32+
// runMigration is called by a CLI we want the directory
33+
// the command is ran in and not the directory of this file
34+
const tsconfigPath = path.join(process.cwd(), './tsconfig.json');
35+
if (isVerbose) {
36+
console.log(`Using the following tsconfig.json file: ${tsconfigPath}`);
37+
}
38+
const project = new Project({
39+
tsConfigFilePath: path.join(tsconfigPath),
40+
});
41+
42+
const pathToMigration = path.join(MIGRATION_DIR, `${name}.js`);
43+
try {
44+
console.log(`Running the following migration: "${name}"`);
45+
const module = await import(pathToMigration);
46+
// This syntax seems odd to need when the code is packaged
47+
module.default.default(project);
48+
} catch (error) {
49+
console.error('Error importing module:', error);
50+
}
51+
52+
await project.save();
53+
}

0 commit comments

Comments
 (0)