Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]chore: add migrate scripts #95

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
41 changes: 27 additions & 14 deletions packages/ice-scripts/bin/ice-scripts.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
#!/usr/bin/env node
const program = require('commander');
const chalk = require('chalk');
const inquirer = require('inquirer');
const packageInfo = require('../package.json');
const checkNodeVersion = require('../lib/utils/checkNodeVersion');
const validationSassAvailable = require('../lib/utils/validationSassAvailable');

console.log();
console.log(chalk.red(`
当前 ice-scripts 版本 ${packageInfo.version} 已不在维护,请升级到 ice.js 进行使用。
ice.js 在可配置性以及扩展性方面做了极大提升,且满足 ice-scripts 的所有功能。
`));
console.log(chalk.yellow(`
升级文档:https://ice.work/docs/guide/migrate
如升级遇到问题可通过钉钉群与我们联系:https://ice.alicdn.com/assets/images/qrcode.png
`));
console.log();
const migrate = require('../lib/utils/migrate');

(async () => {
console.log(packageInfo.name, packageInfo.version);

program.parse(process.argv);
const subCmd = program.args[0];

if (subCmd) {
console.log();
console.log(chalk.red(`
1. 检查到您项目依赖的 ice-scripts 版本 ${packageInfo.version} 已不在维护,请升级到 ice.js 进行使用
2. 升级文档:https://ice.work/docs/guide/migrate
3. 如升级遇到问题可通过钉钉群与我们联系:https://ice.alicdn.com/assets/images/qrcode.png
4. 再次感谢您升级到最新版本,感谢信任并使用 ICE
`));
console.log();

const answer = await inquirer.prompt({
type: 'confirm',
name: 'shouldUpgrade',
message: `检测到您的项目符合一键升级规范,立即升级?`,
default: true,
});
if (answer.shouldUpgrade) {
await migrate();
}
}

// finish check before run command
checkNodeVersion(packageInfo.engines.node);
validationSassAvailable();
Expand All @@ -29,8 +45,6 @@ console.log();
.command('dev', 'start server')
.command('test <regexForTestFiles>', 'run tests with jest');

program.parse(process.argv);

const proc = program.runningCommand;

if (proc) {
Expand All @@ -40,7 +54,6 @@ console.log();
});
}

const subCmd = program.args[0];
if (!subCmd) {
program.help();
}
Expand Down
288 changes: 288 additions & 0 deletions packages/ice-scripts/lib/utils/migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
const path = require('path');
const fs = require('fs-extra');
const parser = require('@babel/parser');
const generate = require('@babel/generator');
const traverse = require('@babel/traverse');
const t = require('@babel/types');
const prettier = require('prettier');
const { getLatestVersion } = require('ice-npm-utils');

// 项目开发插件列表枚举
const pluginMigrateMap = {
'ice-plugin-fusion': 'build-plugin-fusion',
'ice-plugin-antd': 'build-plugin-antd',
'ice-plugin-css-assets-local': 'build-plugin-css-assets-local',
'ice-plugin-moment-locales': 'build-plugin-moment-locales',
'ice-plugin-modular-import': 'build-plugin-modular-import',
'ice-plugin-load-assets': 'build-plugin-load-assets',
'ice-plugin-smart-debug': 'build-plugin-smart-debug',
'@ali/ice-plugin-def': '@ali/build-plugin-ice-def'
};

