@wwkit/shared 1.0.28 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/README.md +34 -34
- package/package.json +1 -1
- package/src/args.js +79 -79
- package/src/config-loader.js +250 -250
- package/src/http.js +482 -482
- package/src/index.js +4 -4
- package/src/release.js +282 -215
- package/src/shell.js +569 -569
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { createConfigLoader, getXdgConfigDir, deepMerge, resolveEnvVars, resolvePkgVars } from './config-loader.js'
|
|
2
|
-
export { Shell } from './shell.js'
|
|
3
|
-
export { parseProxy, openTunnel, fetchText, downloadToFile } from './http.js'
|
|
4
|
-
export { parseFlags, DEFAULT_BOOLEAN_FLAGS } from './args.js'
|
|
1
|
+
export { createConfigLoader, getXdgConfigDir, deepMerge, resolveEnvVars, resolvePkgVars } from './config-loader.js'
|
|
2
|
+
export { Shell } from './shell.js'
|
|
3
|
+
export { parseProxy, openTunnel, fetchText, downloadToFile } from './http.js'
|
|
4
|
+
export { parseFlags, DEFAULT_BOOLEAN_FLAGS } from './args.js'
|
|
5
5
|
export { default as JSON5 } from 'json5'
|
package/src/release.js
CHANGED
|
@@ -1,215 +1,282 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process'
|
|
2
|
-
import { readFileSync } from 'node:fs'
|
|
3
|
-
import { resolve } from 'node:path'
|
|
4
|
-
|
|
5
|
-
// ════════════════════════════ 核心 API ════════════════════════════
|
|
6
|
-
|
|
7
|
-
// 发布过程错误。stage 标记失败阶段(validate|bump|publish|git);
|
|
8
|
-
// published=true 表示新版本已上 npm——此时调用方【不得】rollbackVersion,
|
|
9
|
-
// 而应保留版本号并按 repair 命令手动补齐 git 记录(否则下次发布会撞 E403 重复版本)。
|
|
10
|
-
export class ReleaseError extends Error {
|
|
11
|
-
constructor(message, { stage, published = false, version = null, repair = [] } = {}) {
|
|
12
|
-
super(message)
|
|
13
|
-
this.name = 'ReleaseError'
|
|
14
|
-
this.stage = stage
|
|
15
|
-
this.published = published
|
|
16
|
-
this.version = version
|
|
17
|
-
this.repair = repair
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// patch bump,仅支持稳定版 x.y.z;预发布/异常格式直接抛错(fail-fast)。
|
|
22
|
-
export function bumpPatch(version) {
|
|
23
|
-
if (!/^\d+\.\d+\.\d+$/.test(String(version))) {
|
|
24
|
-
throw new Error(`版本号格式不支持自动 bump(需 x.y.z): ${version}`)
|
|
25
|
-
}
|
|
26
|
-
const [major, minor, patch] = version.split('.').map(Number)
|
|
27
|
-
return `${major}.${minor}.${patch + 1}`
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// 发布前检查: git 工作区干净(不干净仅告警)+ npm 已登录(未登录抛出 Error)。
|
|
31
|
-
// pkgDirs 给定时,按各包 publishConfig.registry【去重】逐一检查登录——
|
|
32
|
-
// 发布实际走 publishConfig(如 npmjs.org),而非 .npmrc 里可能指向镜像的默认 registry。
|
|
33
|
-
export function preflight({ pkgDirs = [] } = {}) {
|
|
34
|
-
const gitStatus = runQuiet('git status --porcelain')
|
|
35
|
-
if (gitStatus) {
|
|
36
|
-
console.warn('⚠ Git 工作区不干净(仅告警,继续发布)')
|
|
37
|
-
console.warn(gitStatus)
|
|
38
|
-
} else {
|
|
39
|
-
console.log(' ✓ Git 工作区干净')
|
|
40
|
-
}
|
|
41
|
-
const registries = [...new Set(pkgDirs.map(resolvePublishRegistry).filter(Boolean))]
|
|
42
|
-
const targets = registries.length > 0 ? registries : [null]
|
|
43
|
-
for (const registry of targets) {
|
|
44
|
-
const whoamiCmd = registry ? `npm whoami --registry=${registry}` : 'npm whoami'
|
|
45
|
-
try {
|
|
46
|
-
const who = runQuiet(whoamiCmd)
|
|
47
|
-
console.log(` ✓ npm 已登录: ${who}${registry ? ` (${registry})` : ''}`)
|
|
48
|
-
} catch {
|
|
49
|
-
console.error('✗ 未登录 npm,请先执行 npm login')
|
|
50
|
-
if (registry) console.error(` (检查的 registry: ${registry})`)
|
|
51
|
-
throw new Error('npm-not-logged-in')
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 发布门禁: 依次在各包目录跑其标准入口 `pnpm run ut`,任一失败返回 false(调用方须中止发布)。
|
|
57
|
-
// dryRun 只打印计划命令,不执行。
|
|
58
|
-
export function runUnitTests(pkgDirs, { dryRun = false } = {}) {
|
|
59
|
-
console.log('\n━━━ 单元测试(发布门禁) ━━━')
|
|
60
|
-
for (const pkgDir of pkgDirs) {
|
|
61
|
-
const { name } = readPackage(pkgDir)
|
|
62
|
-
if (dryRun) {
|
|
63
|
-
console.log(` $ pnpm run ut # ${name}`)
|
|
64
|
-
continue
|
|
65
|
-
}
|
|
66
|
-
console.log(`▶ ${name}: pnpm run ut`)
|
|
67
|
-
try {
|
|
68
|
-
execSync('pnpm run ut', { cwd: pkgDir, stdio: 'inherit' })
|
|
69
|
-
} catch {
|
|
70
|
-
console.error(`\n✗ ${name} 单元测试未通过,发布已阻止(修复测试后重试)`)
|
|
71
|
-
return false
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (!dryRun) console.log(' ✓ 全部包单元测试通过')
|
|
75
|
-
return true
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
} catch {
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
3
|
+
import { resolve, join, dirname, basename } from 'node:path'
|
|
4
|
+
|
|
5
|
+
// ════════════════════════════ 核心 API ════════════════════════════
|
|
6
|
+
|
|
7
|
+
// 发布过程错误。stage 标记失败阶段(validate|bump|publish|git);
|
|
8
|
+
// published=true 表示新版本已上 npm——此时调用方【不得】rollbackVersion,
|
|
9
|
+
// 而应保留版本号并按 repair 命令手动补齐 git 记录(否则下次发布会撞 E403 重复版本)。
|
|
10
|
+
export class ReleaseError extends Error {
|
|
11
|
+
constructor(message, { stage, published = false, version = null, repair = [] } = {}) {
|
|
12
|
+
super(message)
|
|
13
|
+
this.name = 'ReleaseError'
|
|
14
|
+
this.stage = stage
|
|
15
|
+
this.published = published
|
|
16
|
+
this.version = version
|
|
17
|
+
this.repair = repair
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// patch bump,仅支持稳定版 x.y.z;预发布/异常格式直接抛错(fail-fast)。
|
|
22
|
+
export function bumpPatch(version) {
|
|
23
|
+
if (!/^\d+\.\d+\.\d+$/.test(String(version))) {
|
|
24
|
+
throw new Error(`版本号格式不支持自动 bump(需 x.y.z): ${version}`)
|
|
25
|
+
}
|
|
26
|
+
const [major, minor, patch] = version.split('.').map(Number)
|
|
27
|
+
return `${major}.${minor}.${patch + 1}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 发布前检查: git 工作区干净(不干净仅告警)+ npm 已登录(未登录抛出 Error)。
|
|
31
|
+
// pkgDirs 给定时,按各包 publishConfig.registry【去重】逐一检查登录——
|
|
32
|
+
// 发布实际走 publishConfig(如 npmjs.org),而非 .npmrc 里可能指向镜像的默认 registry。
|
|
33
|
+
export function preflight({ pkgDirs = [] } = {}) {
|
|
34
|
+
const gitStatus = runQuiet('git status --porcelain')
|
|
35
|
+
if (gitStatus) {
|
|
36
|
+
console.warn('⚠ Git 工作区不干净(仅告警,继续发布)')
|
|
37
|
+
console.warn(gitStatus)
|
|
38
|
+
} else {
|
|
39
|
+
console.log(' ✓ Git 工作区干净')
|
|
40
|
+
}
|
|
41
|
+
const registries = [...new Set(pkgDirs.map(resolvePublishRegistry).filter(Boolean))]
|
|
42
|
+
const targets = registries.length > 0 ? registries : [null]
|
|
43
|
+
for (const registry of targets) {
|
|
44
|
+
const whoamiCmd = registry ? `npm whoami --registry=${registry}` : 'npm whoami'
|
|
45
|
+
try {
|
|
46
|
+
const who = runQuiet(whoamiCmd)
|
|
47
|
+
console.log(` ✓ npm 已登录: ${who}${registry ? ` (${registry})` : ''}`)
|
|
48
|
+
} catch {
|
|
49
|
+
console.error('✗ 未登录 npm,请先执行 npm login')
|
|
50
|
+
if (registry) console.error(` (检查的 registry: ${registry})`)
|
|
51
|
+
throw new Error('npm-not-logged-in')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 发布门禁: 依次在各包目录跑其标准入口 `pnpm run ut`,任一失败返回 false(调用方须中止发布)。
|
|
57
|
+
// dryRun 只打印计划命令,不执行。
|
|
58
|
+
export function runUnitTests(pkgDirs, { dryRun = false } = {}) {
|
|
59
|
+
console.log('\n━━━ 单元测试(发布门禁) ━━━')
|
|
60
|
+
for (const pkgDir of pkgDirs) {
|
|
61
|
+
const { name } = readPackage(pkgDir)
|
|
62
|
+
if (dryRun) {
|
|
63
|
+
console.log(` $ pnpm run ut # ${name}`)
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
console.log(`▶ ${name}: pnpm run ut`)
|
|
67
|
+
try {
|
|
68
|
+
execSync('pnpm run ut', { cwd: pkgDir, stdio: 'inherit' })
|
|
69
|
+
} catch {
|
|
70
|
+
console.error(`\n✗ ${name} 单元测试未通过,发布已阻止(修复测试后重试)`)
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!dryRun) console.log(' ✓ 全部包单元测试通过')
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ════════════════════════════ 发布日志 ════════════════════════════
|
|
79
|
+
|
|
80
|
+
// 创建 release 日志收集器。close() 时写入 <root>/logs/release/<dirName>/<version>-<timestamp>.log。
|
|
81
|
+
// dryRun 时不写文件(返回 no-op close)。在 releasePackage 内部使用,记录各阶段命令与结果。
|
|
82
|
+
export function createReleaseLog(pkgDir, version, { dryRun = false } = {}) {
|
|
83
|
+
const start = new Date()
|
|
84
|
+
const lines = [
|
|
85
|
+
'# Release Log',
|
|
86
|
+
`# 包目录: ${pkgDir}`,
|
|
87
|
+
`# 目标版本: ${version}`,
|
|
88
|
+
`# 开始时间: ${start.toISOString()}`,
|
|
89
|
+
'',
|
|
90
|
+
]
|
|
91
|
+
const ts = () => new Date().toISOString()
|
|
92
|
+
return {
|
|
93
|
+
section(title) { lines.push('', `── ${title} ──`) },
|
|
94
|
+
log(msg) { lines.push(`[${ts()}] ${msg}`) },
|
|
95
|
+
close({ success = true, error = null } = {}) {
|
|
96
|
+
if (dryRun) return null
|
|
97
|
+
const end = new Date()
|
|
98
|
+
lines.push(
|
|
99
|
+
'',
|
|
100
|
+
`# 结束时间: ${end.toISOString()}`,
|
|
101
|
+
`# 耗时: ${((end - start) / 1000).toFixed(1)}s`,
|
|
102
|
+
`# 结果: ${success ? '成功' : '失败'}`,
|
|
103
|
+
)
|
|
104
|
+
if (error) lines.push(`# 错误: ${error}`)
|
|
105
|
+
const root = findMonorepoRoot(pkgDir)
|
|
106
|
+
const dirName = basename(pkgDir)
|
|
107
|
+
const stamp = formatTimestamp(start)
|
|
108
|
+
const logPath = join(root, 'logs', 'release', dirName, `${version}-${stamp}.log`)
|
|
109
|
+
mkdirSync(dirname(logPath), { recursive: true })
|
|
110
|
+
writeFileSync(logPath, lines.join('\n') + '\n', 'utf-8')
|
|
111
|
+
return logPath
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function findMonorepoRoot(dir) {
|
|
117
|
+
let d = dir
|
|
118
|
+
for (let i = 0; i < 10; i++) {
|
|
119
|
+
if (existsSync(join(d, 'pnpm-workspace.yaml'))) return d
|
|
120
|
+
d = dirname(d)
|
|
121
|
+
}
|
|
122
|
+
return dir
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatTimestamp(d) {
|
|
126
|
+
const p = (n) => String(n).padStart(2, '0')
|
|
127
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 单包发布: bump patch → publish → commit + scoped tag(不推送)。
|
|
131
|
+
// 成功返回 { name, oldVersion, version, tag };失败抛出 ReleaseError(含阶段与 published 标记)。
|
|
132
|
+
export function releasePackage({ pkgDir, dryRun = false } = {}) {
|
|
133
|
+
const { pkgPath, name, version: oldVer } = readPackage(pkgDir)
|
|
134
|
+
if (!name) throw new ReleaseError(`无法从 ${pkgPath} 读取 name`, { stage: 'validate' })
|
|
135
|
+
let newVer
|
|
136
|
+
try {
|
|
137
|
+
newVer = bumpPatch(oldVer)
|
|
138
|
+
} catch (e) {
|
|
139
|
+
throw new ReleaseError(e.message, { stage: 'validate' })
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(`━━━ 发布 ${name}@${newVer}(原 ${oldVer})━━━`)
|
|
143
|
+
|
|
144
|
+
const log = createReleaseLog(pkgDir, newVer, { dryRun })
|
|
145
|
+
log.section(`发布 ${name}@${newVer}(原 ${oldVer})`)
|
|
146
|
+
|
|
147
|
+
// 1. bump version (no git tag — scoped tag created manually below)
|
|
148
|
+
log.log('阶段 1/3: bump version')
|
|
149
|
+
try {
|
|
150
|
+
run('pnpm version patch --no-git-tag-version --no-git-checks', { cwd: pkgDir, dryRun })
|
|
151
|
+
log.log('bump 完成')
|
|
152
|
+
} catch {
|
|
153
|
+
log.close({ success: false, error: 'bump 失败' })
|
|
154
|
+
throw new ReleaseError(`${name} 版本号 bump 失败`, { stage: 'bump' })
|
|
155
|
+
}
|
|
156
|
+
// 以 bump 后 package.json 的【实际版本】为准(dry-run 未实际 bump,用预测值)
|
|
157
|
+
const actualVer = dryRun ? newVer : readPackage(pkgDir).version
|
|
158
|
+
const tag = `${name}@${actualVer}`
|
|
159
|
+
|
|
160
|
+
// 2. publish (prepublishOnly hook runs tests automatically)
|
|
161
|
+
log.log('阶段 2/3: publish')
|
|
162
|
+
try {
|
|
163
|
+
run('pnpm publish --no-git-checks', { cwd: pkgDir, dryRun })
|
|
164
|
+
log.log(`publish 完成: ${tag}`)
|
|
165
|
+
} catch {
|
|
166
|
+
log.close({ success: false, error: `publish 失败: ${tag}` })
|
|
167
|
+
throw new ReleaseError(`${name}@${actualVer} 发布失败`, { stage: 'publish', version: actualVer })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 3. commit + scoped tag —— 自此版本已在 npm 上,失败时不得再回滚版本号。
|
|
171
|
+
// --only 仅提交该 package.json,避免把用户预先 staged 的无关改动裹进 release commit。
|
|
172
|
+
log.log('阶段 3/3: git commit + tag')
|
|
173
|
+
const repair = [
|
|
174
|
+
`git commit --only -m "release ${tag}" -- "${pkgPath}"`,
|
|
175
|
+
`git tag ${tag}`,
|
|
176
|
+
]
|
|
177
|
+
try {
|
|
178
|
+
if (!dryRun) {
|
|
179
|
+
execSync(`git commit --only -m "release ${tag}" -- "${pkgPath}"`, { stdio: 'inherit' })
|
|
180
|
+
execSync(`git tag ${tag}`, { stdio: 'inherit' })
|
|
181
|
+
log.log(`git commit + tag 完成: ${tag}`)
|
|
182
|
+
} else {
|
|
183
|
+
console.log(` $ ${repair.join(' && ')}`)
|
|
184
|
+
log.log(`[dry-run] ${repair.join(' && ')}`)
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
log.close({ success: false, error: `git 记录失败(版本已上 npm): ${tag}` })
|
|
188
|
+
throw new ReleaseError(`${name}@${actualVer} 已发布但 git 记录失败`, {
|
|
189
|
+
stage: 'git', published: true, version: actualVer, repair,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const logPath = log.close({ success: true })
|
|
194
|
+
if (logPath) console.log(` 📄 日志: ${logPath}`)
|
|
195
|
+
return { name, oldVersion: oldVer, version: actualVer, tag }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 回滚 package.json 版本号。仅适用于【未发布】的失败包;
|
|
199
|
+
// 已发布(ReleaseError.published)的包调用本函数会造成 npm 与仓库版本撕裂。
|
|
200
|
+
export function rollbackVersion(pkgDir, { dryRun = false } = {}) {
|
|
201
|
+
if (dryRun) return
|
|
202
|
+
try {
|
|
203
|
+
execSync('git checkout -- package.json', { cwd: pkgDir, stdio: 'pipe' })
|
|
204
|
+
console.log(' 已回滚版本号')
|
|
205
|
+
} catch {
|
|
206
|
+
console.warn(' ⚠ 版本号回滚失败,请手动检查该包 package.json')
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 推送到远端同名分支 + 指定 tag(s),并追加同步到 alsoBranches(如 develop,HEAD:<branch>)。
|
|
211
|
+
// 返回 true 成功/已打印计划,false 失败或跳过(需手动推)。
|
|
212
|
+
// 推送异常被捕获并打印手动命令,不向上抛裸栈。
|
|
213
|
+
export function pushToRemote(tags, { dryRun = false, alsoBranches = [] } = {}) {
|
|
214
|
+
console.log('\n━━━ 推送到远端 ━━━')
|
|
215
|
+
|
|
216
|
+
let remote
|
|
217
|
+
try {
|
|
218
|
+
const remotes = runQuiet('git remote').split('\n').filter(Boolean)
|
|
219
|
+
remote = remotes.includes('origin') ? 'origin' : remotes[0]
|
|
220
|
+
} catch {
|
|
221
|
+
remote = null
|
|
222
|
+
}
|
|
223
|
+
if (!remote) {
|
|
224
|
+
console.error(' ✗ 未配置 git remote,请手动: git push && git push --tags')
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let branch
|
|
229
|
+
try {
|
|
230
|
+
branch = runQuiet('git rev-parse --abbrev-ref HEAD')
|
|
231
|
+
} catch {
|
|
232
|
+
console.error(' ✗ 无法获取当前分支,请手动: git push && git push --tags')
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const cmds = [`git push ${remote} ${branch}`]
|
|
237
|
+
if (tags.length > 0) cmds.push(`git push ${remote} ${tags.join(' ')}`)
|
|
238
|
+
// 额外分支同步(如 develop):推当前 HEAD 到远端目标分支,跳过与当前分支同名的
|
|
239
|
+
for (const b of alsoBranches) {
|
|
240
|
+
if (b && b !== branch) cmds.push(`git push ${remote} HEAD:${b}`)
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
for (const cmd of cmds) run(cmd, { dryRun })
|
|
244
|
+
} catch {
|
|
245
|
+
console.error(' ✗ 推送失败,请手动执行:')
|
|
246
|
+
for (const cmd of cmds) console.error(` ${cmd}`)
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
if (!dryRun) console.log(' ✓ 推送完成')
|
|
250
|
+
return true
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ════════════════════════════ 辅助方法 ════════════════════════════
|
|
254
|
+
|
|
255
|
+
export function readPackage(pkgDir) {
|
|
256
|
+
const pkgPath = resolve(pkgDir, 'package.json')
|
|
257
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
258
|
+
return { pkg, pkgPath, name: pkg.name, version: pkg.version }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function runQuiet(cmd, opts = {}) {
|
|
262
|
+
return execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], ...opts }).trim()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function run(cmd, { dryRun = false, ...execOpts } = {}) {
|
|
266
|
+
if (dryRun) { console.log(` $ ${cmd}`); return }
|
|
267
|
+
execSync(cmd, { stdio: 'inherit', ...execOpts })
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 解析实际发布用的 registry:优先 package.json 的 publishConfig.registry,
|
|
271
|
+
// 否则用该目录下 npm 配置的 registry。.npmrc 可能指向镜像,但发布走 publishConfig。
|
|
272
|
+
function resolvePublishRegistry(pkgDir) {
|
|
273
|
+
try {
|
|
274
|
+
const { pkg } = readPackage(pkgDir)
|
|
275
|
+
if (pkg.publishConfig?.registry) return pkg.publishConfig.registry
|
|
276
|
+
} catch {}
|
|
277
|
+
try {
|
|
278
|
+
return runQuiet('npm config get registry', { cwd: pkgDir })
|
|
279
|
+
} catch {
|
|
280
|
+
return null
|
|
281
|
+
}
|
|
282
|
+
}
|