Skip to content

Commit

Permalink
feat(codemod): Add antd-to-oceanbase-design, obui-to-oceanbase-design…
Browse files Browse the repository at this point in the history
… and techui-to-oceanbase-ui transformer
  • Loading branch information
dengfuping committed Sep 11, 2023
1 parent 8ac1f06 commit 23b8697
Show file tree
Hide file tree
Showing 25 changed files with 6,227 additions and 13,120 deletions.
17 changes: 17 additions & 0 deletions packages/codemod/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# OceanBase Design Codemod

A collection of codemod scripts that help migrate to OceanBase Design using [jscodeshift](https://github.com/facebook/jscodeshift) and [postcss](https://github.com/postcss/postcss).(Inspired by [@oceanbase/codemod](https://github.com/ant-design/codemod-v5))

[![NPM version](https://img.shields.io/npm/v/@oceanbase/codemod.svg?style=flat)](https://npmjs.org/package/@oceanbase/codemod) [![NPM downloads](http://img.shields.io/npm/dm/@oceanbase/codemod.svg?style=flat)](https://npmjs.org/package/@oceanbase/codemod) [![Github Action](https://github.com/oceanbase/design/actions/workflows/ci.yml/badge.svg)](https://github.com/oceanbase/design/actions/workflows/ci.yml)

## Usage

Before run codemod scripts, you'd better make sure to commit your local git changes firstly.

```shell
# Run directly through npx
npx -p @oceanbase/codemod codemod src

# Or run directly through pnpm
pnpm --package=@oceanbase/codemod dlx codemod src
```
43 changes: 43 additions & 0 deletions packages/codemod/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@oceanbase/codemod",
"version": "0.1.0",
"description": "Codemod for OceanBase Design upgrade",
"keywords": [
"oceanbase",
"oceanbase design",
"codemod"
],
"homepage": "https://github.com/oceanbase/oceanbase-design/packages/codemod",
"repository": {
"type": "git",
"url": "[email protected]:oceanbase/design.git"
},
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},
"bin": {
"codemod": "./bin/codemod.js"
},
"scripts": {
"build": "father build"
},
"dependencies": {
"chalk": "^3.0.0",
"find-up": "^4.1.0",
"glob": "^8.0.3",
"is-git-clean": "^1.1.0",
"jscodeshift": "^0.14.0",
"lodash": "^4.17.15",
"read-pkg-up": "^9.1.0",
"semver": "^7.1.3",
"update-check": "^1.5.3",
"yargs-parser": "^21.1.1"
},
"devDependencies": {
"@types/jest": "^29.2.3",
"@types/jscodeshift": "^0.11.5",
"enzyme": "^3.0.0",
"enzyme-to-json": "^3.4.0"
}
}
43 changes: 43 additions & 0 deletions packages/codemod/src/bin/babylon.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"sourceType": "module",
"allowImportExportEverywhere": true,
"allowReturnOutsideFunction": true,
"startLine": 1,
"tokens": true,
"plugins": [
"jsx",
"asyncGenerators",
"bigInt",
"classProperties",
"classPrivateProperties",
"classPrivateMethods",
[
"decorators",
{
"decoratorsBeforeExport": true
}
],
"doExpressions",
"dynamicImport",
"exportDefaultFrom",
"exportExtensions",
"exportNamespaceFrom",
"functionBind",
"functionSent",
"importMeta",
"logicalAssignment",
"nullishCoalescingOperator",
"numericSeparator",
"objectRestSpread",
"optionalCatchBinding",
"optionalChaining",
[
"pipelineOperator",
{
"proposal": "minimal"
}
],
"throwExpressions",
"typescript"
]
}
284 changes: 284 additions & 0 deletions packages/codemod/src/bin/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
/* eslint no-console: 0 */

const path = require('path');
const fs = require('fs');
const os = require('os');

const _ = require('lodash');
const chalk = require('chalk');
const isGitClean = require('is-git-clean');
const updateCheck = require('update-check');
const findUp = require('find-up');
const semver = require('semver');
const { run: jscodeshift } = require('jscodeshift/src/Runner');

