Skip to content

Commit 14ef510

Browse files
committed
最后提交
1 parent 5110603 commit 14ef510

File tree

132 files changed

+6306
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

132 files changed

+6306
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env node
2+
require('../src/index.js').main(process.argv);
3+
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env node
2+
3+
'use strict';
4+
5+
const { Command } = require('commander');
6+
const chalk = require('chalk');
7+
const path = require('path');
8+
const { getCacheInfo, restoreAllFiles, clearCache } = require('../src/utils/cache-manager');
9+
const pkg = require('../package.json');
10+
11+
async function main(argv) {
12+
const program = new Command();
13+
14+
program
15+
.name('inula-rollback')
16+
.description('Rollback changes made by inula-migrate using cached backups')
17+
.version(pkg.version)
18+
.option('--clear', 'Clear cache without restoring files')
19+
.option('--info', 'Show cache information without making changes')
20+
.option('-v, --verbose', 'Verbose output')
21+
.option('-y, --yes', 'Skip confirmation prompts')
22+
.action(async (options) => {
23+
const projectRoot = process.cwd();
24+
const cacheInfo = getCacheInfo(projectRoot);
25+
26+
if (options.info) {
27+
// 显示缓存信息
28+
console.log(chalk.cyan('\n📦 Cache Information'));
29+
console.log(chalk.dim('─'.repeat(50)));
30+
31+
if (!cacheInfo.exists) {
32+
console.log(chalk.yellow('No cache found.'));
33+
return;
34+
}
35+
36+
console.log(`Cache directory: ${chalk.dim(cacheInfo.cacheDir)}`);
37+
console.log(`Cached files: ${chalk.green(cacheInfo.fileCount)}`);
38+
console.log(`Total size: ${chalk.dim((cacheInfo.totalSize / 1024).toFixed(2) + ' KB')}`);
39+
40+
if (cacheInfo.files.length > 0) {
41+
console.log('\nCached files:');
42+
for (const file of cacheInfo.files) {
43+
console.log(` ${chalk.dim('•')} ${file.relativePath} ${chalk.dim(`(${file.timestamp})`)}`);
44+
}
45+
}
46+
return;
47+
}
48+
49+
if (options.clear) {
50+
// 清理缓存
51+
if (!cacheInfo.exists) {
52+
console.log(chalk.yellow('No cache to clear.'));
53+
return;
54+
}
55+
56+
if (!options.yes) {
57+
console.log(chalk.yellow(`About to clear cache with ${cacheInfo.fileCount} files.`));
58+
// 在真实环境中这里应该有确认提示
59+
}
60+
61+
const success = clearCache(projectRoot);
62+
if (success) {
63+
console.log(chalk.green('✅ Cache cleared successfully.'));
64+
} else {
65+
console.error(chalk.red('❌ Failed to clear cache.'));
66+
process.exitCode = 1;
67+
}
68+
return;
69+
}
70+
71+
// 默认行为:回滚文件
72+
if (!cacheInfo.exists || cacheInfo.fileCount === 0) {
73+
console.log(chalk.yellow('No cached files found to rollback.'));
74+
console.log(chalk.dim('Run inula-migrate with --write to create backups before making changes.'));
75+
return;
76+
}
77+
78+
console.log(chalk.cyan('\n🔄 Rolling back changes'));
79+
console.log(chalk.dim('─'.repeat(50)));
80+
console.log(`Found ${chalk.green(cacheInfo.fileCount)} cached files`);
81+
82+
if (!options.yes) {
83+
console.log(chalk.yellow('\nThis will restore the original versions of the following files:'));
84+
for (const file of cacheInfo.files) {
85+
console.log(` ${chalk.dim('•')} ${file.relativePath}`);
86+
}
87+
console.log(chalk.yellow('\nProceed with rollback? (This action cannot be undone)'));
88+
// 在真实环境中这里应该有确认提示
89+
}
90+
91+
const results = restoreAllFiles(projectRoot);
92+
93+
console.log(chalk.dim('\nRollback Results:'));
94+
console.log(`Total files: ${results.total}`);
95+
console.log(`Restored: ${chalk.green(results.restored)}`);
96+
97+
if (results.failed > 0) {
98+
console.log(`Failed: ${chalk.red(results.failed)}`);
99+
if (results.errors.length > 0) {
100+
console.log('\nFailed files:');
101+
for (const error of results.errors) {
102+
console.log(` ${chalk.red('✗')} ${error}`);
103+
}
104+
}
105+
process.exitCode = 1;
106+
} else {
107+
console.log(chalk.green('\n✅ All files restored successfully!'));
108+
}
109+
110+
if (options.verbose) {
111+
console.log(chalk.dim('\nCache directory has been cleaned up.'));
112+
}
113+
});
114+
115+
await program.parseAsync(argv);
116+
}
117+
118+
if (require.main === module) {
119+
main(process.argv).catch(error => {
120+
console.error(chalk.red('Error:'), error.message);
121+
process.exitCode = 1;
122+
});
123+
}
124+
125+
module.exports = { main };
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "@openinula/migrator",
3+
"version": "0.1.0",
4+
"description": "CLI to migrate React 17/18 code to OpenInula 2.0 syntax using jscodeshift",
5+
"private": false,
6+
"bin": {
7+
"inula-migrate": "bin/inula-migrate",
8+
"inula-rollback": "bin/inula-rollback",
9+
"openinula_migrator": "bin/inula-migrate"
10+
},
11+
"scripts": {
12+
"dev": "node src/index.js --help",
13+
"test": "vitest run",
14+
"cli": "node bin/inula-migrate",
15+
"build": "echo 'no build step for JS'",
16+
"format": "prettier --write ."
17+
},
18+
"keywords": [
19+
"openinula",
20+
"react",
21+
"codemod",
22+
"jscodeshift",
23+
"cli",
24+
"migration"
25+
],
26+
"author": "",
27+
"license": "MIT",
28+
"engines": {
29+
"node": ">=18"
30+
},
31+
"dependencies": {
32+
"chalk": "^4.1.2",
33+
"commander": "^12.1.0",
34+
"diff": "^5.2.0",
35+
"fast-glob": "^3.3.2",
36+
"jscodeshift": "^0.16.1",
37+
"p-limit": "^5.0.0",
38+
"prettier": "^2.8.8",
39+
"recast": "^0.23.9"
40+
},
41+
"devDependencies": {
42+
"vitest": "^2.0.5"
43+
}
44+
}
45+
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
6+
/**
7+
* 配置文件加载器
8+
* 支持 .inularc.json, .inularc.js, .inularc.yaml, .inularc.yml
9+
*/
10+
11+
const DEFAULT_CONFIG = {
12+
ignore: [],
13+
prettier: true,
14+
concurrency: require('os').cpus().length,
15+
extensions: ['js', 'jsx', 'ts', 'tsx'],
16+
parser: 'auto',
17+
recursive: true,
18+
failOnWarn: false,
19+
quiet: false,
20+
verbose: false,
21+
report: null,
22+
reportFormat: 'json',
23+
};
24+
25+
/**
26+
* 查找配置文件
27+
* @param {string} startDir 开始查找的目录
28+
* @param {string|null} explicitPath 明确指定的配置文件路径
29+
* @returns {string|null} 配置文件路径或null
30+
*/
31+
function findConfigFile(startDir, explicitPath = null) {
32+
if (explicitPath) {
33+
const resolved = path.resolve(startDir, explicitPath);
34+
return fs.existsSync(resolved) ? resolved : null;
35+
}
36+
37+
const configNames = [
38+
'.inularc.json',
39+
'.inularc.js',
40+
'.inularc.yaml',
41+
'.inularc.yml',
42+
'inula.config.js',
43+
'inula.config.json',
44+
];
45+
46+
let currentDir = startDir;
47+
while (currentDir !== path.dirname(currentDir)) {
48+
for (const name of configNames) {
49+
const configPath = path.join(currentDir, name);
50+
if (fs.existsSync(configPath)) {
51+
return configPath;
52+
}
53+
}
54+
currentDir = path.dirname(currentDir);
55+
}
56+
57+
return null;
58+
}
59+
60+
/**
61+
* 加载配置文件内容
62+
* @param {string} configPath 配置文件路径
63+
* @returns {object} 配置对象
64+
*/
65+
function loadConfigFile(configPath) {
66+
if (!configPath || !fs.existsSync(configPath)) {
67+
return {};
68+
}
69+
70+
const ext = path.extname(configPath).toLowerCase();
71+
72+
try {
73+
if (ext === '.json') {
74+
const content = fs.readFileSync(configPath, 'utf8');
75+
return JSON.parse(content);
76+
}
77+
78+
if (ext === '.js') {
79+
// 清除缓存以支持重新加载
80+
delete require.cache[require.resolve(configPath)];
81+
const mod = require(configPath);
82+
return mod && mod.default ? mod.default : mod;
83+
}
84+
85+
if (ext === '.yaml' || ext === '.yml') {
86+
// 简单的 YAML 解析(仅支持基本格式)
87+
const content = fs.readFileSync(configPath, 'utf8');
88+
return parseSimpleYaml(content);
89+
}
90+
} catch (error) {
91+
throw new Error(`Failed to load config file ${configPath}: ${error.message}`);
92+
}
93+
94+
return {};
95+
}
96+
97+
/**
98+
* 简单的 YAML 解析器(仅支持基本格式)
99+
* @param {string} content YAML 内容
100+
* @returns {object} 解析后的对象
101+
*/
102+
function parseSimpleYaml(content) {
103+
const result = {};
104+
const lines = content.split('\n');
105+
106+
for (const line of lines) {
107+
const trimmed = line.trim();
108+
if (!trimmed || trimmed.startsWith('#')) continue;
109+
110+
const colonIndex = trimmed.indexOf(':');
111+
if (colonIndex === -1) continue;
112+
113+
const key = trimmed.slice(0, colonIndex).trim();
114+
let value = trimmed.slice(colonIndex + 1).trim();
115+
116+
// 处理基本类型
117+
if (value === 'true') value = true;
118+
else if (value === 'false') value = false;
119+
else if (/^\d+$/.test(value)) value = parseInt(value, 10);
120+
else if (value.startsWith('[') && value.endsWith(']')) {
121+
// 简单数组解析
122+
value = value.slice(1, -1).split(',').map(s => s.trim().replace(/['"]/g, ''));
123+
} else {
124+
// 字符串值,移除引号
125+
value = value.replace(/^['"]|['"]$/g, '');
126+
}
127+
128+
result[key] = value;
129+
}
130+
131+
return result;
132+
}
133+
134+
/**
135+
* 合并配置
136+
* @param {object} defaultConfig 默认配置
137+
* @param {object} fileConfig 文件配置
138+
* @param {object} cliConfig CLI 配置
139+
* @returns {object} 合并后的配置
140+
*/
141+
function mergeConfigs(defaultConfig, fileConfig, cliConfig) {
142+
const merged = { ...defaultConfig };
143+
144+
// 合并文件配置
145+
Object.keys(fileConfig).forEach(key => {
146+
if (fileConfig[key] !== undefined) {
147+
if (Array.isArray(defaultConfig[key]) && Array.isArray(fileConfig[key])) {
148+
// 对于数组类型,如果是 ignore 则合并,其他则替换
149+
if (key === 'ignore') {
150+
merged[key] = [...defaultConfig[key], ...fileConfig[key]];
151+
} else {
152+
merged[key] = fileConfig[key];
153+
}
154+
} else {
155+
merged[key] = fileConfig[key];
156+
}
157+
}
158+
});
159+
160+
// 合并 CLI 配置(CLI 优先级最高)
161+
Object.keys(cliConfig).forEach(key => {
162+
if (cliConfig[key] !== undefined) {
163+
if (Array.isArray(merged[key]) && Array.isArray(cliConfig[key])) {
164+
// 对于数组类型,如果是 ignore 则合并,其他则替换
165+
if (key === 'ignore') {
166+
merged[key] = [...merged[key], ...cliConfig[key]];
167+
} else {
168+
merged[key] = cliConfig[key];
169+
}
170+
} else {
171+
merged[key] = cliConfig[key];
172+
}
173+
}
174+
});
175+
176+
return merged;
177+
}
178+
179+
/**
180+
* 加载完整配置
181+
* @param {object} options CLI 选项
182+
* @param {string} cwd 当前工作目录
183+
* @returns {object} 最终配置
184+
*/
185+
function loadConfig(options = {}, cwd = process.cwd()) {
186+
const configPath = findConfigFile(cwd, options.config);
187+
const fileConfig = loadConfigFile(configPath);
188+
189+
const finalConfig = mergeConfigs(DEFAULT_CONFIG, fileConfig, options);
190+
191+
// 添加元信息
192+
finalConfig._meta = {
193+
configPath,
194+
loadedAt: new Date().toISOString(),
195+
cwd,
196+
};
197+
198+
return finalConfig;
199+
}
200+
201+
module.exports = {
202+
loadConfig,
203+
findConfigFile,
204+
loadConfigFile,
205+
mergeConfigs,
206+
DEFAULT_CONFIG,
207+
};

0 commit comments

Comments
 (0)