module.exports = async () => {
const dir = process.cwd();
const oldConfigPath = path.join(dir, 'ice.config.js');
const newConfigPath = path.join(dir, 'build.json');
const pkgPath = path.join(dir, 'package.json');

// 检测是否含有 build.json
if (fs.existsSync(newConfigPath)) {
console.log('当前目录下已存在 build.json 配置,更多配置信息请查看 https://ice.work/docs/guide/migrate')
return;
}

// 检测是否含有 ice.config.js
if (!fs.existsSync(oldConfigPath)) {
console.log('自动迁移工具目前仅支持 ice-scripts 2.x 向 icejs 迁移,其他工程迁移,请联系 ICE 团队');
return;
}

// 检测是否含有 package.json
if (!fs.existsSync(pkgPath)) {
console.log('未找到 package.json 文件,请在项目根目录执行迁移');
return;
}

const pkgData = fs.readJSONSync(pkgPath);
const oldConfigContent = fs.readFileSync(oldConfigPath, 'utf-8');

// 备份 ice.config.js 后删除
fs.copyFileSync(oldConfigPath, path.join(dir, 'ice.config.backup.js'));
fs.removeSync(oldConfigPath);

// 备份 package.json
fs.copyFileSync(pkgPath, path.join(dir, 'package.backup.json'));

const configs = {plugins: []};
let customNode = null;
let customParam = null;
let declarations = [];

const npmCollect = {
add: ['ice.js'],
remove: ['ice-scripts'],
};

const migratePlugin = (add, remove) => {
configs.plugins.push(add);
npmCollect.add.push(Array.isArray(add) ? add[0] : add);
if (remove) {
npmCollect.remove.push(remove);
}
};

const parseConfig = {
sourceType: 'module',
plugins: ['jsx', 'typescript', 'decorators-legacy', 'dynamicImport', 'classProperties'],
};
const ast = parser.parse(oldConfigContent, parseConfig);
traverse.default(ast, {
Program(nodePath){
const { node } = nodePath;
node.body.forEach(bodyNode => {
if (t.isExpressionStatement(bodyNode) && checkExportNode(bodyNode.expression)) {
bodyNode.expression.right.properties.forEach(property => {
const propertyKey = property.key.name;
if (propertyKey === 'chainWebpack') {
customNode = property.value.body.body;
customParam = property.value.params;
} else if (propertyKey === 'plugins') {
// 插件配置比为数组形式
if (t.isArrayExpression(property.value)) {
const elements = property.value.elements;
elements.forEach(element => {
let pluginInfo;
if (t.isArrayExpression(element)) {
pluginInfo = element.elements;
} else {
pluginInfo = [element];
}
const [nameNode, optionNode] = pluginInfo;
const pluginName = nameNode.value;
const migrateName = pluginMigrateMap[pluginName];
if (migrateName) {
if (!optionNode) {
migratePlugin(migrateName, pluginName);
} else if (['ice-plugin-multi-pages'].includes(pluginName) && optionNode) {
// 特殊配置迁移处理,ice-plugin-multi-pages 配置移除
console.log(`插件${pluginName}配置迁移,请查看 build-scripts 迁移文档`);
migratePlugin(migrateName, pluginName);
} else {
let options = null;
try {
options = getNodeValue(optionNode);
} catch (error) {
console.log(`插件${pluginName}配置无法自定迁移,请手动处理`)
}
// 特殊配置迁移,ice-plugin-load-assets dev => start
if (pluginName === 'ice-plugin-load-assets' && options.assets && options.assets.dev) {
options.assets.start = [...options.assets.dev];
delete options.assets.dev;
}
migratePlugin(options ? [migrateName, options] : [migrateName], pluginName);
}
} else {
console.log(`请手动迁移插件${pluginName}`);
}
})
}
} else {
if (checkJsonValue(property.value)) {
configs[propertyKey] = property.value.value;
} else {
try {
configs[propertyKey] = getNodeValue(property.value);
} catch(error) {
console.log(`${propertyKey} 无法自动解析到 JSON 请手动处理`);
}
}
}
});
} else {
declarations.push(bodyNode);
}
})
},
});

if (customNode) {
// 写入本地插件
const ast = parser.parse(`
module.exports = ({ onGetWebpackConfig, context }) => {
const { command } = context;
onGetWebpackConfig((config) => {});
};`, parseConfig);
try {
traverse.default(ast, {
Program(nodePath) {
if (declarations.length) {
const { node } = nodePath;
node.body = declarations.concat(node.body);
declarations = [];
}
},
CallExpression(nodePath) {
const { node } = nodePath;
if (t.isIdentifier(node.callee, { name: 'onGetWebpackConfig'})) {
node.arguments[0].body.body = customNode;
if (customParam && customParam[0]) {
node.arguments[0].params[0] = customParam[0];
}
}
if (
t.isMemberExpression(node.callee)
&& t.isIdentifier(node.callee.property, { name: 'minimizer'})
&& node.arguments && node.arguments[0] && t.isStringLiteral(node.arguments[0])
&& node.arguments[0].value === 'UglifyJsPlugin'
) {
// UglifyJsPlugin => TerserPlugin
node.arguments[0].value = 'TerserPlugin';
}
},
BinaryExpression(nodePath) {
// 自动迁移 dev => start
const { node } = nodePath;
if (
(node.operator === '===' || node.operator === '==')
&& t.isIdentifier(node.left, { name: 'command'})
&& t.isStringLiteral(node.right)
&& node.right.value === 'dev'
) {
node.right.value = 'start';
}
},
});

const { code } = generate.default(ast, {});
const content = prettierCode(code.replace('\n', ''));
const localPlugin = 'local-plugin.js';
fs.writeFileSync(path.join(dir, localPlugin), content, 'utf-8');
configs.plugins.push(`./${localPlugin}`);
} catch (error) {
console.log('自定义 webpack 配置迁移失败,请手动迁移');
console.log(error);
}
}
// reorder plugin
const buildPlugins = [...configs.plugins];
delete configs.plugins;
// generate build.json
fs.writeJSONSync(newConfigPath, { ...configs, plugins: buildPlugins }, { spaces: 2 });

// migrate abc.json
const defConfigPath = path.join(dir, 'abc.json');
const isDEF = fs.existsSync(defConfigPath);
if (isDEF) {
// abc.json 变更无逻辑变化,无需备份
const jsonString = JSON.stringify({
type: 'ice-app',
builder: '@ali/builder-ice-app'
}, null, 2);
fs.writeFileSync(defConfigPath, jsonString, 'utf-8');
}

// modify pdgData
pkgData.scripts = pkgData.scripts || {};
pkgData.scripts.start = 'icejs start';
pkgData.scripts.build = 'icejs build';
pkgData.scripts.test = 'icejs test';

pkgData.devDependencies = pkgData.devDependencies || {};
const devDependencies = [];
for (const npm of npmCollect.add) {
devDependencies.push({ key: npm, version: `^${await getLatestVersion(npm)}` });
}
npmCollect.remove.forEach((npm) => {
delete pkgData.devDependencies[npm];
});
Object.keys(pkgData.devDependencies).forEach(key => {
devDependencies.push({ key, version: pkgData.devDependencies[key] });
});
// reorder devDependencies
devDependencies.sort((a, b) => (a > b));
pkgData.devDependencies = {};
devDependencies.forEach(({key, version}) => {pkgData.devDependencies[key] = version;});

// generate package.json
fs.writeJSONSync(pkgPath, pkgData, {
spaces: 2
});

console.log();
console.log('自动迁移完成,请删除依赖并重新安装后启动项目。');
console.log();
}