const pkg = require('../package.json');
const pkgUpgradeList = require('./upgrade-list');
const { getDependencies } = require('../transforms/utils/marker');

// jscodeshift codemod scripts dir
const transformersDir = path.join(__dirname, '../transforms');

// jscodeshift bin#--ignore-config
const ignoreConfig = path.join(__dirname, './codemod.ignore');

const transformers = [
'antd-to-oceanbase-design',
'obui-to-oceanbase-design',
'techui-to-oceanbase-ui',
];

const dependencyProperties = [
'dependencies',
'devDependencies',
'clientDependencies',
'isomorphicDependencies',
'buildDependencies',
];

async function ensureGitClean() {
let clean = false;
try {
clean = await isGitClean();
} catch (err) {
if (err && err.stderr && err.stderr.toLowerCase().includes('not a git repository')) {
clean = true;
}
}

if (!clean) {
console.log(chalk.yellow('Sorry that there are still some git changes'));
console.log('\n you must commit or stash them firstly');
process.exit(1);
}
}

async function checkUpdates() {
let update;
try {
update = await updateCheck(pkg);
} catch (err) {
console.log(chalk.yellow(`Failed to check for updates: ${err}`));
}

if (update) {
console.log(chalk.blue(`Latest version is ${update.latest}. Please update firstly`));
process.exit(1);
}
}

function getMaxWorkers(options = {}) {
// limit usage for cpus
return options.cpus || Math.max(2, Math.ceil(os.cpus().length / 3));
}

function getRunnerArgs(
transformerPath,
parser = 'babylon', // use babylon as default parser
options = {}
) {
const args = {
verbose: 2,
// limit usage for cpus
cpus: getMaxWorkers(options),
// https://github.com/facebook/jscodeshift/blob/master/src/Runner.js#L255
// https://github.com/facebook/jscodeshift/blob/master/src/Worker.js#L50
babel: false,
parser,
// override default babylon parser config to enable `decorator-legacy`
// https://github.com/facebook/jscodeshift/blob/master/parser/babylon.js
parserConfig: require('./babylon.config.json'),
extensions: ['tsx', 'ts', 'jsx', 'js'].join(','),
transform: transformerPath,
ignorePattern: '**/node_modules',
ignoreConfig,
};

return args;
}

async function run(filePath, args = {}) {
for (const transformer of transformers) {
await transform(transformer, 'babylon', filePath, args);
}
}

async function transform(transformer, parser, filePath, options) {
console.log(chalk.bgGreen.bold('Transform'), transformer);
const transformerPath = path.join(transformersDir, `${transformer}.js`);

const args = getRunnerArgs(transformerPath, parser, {
...options,
});

try {
if (process.env.NODE_ENV === 'local') {
console.log(`Running jscodeshift with: ${JSON.stringify(args)}`);
}

// js part
await jscodeshift(transformerPath, [filePath], args);
} catch (err) {
console.error(err);
if (process.env.NODE_ENV === 'local') {
const errorLogFile = path.join(__dirname, './error.log');
fs.appendFileSync(errorLogFile, err);
fs.appendFileSync(errorLogFile, '\n');
}
}
}

