From a682dee3ec394319106df3971f708583a5c03e3e Mon Sep 17 00:00:00 2001 From: LZS911 <932177767@qq.com> Date: Thu, 6 Nov 2025 15:30:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(ci):=20Fix=20npm=20cache=20directory=20perm?= =?UTF-8?q?issions=20in=20CI=EF=BC=8CRefactor=20publish=20icon=20code=20lo?= =?UTF-8?q?gic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/icons/PUBLISH_TODO.md | 24 -- packages/icons/package_publish.json | 4 +- packages/icons/publish-icons.mjs | 253 ------------------ .../dms-kit-publish/src/config/environment.ts | 12 +- .../dms-kit-publish/src/services/deploy.ts | 214 +++++++++++++-- .../src/services/notification.ts | 8 +- .../dms-kit-publish/src/services/validator.ts | 5 +- .../dms-kit-publish/src/utils/changelog.ts | 9 +- scripts/cli/dms-kit-publish/src/utils/exec.ts | 25 ++ 9 files changed, 233 insertions(+), 321 deletions(-) delete mode 100644 packages/icons/PUBLISH_TODO.md delete mode 100755 packages/icons/publish-icons.mjs create mode 100644 scripts/cli/dms-kit-publish/src/utils/exec.ts diff --git a/packages/icons/PUBLISH_TODO.md b/packages/icons/PUBLISH_TODO.md deleted file mode 100644 index 2610be39c..000000000 --- a/packages/icons/PUBLISH_TODO.md +++ /dev/null @@ -1,24 +0,0 @@ -# Icons 包发布临时处理方案(PUBLISH_TODO) - -## 背景 - -`packages/icons` 目录下存在两个配置文件: - -- `package.json`:供 DMS 项目本地/开发运行时使用,入口为源码 `src/index.ts`。 -- `package_publish.json`:供发版时使用,入口为构建产物(如 `es/index.js`、`dist/index.js`,并包含类型文件)。 - -之所以采用“双配置”方案,是为了在日常开发中保持 DMS 工程对源码入口的稳定依赖,同时在发版时切换到产物入口,避免对 DMS 运行造成影响。 - -## 何时需要发布 - -- 新增/更新图标资源(`svg/`)并已同步生成 React 组件(`src/`)。 -- 变更了打包或导出行为,需要产出新的 `es/` 或 `dist/`。 -- 修复线上问题并需要对外发版。 - -## 发布前检查清单 - -- 版本号:在 `packages/icons/package_publish.json` 中按语义化版本更新 `version`。 -- 入口与导出:确认 `main/module/types/exports` 指向构建产物(当前为 `es/` 与 `dist/`)。 -- 发布范围:`files` 至少包含 `es`;如需提供 CJS(`require`)消费,确保发布包中包含 `dist`(见 FAQ)。 -- 脚本:`prepublishOnly` 会自动构建(`pnpm build`)。 -- Registry:`publishConfig.registry` 指向私有仓库 `http://10.186.18.19:4873/`,无需额外指定。 diff --git a/packages/icons/package_publish.json b/packages/icons/package_publish.json index 8e8d9987f..c2bfb7ffc 100644 --- a/packages/icons/package_publish.json +++ b/packages/icons/package_publish.json @@ -1,7 +1,7 @@ { "name": "@actiontech/icons", "description": "", - "version": "0.0.1-rc.7", + "version": "1.0.0", "main": "./dist/index.js", "module": "./es/index.js", "types": "./es/index.d.ts", @@ -42,4 +42,4 @@ "peerDependencies": { "react": ">=17" } -} +} \ No newline at end of file diff --git a/packages/icons/publish-icons.mjs b/packages/icons/publish-icons.mjs deleted file mode 100755 index c0b864af1..000000000 --- a/packages/icons/publish-icons.mjs +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env node -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { spawnSync } from 'node:child_process'; -import readline from 'node:readline'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const iconsDir = __dirname; - -function parseArgs(argv) { - const args = { version: '', skipConfirm: false, registry: '', auth: '' }; - for (let i = 2; i < argv.length; i += 1) { - const key = argv[i]; - const val = argv[i + 1]; - if (key === '--version' || key === '-v') { - args.version = val || ''; - i += 1; - } else if (key === '--skip-confirm' || key === '-y') { - args.skipConfirm = true; - } else if (key === '--registry' || key === '-r') { - args.registry = val || ''; - } else if (key === '--auth' || key === '-a') { - args.auth = val || ''; - } - } - return args; -} - -function readJson(filePath) { - const raw = fs.readFileSync(filePath, 'utf8'); - return JSON.parse(raw); -} - -function writeJson(filePath, obj) { - const content = JSON.stringify(obj, null, 2) + '\n'; - fs.writeFileSync(filePath, content, 'utf8'); -} - -function ensureFileExists(filePath, desc) { - if (!fs.existsSync(filePath)) { - throw new Error(`${desc} 不存在: ${filePath}`); - } -} - -function runCmd(cmd, args, cwd) { - const res = spawnSync(cmd, args, { - stdio: 'inherit', - cwd, - env: process.env, - shell: process.platform === 'win32' - }); - if (res.status !== 0) { - throw new Error(`${cmd} ${args.join(' ')} 执行失败,退出码 ${res.status}`); - } -} - -function copyDirSync(src, dest, options = {}) { - const { filter = (p) => true, dereference = false } = options; - - const statFn = dereference ? fs.statSync : fs.lstatSync; - - const copyItem = (from, to) => { - if (!filter(from)) return; - const stat = statFn(from); - if (stat.isDirectory()) { - if (!fs.existsSync(to)) fs.mkdirSync(to, { recursive: true }); - const entries = fs.readdirSync(from); - for (const entry of entries) { - copyItem(path.join(from, entry), path.join(to, entry)); - } - } else if (stat.isSymbolicLink()) { - const real = fs.readlinkSync(from); - fs.symlinkSync(real, to); - } else if (stat.isFile()) { - fs.copyFileSync(from, to); - } - }; - - copyItem(src, dest); -} - -function showPackageStructure(tmpDir) { - console.log('\n📦 发包产物结构预览:'); - console.log('='.repeat(50)); - - function listDir(dir, prefix = '', maxDepth = 3, currentDepth = 0) { - if (currentDepth >= maxDepth) return; - - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - const sortedEntries = entries.sort((a, b) => { - // 目录在前,文件在后 - if (a.isDirectory() && !b.isDirectory()) return -1; - if (!a.isDirectory() && b.isDirectory()) return 1; - return a.name.localeCompare(b.name); - }); - - for (const entry of sortedEntries) { - const fullPath = path.join(dir, entry.name); - const relativePath = path.relative(tmpDir, fullPath); - const icon = entry.isDirectory() ? '📁' : '📄'; - console.log(`${prefix}${icon} ${relativePath}`); - - if (entry.isDirectory() && currentDepth < maxDepth - 1) { - listDir(fullPath, prefix + ' ', maxDepth, currentDepth + 1); - } - } - } catch (err) { - console.log(`${prefix}❌ 无法读取目录: ${err.message}`); - } - } - - listDir(tmpDir); - console.log('='.repeat(50)); -} - -async function confirmPublish() { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - return new Promise((resolve) => { - rl.question('\n❓ 确认要发布这个包吗?(y/N): ', (answer) => { - rl.close(); - const confirmed = - answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'; - resolve(confirmed); - }); - }); -} - -async function main() { - const { skipConfirm, registry, auth } = parseArgs(process.argv); - - const version = readJson(path.join(iconsDir, 'package.json')).version; - - const pubPkg = path.join(iconsDir, 'package_publish.json'); - ensureFileExists(pubPkg, '发布用 package_publish.json'); - - // 保存原始版本号,用于后续还原 - const originalPubPkgContent = readJson(pubPkg); - const originalVersion = originalPubPkgContent.version; - - const tmpBase = path.join(__dirname, '..'); - const tmpDir = path.join(tmpBase, 'actiontech-icons-publish'); - - try { - console.log(`[1/7] 创建临时目录: ${tmpDir}`); - fs.mkdirSync(tmpDir, { recursive: true }); - - console.log('[2/7] 复制整个 icons 包到临时目录'); - // 复制整个包目录,但排除一些不需要的文件 - const excludeTopLevel = new Set([ - 'node_modules', - 'dist', - 'es', - '.git', - '.github', - 'coverage', - '.nyc_output' - ]); - - const filter = (filePath) => { - const relativePath = path.relative(iconsDir, filePath); - if (!relativePath) return true; // 根目录 - const segments = relativePath.split(path.sep); - const top = segments[0]; - if (excludeTopLevel.has(top)) return false; - const base = path.basename(filePath); - if (base === '.DS_Store') return false; - if (base.endsWith('.log')) return false; - return true; - }; - - copyDirSync(iconsDir, tmpDir, { filter }); - - console.log('[3/7] 调整 package.json 配置'); - // 将 package_publish.json 重命名为 package.json - const tmpPkgJson = path.join(tmpDir, 'package.json'); - const pubPkgContent = readJson(pubPkg); - - // 更新版本号 - pubPkgContent.version = version; - - // 移除 prepublishOnly 脚本,避免在临时目录触发构建 - if (pubPkgContent.scripts) { - delete pubPkgContent.scripts.prepublishOnly; - delete pubPkgContent.scripts.prepare; - } - - writeJson(tmpPkgJson, pubPkgContent); - - console.log('[4/7] 安装依赖: pnpm install'); - runCmd('pnpm', ['install'], tmpDir); - - console.log('[5/7] 执行构建: pnpm build'); - runCmd('pnpm', ['build'], tmpDir); - - // 显示产物结构预览 - showPackageStructure(tmpDir); - - // 用户确认发包 - if (!skipConfirm) { - const confirmed = await confirmPublish(); - if (!confirmed) { - console.log('❌ 用户取消发布'); - console.log('[7/7] 清理临时目录'); - fs.rmSync(tmpDir, { recursive: true, force: true }); - return; - } - } - console.log('[6/7] 配置认证'); - runCmd('pnpm', ['config', 'set', auth], tmpDir); - console.log('[7/7] 执行发布: pnpm publish'); - runCmd( - 'pnpm', - ['publish', '--registry', registry, '--no-git-checks'], - tmpDir - ); - - console.log('✅ 发布完成'); - } catch (err) { - console.error('❌ 发布流程发生错误:', err?.message || err); - process.exitCode = 1; - - // 还原原始版本号 - try { - const pubPkgPath = path.join(iconsDir, 'package_publish.json'); - originalPubPkgContent.version = originalVersion; - writeJson(pubPkgPath, originalPubPkgContent); - console.log( - `🔄 已还原 package_publish.json 版本号为: ${originalVersion}` - ); - } catch (restoreErr) { - console.warn(`⚠️ 还原版本号失败: ${restoreErr.message}`); - } - } finally { - console.log('[7/7] 清理临时目录'); - try { - if (fs.existsSync(tmpDir)) { - fs.rmSync(tmpDir, { recursive: true, force: true }); - console.log('✅ 已清理临时目录'); - } - } catch (cleanupErr) { - console.warn(`⚠️ 清理临时目录失败: ${cleanupErr.message}`); - } - } -} - -main(); diff --git a/scripts/cli/dms-kit-publish/src/config/environment.ts b/scripts/cli/dms-kit-publish/src/config/environment.ts index 056b7adcc..9eb688cc6 100644 --- a/scripts/cli/dms-kit-publish/src/config/environment.ts +++ b/scripts/cli/dms-kit-publish/src/config/environment.ts @@ -1,6 +1,6 @@ -import chalk from 'chalk'; -import 'dotenv/config'; import type { EnvironmentConfig } from '../types/index'; +import { errorLog, infoLog } from '../utils/logger'; +import 'dotenv/config'; // 获取当前环境 export const ENV = process.env.DEPLOY_ENV || 'production'; @@ -85,10 +85,8 @@ const environments: Record = { // 验证环境并获取配置 if (!environments[ENV]) { - console.error(chalk.red(`不支持的环境: ${ENV}`)); - console.error( - chalk.red(`支持的环境: ${Object.keys(environments).join(', ')}`) - ); + errorLog(`不支持的环境: ${ENV}`); + errorLog(`支持的环境: ${Object.keys(environments).join(', ')}`); process.exit(1); } @@ -96,4 +94,4 @@ if (!environments[ENV]) { export const config = environments[ENV]; // 输出当前环境信息 -console.log(chalk.blue(`\n当前部署环境: ${ENV}\n`)); +infoLog(`\n当前部署环境: ${ENV}\n`); diff --git a/scripts/cli/dms-kit-publish/src/services/deploy.ts b/scripts/cli/dms-kit-publish/src/services/deploy.ts index 8d89a75ec..efc07d6e0 100644 --- a/scripts/cli/dms-kit-publish/src/services/deploy.ts +++ b/scripts/cli/dms-kit-publish/src/services/deploy.ts @@ -23,6 +23,7 @@ import { compressFolder } from '../utils/compress'; import { VersionValidator } from './validator'; import { NotificationService } from './notification'; import axios from 'axios'; +import { getPnpmEnv } from '../utils/exec'; /** * DMS UI 部署主类 @@ -335,12 +336,18 @@ ${this.pkgs.map((p) => ` - ${p.dir}-v${p.version}`).join('\n')} // 检查包是否已发布 let isPublished = false; try { - const { stdout } = await execa('pnpm', [ - 'info', - `${pkg.name}@${pkg.version}`, - '--registry', - config.pnpm.registry - ]); + const { stdout } = await execa( + 'pnpm', + [ + 'info', + `${pkg.name}@${pkg.version}`, + '--registry', + config.pnpm.registry + ], + { + env: getPnpmEnv() + } + ); if (stdout.length) { isPublished = true; } @@ -356,27 +363,15 @@ ${this.pkgs.map((p) => ` - ${p.dir}-v${p.version}`).join('\n')} try { stepLog(`发布包 ${pkg.name}@${pkg.version}`); if (pkg.dir === 'icons') { - await execa( - 'node', - [ - 'publish-icons.mjs', - '--skip-confirm', - '--registry', - config.pnpm.registry, - '--auth', - config.pnpm.auth - ], - { - cwd: path.join(this.cwd, 'packages', 'icons') - } - ); + await this.publishIconsPackage(pkg); continue; } const pkgPath = path.join(this.cwd, 'packages', pkg.dir); // 设置 npm 认证 await execa('pnpm', ['config', 'set', config.pnpm.auth], { - cwd: pkgPath + cwd: pkgPath, + env: getPnpmEnv() }); // 发布包 @@ -384,7 +379,8 @@ ${this.pkgs.map((p) => ` - ${p.dir}-v${p.version}`).join('\n')} 'pnpm', ['publish', '--registry', config.pnpm.registry, '--no-git-checks'], { - cwd: pkgPath + cwd: pkgPath, + env: getPnpmEnv() } ); @@ -497,7 +493,8 @@ ${this.pkgs.map((p) => ` - ${p.dir}-v${p.version}`).join('\n')} // icons 包特殊处理 if (pkg.dir === 'icons') { await execa('pnpm', ['docs:g'], { - cwd: path.join(this.cwd, 'packages', 'icons') + cwd: path.join(this.cwd, 'packages', 'icons'), + env: getPnpmEnv() }); } @@ -523,7 +520,8 @@ ${this.pkgs.map((p) => ` - ${p.dir}-v${p.version}`).join('\n')} try { // 构建文档 await execa('pnpm', ['docs:build'], { - cwd: path.join(this.cwd, 'packages', pkg.dir) + cwd: path.join(this.cwd, 'packages', pkg.dir), + env: getPnpmEnv() }); // 验证构建产物 @@ -736,6 +734,174 @@ ${this.pkgs.map((p) => ` - ${p.dir}-v${p.version}`).join('\n')} infoLog('临时文件清理完成'); } + /** + * 发布 icons 包 + */ + private async publishIconsPackage(pkg: PackageInfo) { + const iconsDir = path.join(this.cwd, 'packages', 'icons'); + const pubPkgPath = path.join(iconsDir, 'package_publish.json'); + + // 1. 验证 package_publish.json 存在 + if (!fs.existsSync(pubPkgPath)) { + throw new DeployError( + ErrorCode.PKG_LOAD_FAILED, + `发布用 package_publish.json 不存在: ${pubPkgPath}` + ); + } + + // 2. 读取原始 package_publish.json 内容(用于错误恢复) + const originalPubPkgContent = JSON.parse( + fs.readFileSync(pubPkgPath, 'utf-8') + ); + const originalVersion = originalPubPkgContent.version; + + // 3. 创建临时目录 + const tmpDir = path.join(this.cwd, 'packages', 'actiontech-icons-publish'); + + try { + infoLog(' [1/7] 创建临时目录'); + fs.mkdirSync(tmpDir, { recursive: true }); + + // 4. 复制 icons 包到临时目录(排除不需要的文件) + infoLog(' [2/7] 复制 icons 包到临时目录'); + const excludeTopLevel = new Set([ + 'node_modules', + 'dist', + 'es', + '.git', + '.github', + 'coverage', + '.nyc_output' + ]); + + const filter = (filePath: string) => { + const relativePath = path.relative(iconsDir, filePath); + if (!relativePath) return true; // 根目录 + const segments = relativePath.split(path.sep); + const top = segments[0]; + if (excludeTopLevel.has(top)) return false; + const base = path.basename(filePath); + if (base === '.DS_Store') return false; + if (base.endsWith('.log')) return false; + return true; + }; + + this.copyDirSync(iconsDir, tmpDir, { filter }); + + // 5. 调整 package.json 配置 + infoLog(' [3/7] 调整 package.json 配置'); + const tmpPkgJson = path.join(tmpDir, 'package.json'); + const pubPkgContent = JSON.parse(fs.readFileSync(pubPkgPath, 'utf-8')); + + // 更新版本号 + pubPkgContent.version = pkg.version; + + // 移除 prepublishOnly 和 prepare 脚本 + if (pubPkgContent.scripts) { + delete pubPkgContent.scripts.prepublishOnly; + delete pubPkgContent.scripts.prepare; + } + + fs.writeFileSync( + tmpPkgJson, + JSON.stringify(pubPkgContent, null, 2) + '\n' + ); + + // 6. 安装依赖 + infoLog(' [4/7] 安装依赖: pnpm install'); + await execa('pnpm', ['install'], { cwd: tmpDir, env: getPnpmEnv() }); + + // 7. 执行构建 + infoLog(' [5/7] 执行构建: pnpm build'); + await execa('pnpm', ['build'], { cwd: tmpDir, env: getPnpmEnv() }); + + // 8. 配置认证 + infoLog(' [6/7] 配置认证'); + await execa('pnpm', ['config', 'set', config.pnpm.auth], { + cwd: tmpDir, + env: getPnpmEnv() + }); + + // 9. 执行发布 + infoLog(' [7/7] 执行发布: pnpm publish'); + await execa( + 'pnpm', + ['publish', '--registry', config.pnpm.registry, '--no-git-checks'], + { + cwd: tmpDir, + env: getPnpmEnv() + } + ); + + successLog(`包 ${pkg.name}@${pkg.version} 发布成功`); + } catch (error: any) { + // 还原原始版本号 + try { + originalPubPkgContent.version = originalVersion; + fs.writeFileSync( + pubPkgPath, + JSON.stringify(originalPubPkgContent, null, 2) + '\n' + ); + warnLog(`已还原 package_publish.json 版本号为: ${originalVersion}`); + } catch (restoreErr: any) { + warnLog(`还原版本号失败: ${restoreErr.message}`); + } + + throw new DeployError( + ErrorCode.NPM_PUBLISH_FAILED, + `包 ${pkg.name}@${pkg.version} 发布失败`, + { originalError: error.message } + ); + } finally { + // 10. 清理临时目录 + infoLog(' [清理] 清理临时目录'); + try { + if (fs.existsSync(tmpDir)) { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + } catch (cleanupErr: any) { + warnLog(`清理临时目录失败: ${cleanupErr.message}`); + } + } + } + + /** + * 复制目录(支持过滤) + */ + private copyDirSync( + src: string, + dest: string, + options: { + filter?: (filePath: string) => boolean; + dereference?: boolean; + } = {} + ) { + const { filter = () => true, dereference = false } = options; + + const statFn = dereference ? fs.statSync : fs.lstatSync; + + const copyItem = (from: string, to: string) => { + if (!filter(from)) return; + const stat = statFn(from); + if (stat.isDirectory()) { + if (!fs.existsSync(to)) { + fs.mkdirSync(to, { recursive: true }); + } + const entries = fs.readdirSync(from); + for (const entry of entries) { + copyItem(path.join(from, entry), path.join(to, entry)); + } + } else if (stat.isSymbolicLink()) { + const real = fs.readlinkSync(from); + fs.symlinkSync(real, to); + } else if (stat.isFile()) { + fs.copyFileSync(from, to); + } + }; + + copyItem(src, dest); + } + /** * 错误处理 */ diff --git a/scripts/cli/dms-kit-publish/src/services/notification.ts b/scripts/cli/dms-kit-publish/src/services/notification.ts index a9add3b9a..d288494a5 100644 --- a/scripts/cli/dms-kit-publish/src/services/notification.ts +++ b/scripts/cli/dms-kit-publish/src/services/notification.ts @@ -1,5 +1,6 @@ import nodemailer from 'nodemailer'; import { config, ENV } from '../config/index'; +import { errorLog, infoLog } from '../utils/logger'; /** * 通知服务 @@ -32,7 +33,7 @@ export class NotificationService { isError = false ): Promise { if (!this.transporter || !config.email) { - console.log('邮件通知未配置,跳过发送'); + infoLog('邮件通知未配置,跳过发送'); return; } @@ -75,10 +76,9 @@ export class NotificationService { html }); - console.log('邮件通知发送成功'); + infoLog('邮件通知发送成功'); } catch (error: any) { - console.error('邮件通知发送失败:', error.message); - // 不抛出错误,避免影响主流程 + errorLog(`邮件通知发送失败: ${error.message}`); } } } diff --git a/scripts/cli/dms-kit-publish/src/services/validator.ts b/scripts/cli/dms-kit-publish/src/services/validator.ts index 756b17e5f..7acac8d38 100644 --- a/scripts/cli/dms-kit-publish/src/services/validator.ts +++ b/scripts/cli/dms-kit-publish/src/services/validator.ts @@ -3,7 +3,7 @@ import { execa } from 'execa'; import semver from 'semver'; import { config } from '../config/index'; import { ErrorCode, DeployError } from '../types/index'; -import { successLog, warnLog } from '../utils/logger'; +import { infoLog, successLog, warnLog } from '../utils/logger'; /** * 版本校验器 @@ -30,7 +30,6 @@ export class VersionValidator { newVersion: string ): Promise { try { - console.log(config.pnpm.registry, 'config.pnpm.registry'); const { stdout } = await execa('pnpm', [ 'view', pkgName, @@ -68,7 +67,7 @@ export class VersionValidator { } catch (error: any) { // 如果包还未发布,跳过检查 if (error.message && error.message.includes('404')) { - console.log(`包 ${pkgName} 首次发布`); + infoLog(`包 ${pkgName} 首次发布`); return true; } throw error; diff --git a/scripts/cli/dms-kit-publish/src/utils/changelog.ts b/scripts/cli/dms-kit-publish/src/utils/changelog.ts index a420e8546..896163dcf 100644 --- a/scripts/cli/dms-kit-publish/src/utils/changelog.ts +++ b/scripts/cli/dms-kit-publish/src/utils/changelog.ts @@ -1,4 +1,5 @@ import * as fs from 'fs'; +import { errorLog, infoLog } from './logger'; /** * 从 CHANGELOG.md 中提取指定版本的更新内容 @@ -33,10 +34,10 @@ export function getChangelogForVersion( return content; } - console.log(`未找到版本 ${version} 的 changelog`); + infoLog(`未找到版本 ${version} 的 changelog`); return `未找到版本 ${version} 的 changelog`; - } catch (error) { - console.error('读取 changelog.md 文件失败:', error); - return `读取 changelog.md 文件失败: ${error}`; + } catch (error: any) { + errorLog(`读取 changelog.md 文件失败: ${error.message}`); + return `读取 changelog.md 文件失败: ${error.message}`; } } diff --git a/scripts/cli/dms-kit-publish/src/utils/exec.ts b/scripts/cli/dms-kit-publish/src/utils/exec.ts new file mode 100644 index 000000000..8acecaf00 --- /dev/null +++ b/scripts/cli/dms-kit-publish/src/utils/exec.ts @@ -0,0 +1,25 @@ +import * as os from 'os'; +import * as path from 'path'; + +/** + * 获取 pnpm/npm 命令的环境变量配置 + * 解决 CI/CD 环境中的缓存目录权限问题 + */ +export function getPnpmEnv(): Record { + const tempDir = os.tmpdir(); + const npmCacheDir = path.join(tempDir, '.npm-cache'); + const pnpmCacheDir = path.join(tempDir, '.pnpm-store'); + const npmrcPath = path.join(tempDir, '.npmrc'); + + return { + ...process.env, + npm_config_cache: npmCacheDir, + NPM_CONFIG_CACHE: npmCacheDir, + PNPM_HOME: pnpmCacheDir, + npm_config_userconfig: npmrcPath, + NPM_CONFIG_USERCONFIG: npmrcPath, + npm_config_globalconfig: '/dev/null', + NPM_CONFIG_GLOBALCONFIG: '/dev/null', + npm_config_prefer_offline: 'false' + } as Record; +}