Skip to content

Commit

Permalink
feat: add eds-migrate script for running codemods on major version …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
jeremiah-clothier authored and booc0mtaco committed May 24, 2024
1 parent bac3594 commit 109a0e5
Show file tree
Hide file tree
Showing 16 changed files with 1,844 additions and 5 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

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.

## [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)


### Bug Fixes

* handle more cases ([a7e5830](https://github.com/chanzuckerberg/edu-design-system/commit/a7e5830cf1a831e6df3f79a42e8c49003f11a538))

## [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)


### Features

* add eds-migrate command ([b45061e](https://github.com/chanzuckerberg/edu-design-system/commit/b45061ec8dfb322ac569b842a4b24da91e741c3c))
* **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))

## [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)


Expand Down
14 changes: 14 additions & 0 deletions bin/eds-migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env node

// eslint-disable-next-line import/extensions
require('../lib/bin/eds-migrate.js')
.run()
.then(() => {
process.exit(0);
})
.catch((error) => {
if (error) {
console.log(error);
}
process.exit(1);
});
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chanzuckerberg/eds",
"version": "15.0.0-alpha.10",
"version": "15.0.0-alpha.12",
"description": "The React-powered design system library for Chan Zuckerberg Initiative education web applications",
"author": "CZI <edu-frontend-infra@chanzuckerberg.com>",
"homepage": "https://github.com/chanzuckerberg/edu-design-system",
Expand Down Expand Up @@ -35,10 +35,12 @@
"bin": {
"eds-apply-theme": "bin/eds-apply-theme.js",
"eds-import-from-figma": "bin/eds-import-from-figma.js",
"eds-init-theme": "bin/eds-init.js"
"eds-init-theme": "bin/eds-init.js",
"eds-migrate": "bin/eds-migrate.js"
},
"scripts": {
"build": "yarn build:clean && yarn build:tokens && yarn build:js && yarn copy-fonts-to-lib",
"build": "yarn build:clean && yarn build:tokens && yarn build:js && yarn build:bin && yarn copy-fonts-to-lib",
"build:bin": "tsc -p src/bin/tsconfig.json",
"build:clean": "rm -rf lib/",
"build:tokens": "rm -rf src/tokens-dist/ && node ./style-dictionary.config.js && yarn prettier-tokens-dist",
"build:js": "rollup --config",
Expand Down Expand Up @@ -67,7 +69,7 @@
"plop": "plop component",
"test": "jest",
"test:ci": "yarn run test --ci --coverage && cat ./coverage/lcov.info | codecov",
"types": "tsc --noEmit"
"types": "tsc --noEmit && npm run build:bin -- --noEmit"
},
"size-limit": [
{
Expand Down Expand Up @@ -105,6 +107,8 @@
"react-uid": "^2.3.3",
"style-dictionary": "^3.9.2",
"svg4everybody": "^2.1.9",
"ts-dedent": "^2.2.0",
"ts-morph": "^22.0.0",
"yargs": "^17.7.2"
},
"devDependencies": {
Expand Down
55 changes: 55 additions & 0 deletions src/bin/eds-migrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import chalk from 'chalk';
import { hideBin } from 'yargs/helpers'; // eslint-disable-line import/extensions
import yargs from 'yargs/yargs';
import runMigration, { listMigrations } from './migrate';

export async function run() {
// Set up the command
const args = yargs(hideBin(process.argv))
.command(
['$0 [options]'],
'Run an EDS codemod migration on your source files',
)
.options({
list: {
describe: 'List available migrations',
type: 'boolean',
},
name: {
describe: 'The migration to run',
type: 'string',
},
verbose: {
describe: 'Print additional details for debugging purposes',
type: 'boolean',
},
}).argv;

// @ts-expect-error Typing for args isn't as good as we'd like them to be
const { name, list, verbose: isVerbose } = args;

if (list) {
listMigrations().forEach((migration) => {
console.log(` ${migration}`);
});
} else if (name) {
const migrations = listMigrations();
if (!migrations.includes(name)) {
console.warn(
chalk.red(`Migrate: Oops we do not have a migration named "${name}"!`),
);
console.log('Here is a list of migrations available');
listMigrations().forEach((migration) => {
console.log(` ${migration}`);
});
return;
}
await runMigration(name, { isVerbose });
} else {
console.warn(
chalk.red(
'Migrate: please use --name to specify a migration name or use --list to see the list of available migrations',
),
);
}
}
19 changes: 19 additions & 0 deletions src/bin/migrate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# EDS Migrate

EDS Migrate is a collection of codemods written with [ts-morph](https://ts-morph.com/). It will help you migrate breaking changes & deprecations.

## CLI Integration

The preferred way to run these codemods is via the CLI's `eds-migrate` command.

```
npx eds-migrate --help
```

## Additional Resources

Below are some helpful resources when writing codemodes with ts-morph

- [ts-morph documentation](https://ts-morph.com/)
- [TypeScript AST Viewer](https://ts-ast-viewer.com/#)
- [AST Explorer](https://astexplorer.net/)
27 changes: 27 additions & 0 deletions src/bin/migrate/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
InMemoryFileSystemHost,
Project,
type ImportDeclaration,
} from 'ts-morph';

/**
* Checks if the import declaration is for the design system.
* @returns {boolean} - True if the import is from the design system, false otherwise.
*/
export function isDesignSystemImport(node: ImportDeclaration) {
return node.getModuleSpecifierValue() === '@chanzuckerberg/eds';
}

/**
* Creates an in-memory source file for testing
*/
export function createTestSourceFile(sourceFileText: string) {
const host = new InMemoryFileSystemHost();
const project = new Project({
compilerOptions: undefined,
fileSystem: host,
skipLoadingLibFiles: true,
});

return project.createSourceFile('testFile.tsx', sourceFileText);
}
53 changes: 53 additions & 0 deletions src/bin/migrate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import fs from 'node:fs';
import path from 'node:path';
import { Project } from 'ts-morph';

const MIGRATION_DIR = `${__dirname}/migrations`;

/**
* Lists all the available migrations.
*
* @returns {string[]} Array of migration names.
*/
export function listMigrations() {
return fs
.readdirSync(MIGRATION_DIR)
.filter((fname) => fname.endsWith('.js'))
.map((fname) => fname.slice(0, -3));
}

/**
* Runs the migration specified by name with given options.
*
* @param {string} name - The name of the migration.
* @param {Options} options - Options for the migration.
* @returns {Promise<void>} A Promise that resolves when the migration is complete.
*/
export default async function runMigration(
name: string,
options: { isVerbose?: boolean },
): Promise<void> {
const { isVerbose } = options;

// runMigration is called by a CLI we want the directory
// the command is ran in and not the directory of this file
const tsconfigPath = path.join(process.cwd(), './tsconfig.json');
if (isVerbose) {
console.log(`Using the following tsconfig.json file: ${tsconfigPath}`);
}
const project = new Project({
tsConfigFilePath: path.join(tsconfigPath),
});

const pathToMigration = path.join(MIGRATION_DIR, `${name}.js`);
try {
console.log(`Running the following migration: "${name}"`);
const module = await import(pathToMigration);
// This syntax seems odd to need when the code is packaged
module.default.default(project);
} catch (error) {
console.error('Error importing module:', error);
}

await project.save();
}
Loading

0 comments on commit 109a0e5

Please sign in to comment.