function checkExportNode(node) {
return node && t.isAssignmentExpression(node) && t.isMemberExpression(node.left)
&& t.isIdentifier(node.left.object, { name: 'module'})
&& t.isIdentifier(node.left.property, { name: 'exports'});
}

function checkJsonValue(node) {
return t.isStringLiteral(node) || t.isNumericLiteral(node) || t.isBooleanLiteral(node);
}

function getNodeValue(node) {
if (t.isArrayExpression(node)) {
return node.elements.map(element => getNodeValue(element));
} else if (t.isObjectExpression(node)) {
const value = {};
node.properties.forEach(property => {
value[property.key.name || property.key.value] = getNodeValue(property.value);
});
return value;
} else if (checkJsonValue(node)) {
return node.value;
} else {
throw new Error('无法自动解析到 JSON');
}
}

function prettierCode(code) {
return prettier.format(code, {
singleQuote: true,
trailingComma: 'es5',
parser: 'typescript',
});
}
11 changes: 9 additions & 2 deletions packages/ice-scripts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ice-scripts",
"version": "2.1.17",
"version": "2.1.18-0",
"description": "ICE SDK",
"main": "index.js",
"bin": {
Expand All @@ -11,7 +11,8 @@
"clear": "rm -rf test/fixtures/*/build/ && rm -rf test/fixtures/*/.happypack/",
"webpack": "npm run clear && node ./test/compiler.js",
"start": "http-server ./test/fixtures/ -o -c-1 -p 8081",
"test": "LOG_LEVEL=verbose jest test/**/*.test.js"
"test": "LOG_LEVEL=verbose jest test/**/*.test.js",
"migrate": "cd ./test/migrate && node ../../lib/utils/migrate.js"
},
"keywords": [
"ice"
Expand Down Expand Up @@ -45,6 +46,12 @@
"@babel/preset-env": "^7.4.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.3.3",
"@babel/generator": "^7.7.7",
"@babel/parser": "^7.7.7",
"@babel/traverse": "^7.7.4",
"@babel/types": "^7.7.4",
"ice-npm-utils": "^1.3.1",
"prettier": "^1.19.1",
"address": "^1.0.3",
"autoprefixer": "^9.1.5",
"axios": "^0.19.0",
Expand Down
Loading