From a5a5058565730e8850ae137d4732415ca7d66287 Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Sun, 30 Nov 2025 22:15:57 +0800 Subject: [PATCH] feat(scripts): add git-multi script for multi-repo commands --- .gitignore | 1 + package.json | 1 + scripts/git-multi.js | 112 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 scripts/git-multi.js diff --git a/.gitignore b/.gitignore index 2cea092ff79..6bb15e4c453 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ vitest.config.mts.timestamp-* .github/copilot-instructions.md .claude/ python/ +git-repos.json diff --git a/package.json b/package.json index 0c5c82cddd8..36f22ac86ff 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "version:alpha": "lerna version prerelease --preid alpha --force-publish=* --no-git-tag-version -m \"chore(versions): publish packages %s\"", "release:force": "lerna publish from-package --yes --no-git-tag-version", "release": "lerna publish", + "git-multi": "node scripts/git-multi.js", "run:example": "tsx -r dotenv/config -r tsconfig-paths/register ./examples/index.ts" }, "resolutions": { diff --git a/scripts/git-multi.js b/scripts/git-multi.js new file mode 100644 index 00000000000..a1b324c2336 --- /dev/null +++ b/scripts/git-multi.js @@ -0,0 +1,112 @@ +const fs = require('fs'); +const path = require('path'); +const { spawn } = require('child_process'); + +const rootDir = path.resolve(__dirname, '..'); +const configFile = path.join(rootDir, 'git-repos.json'); + +// 获取命令行参数作为 git 命令 +const gitArgs = process.argv.slice(2); + +if (gitArgs.length === 0) { + console.log('Usage: node scripts/git-multi.js '); + console.log(' or: yarn git-multi '); + console.log('Example: node scripts/git-multi.js status'); + console.log(' yarn git-multi pull'); + console.log('\nConfiguration:'); + console.log(' Create a "git-repos.json" file in the root directory to configure repositories.'); + console.log(' Format: { "repos": ["path/to/repo1", "path/to/repo2"] }'); + console.log(' If no config file is found, it will scan default locations.'); + process.exit(1); +} + +function getGitRepos() { + let repos = []; + let usedConfig = false; + + // 1. 尝试读取配置文件 + if (fs.existsSync(configFile)) { + try { + const config = JSON.parse(fs.readFileSync(configFile, 'utf8')); + if (Array.isArray(config.repos)) { + // 处理相对路径 + repos = config.repos.map((repoPath) => path.resolve(rootDir, repoPath)); + usedConfig = true; + } + } catch (e) { + console.error('Error reading git-repos.json:', e); + } + } + + // 2. 如果没有配置或配置为空,使用默认扫描 + if (!usedConfig || repos.length === 0) { + if (!usedConfig) { + console.log('No git-repos.json found, scanning default directories...'); + } + + // 添加根目录 + if (fs.existsSync(path.join(rootDir, '.git'))) { + repos.push(rootDir); + } + + // 扫描 pro-plugins + const proPluginsDir = path.join(rootDir, 'packages/pro-plugins/@nocobase'); + if (fs.existsSync(proPluginsDir)) { + const dirs = fs.readdirSync(proPluginsDir); + for (const dir of dirs) { + const fullPath = path.join(proPluginsDir, dir); + // 忽略 .DS_Store 等文件 + if (dir.startsWith('.')) continue; + + try { + if (fs.statSync(fullPath).isDirectory() && fs.existsSync(path.join(fullPath, '.git'))) { + repos.push(fullPath); + } + } catch (e) { + // ignore + } + } + } + } + + return [...new Set(repos)]; // 去重 +} + +const repos = getGitRepos(); + +console.log(`Found ${repos.length} git repositories.`); + +async function runGitCommand(repo, args) { + const relativePath = path.relative(rootDir, repo); + const repoName = relativePath || 'ROOT'; + + // 使用颜色高亮仓库名 + console.log(`\n\x1b[36m--- [${repoName}] git ${args.join(' ')} ---\x1b[0m`); + + return new Promise((resolve, reject) => { + const child = spawn('git', args, { + cwd: repo, + stdio: 'inherit', // 直接输出到终端,保留颜色 + }); + + child.on('close', (code) => { + if (code !== 0) { + console.error(`\x1b[31m[${repoName}] exited with code ${code}\x1b[0m`); + } + resolve(); + }); + + child.on('error', (err) => { + console.error(`\x1b[31m[${repoName}] failed to start git: ${err.message}\x1b[0m`); + resolve(); + }); + }); +} + +async function main() { + for (const repo of repos) { + await runGitCommand(repo, gitArgs); + } +} + +main();