async function upgradeDetect(targetDir, needOBCharts, needCompatible) {
const result = [];
const cwd = path.join(process.cwd(), targetDir);
const { readPackageUp } = await import('read-pkg-up');
const closetPkgJson = await readPackageUp({ cwd });

let pkgJsonPath;
if (!closetPkgJson) {
pkgJsonPath = "we didn't find your package.json";
// unknown dependency property
result.push(['install', '@oceanbase/design', pkgUpgradeList['@oceanbase/design']]);
if (needOBCharts) {
result.push(['install', '@oceanbase/charts', pkgUpgradeList['@oceanbase/charts'].version]);
}

if (needCompatible) {
result.push([
'install',
'@ant-design/compatible',
pkgUpgradeList['@ant-design/compatible'].version,
]);
}
} else {
const { packageJson } = closetPkgJson;
pkgJsonPath = closetPkgJson.path;

// dependencies must be installed or upgraded with correct version
const mustInstallOrUpgradeDeps = ['@oceanbase/design', '@oceanbase/icons'];
if (needOBCharts) {
mustInstallOrUpgradeDeps.push('@oceanbase/charts');
}
if (needCompatible) {
mustInstallOrUpgradeDeps.push('@ant-design/compatible');
}

// handle mustInstallOrUpgradeDeps
mustInstallOrUpgradeDeps.forEach(depName => {
let hasDependency = false;
const expectVersion = pkgUpgradeList[depName].version;
// const upgradePkgDescription = pkgUpgradeList[depName].description;
dependencyProperties.forEach(property => {
const versionRange = _.get(packageJson, `${property}.${depName}`);
// mark dependency installment state
hasDependency = hasDependency || !!versionRange;
// no dependency or improper version dependency
if (versionRange && !semver.satisfies(semver.minVersion(versionRange), expectVersion)) {
result.push(['update', depName, expectVersion, property]);
}
});
if (!hasDependency) {
// unknown dependency property
result.push(['install', depName, pkgUpgradeList[depName].version]);
}
});

// dependencies must be upgraded to correct version
const mustUpgradeDeps = _.without(Object.keys(pkgUpgradeList), ...mustInstallOrUpgradeDeps);
mustUpgradeDeps.forEach(depName => {
dependencyProperties.forEach(property => {
const expectVersion = pkgUpgradeList[depName].version;
const versionRange = _.get(packageJson, `${property}.${depName}`);
/**
* we may have dependencies in `package.json`
* make sure that they can `work well` with `oceanbase design system`
* so we check dependency's version here
*/
if (versionRange && !semver.satisfies(semver.minVersion(versionRange), expectVersion)) {
result.push(['update', depName, expectVersion, property]);
}
});
});
}

if (!result.length) {
console.log(chalk.green('Checking passed'));
return;
}

console.log(
chalk.yellow(
"It's recommended to install or upgrade these dependencies to ensure working well with oceanbase design system\n"
)
);
console.log(`> package.json file: ${pkgJsonPath} \n`);
const dependencies = result.map(([operateType, depName, expectVersion, dependencyProperty]) =>
[
_.capitalize(operateType),
`${depName}${expectVersion}`,
dependencyProperty ? `in ${dependencyProperty}` : '',
].join(' ')
);

console.log(dependencies.map(n => `* ${n}`).join('\n'));
}

/**
* options
* --force // force skip git checking (dangerously)
* --cpus=1 // specify cpus cores to use
*/

async function bootstrap() {
const dir = process.argv[2];
// eslint-disable-next-line global-require
const args = require('yargs-parser')(process.argv.slice(3));
if (process.env.NODE_ENV !== 'local') {
// check for updates
await checkUpdates();
// check for git status
if (!args.force) {
await ensureGitClean();
} else {
console.log(
Array(3)
.fill(1)
.map(() =>
chalk.yellow('WARNING: You are trying to skip git status checking, please be careful')
)
.join('\n')
);
}
}

// check for `path`
if (!dir || !fs.existsSync(dir)) {
console.log(chalk.yellow('Invalid dir:', dir, ', please pass a valid dir'));
process.exit(1);
}

await run(dir, args);

try {
console.log('----------- dependencies alert -----------\n');
const depsList = await getDependencies();
await upgradeDetect(
dir,
depsList.includes('@ant-design/pro-layout'),
depsList.includes('@ant-design/compatible')
);
} catch (err) {
console.log('skip summary due to', err);
} finally {
console.log(`\n----------- Thanks for using @ant-design/codemod ${pkg.version} -----------`);
}
}

module.exports = {
bootstrap,
ensureGitClean,
transform,
run,
getRunnerArgs,
checkUpdates,
};
7 changes: 7 additions & 0 deletions packages/codemod/src/bin/codemod.ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
*.css
*.json
*.less
*.sass
*.scss
.umi
Loading

0 comments on commit 23b8697

Please sign in to comment.