alemonjs 2.1.87 → 2.1.89
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/bin/README.md +22 -0
- package/bin/alemonc.js +20 -1
- package/bin/publish.js +426 -0
- package/package.json +1 -1
package/bin/README.md
CHANGED
|
@@ -100,6 +100,28 @@ alemonc version update
|
|
|
100
100
|
|
|
101
101
|
读取本地 `package.json`,查找所有 `alemonjs` 和 `@alemonjs/*` 依赖,检查并更新到最新版本。
|
|
102
102
|
|
|
103
|
+
#### publish — 智能发布当前包
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
alemonc publish
|
|
107
|
+
alemonc publish patch
|
|
108
|
+
alemonc publish --dry-run
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
行为说明:
|
|
112
|
+
|
|
113
|
+
- 默认会查询 git tag 历史作为版本基线
|
|
114
|
+
- 不传参数时:
|
|
115
|
+
- 如果本地版本高于最新 tag,直接发布本地版本
|
|
116
|
+
- 否则自动按最新 tag `patch +1`
|
|
117
|
+
- 传 `patch/minor/major/prepatch/preminor/premajor/prerelease` 时会自动递增
|
|
118
|
+
- 传具体版本号时会直接以该版本发布
|
|
119
|
+
- 默认先执行 `npm run build`
|
|
120
|
+
- 默认发布内容是 `lib/`、`package.json`、`README.md`
|
|
121
|
+
- 如果项目配置了 `.npmignore` 或 `package.json.files`,则切换为 npm 文件选择规则
|
|
122
|
+
- 最终把产物提交到 git `release` 分支,并推送对应 tag
|
|
123
|
+
- 默认要求 git 工作区干净,发布成功后会自动提交源码中的 `package.json` 版本变更
|
|
124
|
+
|
|
103
125
|
---
|
|
104
126
|
|
|
105
127
|
### 项目诊断
|
package/bin/alemonc.js
CHANGED
|
@@ -6,6 +6,7 @@ import { versionUpdate } from './versionUpdate.js';
|
|
|
6
6
|
import { info } from './info.js';
|
|
7
7
|
import { platformAdd, platformRemove, platformList } from './platform.js';
|
|
8
8
|
import { login } from './login.js';
|
|
9
|
+
import { publish } from './publish.js';
|
|
9
10
|
import { Command } from 'commander';
|
|
10
11
|
const program = new Command();
|
|
11
12
|
|
|
@@ -74,6 +75,24 @@ program
|
|
|
74
75
|
versionUpdate();
|
|
75
76
|
});
|
|
76
77
|
|
|
78
|
+
program
|
|
79
|
+
.command('publish [release]')
|
|
80
|
+
.description('智能发布当前包到 git release 仓库,支持 patch/minor/major 或直接指定版本号')
|
|
81
|
+
.option('--tag <tag>', 'git 标签通道,默认 stable 为 latest,预发布为 next')
|
|
82
|
+
.option('--preid <preid>', '预发布标识,默认 beta', 'beta')
|
|
83
|
+
.option('--branch <branch>', '发布目标分支,默认 release')
|
|
84
|
+
.option('--dry-run', '只执行检查和打包,不真正发布')
|
|
85
|
+
.option('--skip-build', '跳过构建')
|
|
86
|
+
.option('--no-git-checks', '跳过 git 干净工作区检查及发布后自动提交源码版本')
|
|
87
|
+
.action(async (release, options) => {
|
|
88
|
+
try {
|
|
89
|
+
await publish(release, options);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(error instanceof Error ? error.message : error);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
77
96
|
program
|
|
78
97
|
.command('info')
|
|
79
98
|
.description('输出项目诊断信息')
|
|
@@ -118,4 +137,4 @@ program
|
|
|
118
137
|
program.help();
|
|
119
138
|
});
|
|
120
139
|
|
|
121
|
-
program.
|
|
140
|
+
program.parseAsync(process.argv);
|
package/bin/publish.js
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { execSync, spawnSync } from 'child_process';
|
|
6
|
+
|
|
7
|
+
const RELEASE_TYPES = new Set(['patch', 'minor', 'major', 'prepatch', 'preminor', 'premajor', 'prerelease']);
|
|
8
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
9
|
+
const DEFAULT_PUBLISH_FILES = ['lib', 'package.json', 'README.md'];
|
|
10
|
+
|
|
11
|
+
function readPackageJson() {
|
|
12
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(pkgPath)) {
|
|
15
|
+
throw new Error('未找到 package.json');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
pkgPath,
|
|
20
|
+
pkg: JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runCommand(command, args, options = {}) {
|
|
25
|
+
const result = spawnSync(command, args, {
|
|
26
|
+
cwd: process.cwd(),
|
|
27
|
+
stdio: 'inherit',
|
|
28
|
+
shell: false,
|
|
29
|
+
...options
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (result.error) {
|
|
33
|
+
throw result.error;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (result.status !== 0) {
|
|
37
|
+
throw new Error(`命令执行失败: ${command} ${args.join(' ')}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getCommandOutput(command) {
|
|
42
|
+
return execSync(command, {
|
|
43
|
+
cwd: process.cwd(),
|
|
44
|
+
encoding: 'utf8',
|
|
45
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
46
|
+
}).trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function hasCommand(command) {
|
|
50
|
+
const result = spawnSync(command, ['--version'], {
|
|
51
|
+
cwd: process.cwd(),
|
|
52
|
+
stdio: 'ignore'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return !result.error;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isGitRepo() {
|
|
59
|
+
try {
|
|
60
|
+
return getCommandOutput('git rev-parse --is-inside-work-tree') === 'true';
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function ensureCleanGit() {
|
|
67
|
+
const status = getCommandOutput('git status --porcelain');
|
|
68
|
+
if (status) {
|
|
69
|
+
throw new Error('工作区存在未提交改动,请先提交或使用 --no-git-checks 跳过检查');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseVersion(version) {
|
|
74
|
+
const matched = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
75
|
+
if (!matched) {
|
|
76
|
+
throw new Error(`非法版本号: ${version}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
major: Number(matched[1]),
|
|
81
|
+
minor: Number(matched[2]),
|
|
82
|
+
patch: Number(matched[3]),
|
|
83
|
+
prerelease: matched[4] ?? null
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function compareVersions(a, b) {
|
|
88
|
+
const av = parseVersion(a);
|
|
89
|
+
const bv = parseVersion(b);
|
|
90
|
+
|
|
91
|
+
for (const key of ['major', 'minor', 'patch']) {
|
|
92
|
+
if (av[key] > bv[key]) return 1;
|
|
93
|
+
if (av[key] < bv[key]) return -1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (av.prerelease === bv.prerelease) return 0;
|
|
97
|
+
if (av.prerelease === null) return 1;
|
|
98
|
+
if (bv.prerelease === null) return -1;
|
|
99
|
+
|
|
100
|
+
return av.prerelease.localeCompare(bv.prerelease);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function incrementVersion(baseVersion, releaseType, preid = 'beta') {
|
|
104
|
+
const parsed = parseVersion(baseVersion);
|
|
105
|
+
|
|
106
|
+
if (releaseType === 'patch') {
|
|
107
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (releaseType === 'minor') {
|
|
111
|
+
return `${parsed.major}.${parsed.minor + 1}.0`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (releaseType === 'major') {
|
|
115
|
+
return `${parsed.major + 1}.0.0`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (releaseType === 'prepatch') {
|
|
119
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}-${preid}.0`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (releaseType === 'preminor') {
|
|
123
|
+
return `${parsed.major}.${parsed.minor + 1}.0-${preid}.0`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (releaseType === 'premajor') {
|
|
127
|
+
return `${parsed.major + 1}.0.0-${preid}.0`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (releaseType === 'prerelease') {
|
|
131
|
+
if (parsed.prerelease) {
|
|
132
|
+
const next = parsed.prerelease.match(/^(.*?)(\d+)$/);
|
|
133
|
+
if (next) {
|
|
134
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch}-${next[1]}${Number(next[2]) + 1}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch}-${parsed.prerelease}.1`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}-${preid}.0`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
throw new Error(`不支持的发布类型: ${releaseType}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resolveTargetVersion(localVersion, remoteVersion, release, preid) {
|
|
147
|
+
if (!release) {
|
|
148
|
+
if (!remoteVersion) {
|
|
149
|
+
return localVersion;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (compareVersions(localVersion, remoteVersion) > 0) {
|
|
153
|
+
return localVersion;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return incrementVersion(remoteVersion, 'patch', preid);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (RELEASE_TYPES.has(release)) {
|
|
160
|
+
const base = remoteVersion && compareVersions(remoteVersion, localVersion) > 0 ? remoteVersion : localVersion;
|
|
161
|
+
return incrementVersion(base, release, preid);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (SEMVER_RE.test(release)) {
|
|
165
|
+
return release;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
throw new Error(`无法识别的发布参数: ${release}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function updateVersion(pkgPath, pkg, version) {
|
|
172
|
+
pkg.version = version;
|
|
173
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getPackResult(cwd = process.cwd()) {
|
|
177
|
+
const output = execSync('npm pack --json --dry-run', {
|
|
178
|
+
cwd,
|
|
179
|
+
encoding: 'utf8',
|
|
180
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
181
|
+
maxBuffer: 20 * 1024 * 1024
|
|
182
|
+
}).trim();
|
|
183
|
+
|
|
184
|
+
const result = JSON.parse(output);
|
|
185
|
+
if (!Array.isArray(result) || result.length === 0 || !Array.isArray(result[0]?.files)) {
|
|
186
|
+
throw new Error('无法解析 npm pack 文件清单');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return result[0];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function hasNpmPublishRules(pkg) {
|
|
193
|
+
if (fs.existsSync(join(process.cwd(), '.npmignore'))) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return Array.isArray(pkg.files) && pkg.files.length > 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getDefaultPublishFiles() {
|
|
201
|
+
return DEFAULT_PUBLISH_FILES.filter(file => fs.existsSync(join(process.cwd(), file)));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function warnDefaultPublishState(pkg, publishFiles) {
|
|
205
|
+
const hasLibDir = fs.existsSync(join(process.cwd(), 'lib'));
|
|
206
|
+
const mainField = typeof pkg.main === 'string' ? pkg.main : '';
|
|
207
|
+
|
|
208
|
+
if (!hasLibDir && mainField.startsWith('./lib/')) {
|
|
209
|
+
console.warn(`警告: 缺少 lib/ 目录,但 package.json main 指向 ${mainField},将继续发布`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (publishFiles.length === 0) {
|
|
213
|
+
console.warn('警告: 默认发布规则下没有匹配到任何文件');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function copyPublishFiles(files) {
|
|
218
|
+
const publishDir = fs.mkdtempSync(join(os.tmpdir(), 'alemon-publish-'));
|
|
219
|
+
|
|
220
|
+
for (const item of files) {
|
|
221
|
+
const relativePath = typeof item === 'string' ? item : item.path;
|
|
222
|
+
const sourcePath = join(process.cwd(), relativePath);
|
|
223
|
+
const targetPath = join(publishDir, relativePath);
|
|
224
|
+
|
|
225
|
+
if (!fs.existsSync(sourcePath)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (fs.statSync(sourcePath).isDirectory()) {
|
|
230
|
+
fs.mkdirSync(join(targetPath, '..'), { recursive: true });
|
|
231
|
+
fs.cpSync(sourcePath, targetPath, { recursive: true });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
fs.mkdirSync(join(targetPath, '..'), { recursive: true });
|
|
236
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return publishDir;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function removeDirContents(dirPath) {
|
|
243
|
+
for (const entry of fs.readdirSync(dirPath)) {
|
|
244
|
+
if (entry === '.git') {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fs.rmSync(join(dirPath, entry), { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function copyDirContents(sourceDir, targetDir) {
|
|
253
|
+
for (const entry of fs.readdirSync(sourceDir)) {
|
|
254
|
+
fs.cpSync(join(sourceDir, entry), join(targetDir, entry), { recursive: true });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function ensureGitRepo() {
|
|
259
|
+
if (!isGitRepo()) {
|
|
260
|
+
throw new Error('当前目录不是 git 仓库,无法执行 git 发布');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function remoteBranchExists(branch) {
|
|
265
|
+
const result = spawnSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', branch], {
|
|
266
|
+
cwd: process.cwd(),
|
|
267
|
+
stdio: 'ignore'
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return result.status === 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function localBranchExists(branch) {
|
|
274
|
+
const result = spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`], {
|
|
275
|
+
cwd: process.cwd(),
|
|
276
|
+
stdio: 'ignore'
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return result.status === 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function createReleaseWorktree(branch) {
|
|
283
|
+
const worktreeDir = fs.mkdtempSync(join(os.tmpdir(), 'alemon-worktree-'));
|
|
284
|
+
|
|
285
|
+
if (remoteBranchExists(branch)) {
|
|
286
|
+
runCommand('git', ['fetch', 'origin', branch]);
|
|
287
|
+
runCommand('git', ['worktree', 'add', '-B', branch, worktreeDir, `origin/${branch}`]);
|
|
288
|
+
} else if (localBranchExists(branch)) {
|
|
289
|
+
runCommand('git', ['worktree', 'add', worktreeDir, branch]);
|
|
290
|
+
} else {
|
|
291
|
+
runCommand('git', ['worktree', 'add', '-b', branch, worktreeDir]);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return worktreeDir;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function cleanupWorktree(worktreeDir) {
|
|
298
|
+
if (!worktreeDir) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
spawnSync('git', ['worktree', 'remove', '--force', worktreeDir], {
|
|
303
|
+
cwd: process.cwd(),
|
|
304
|
+
stdio: 'ignore'
|
|
305
|
+
});
|
|
306
|
+
fs.rmSync(worktreeDir, { recursive: true, force: true });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function publish(release, options = {}) {
|
|
310
|
+
if (!hasCommand('git')) {
|
|
311
|
+
throw new Error('未找到 git,请先安装 git');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!hasCommand('npm')) {
|
|
315
|
+
throw new Error('未找到 npm,请先安装 Node.js/npm');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const { pkgPath, pkg } = readPackageJson();
|
|
319
|
+
const packageName = pkg.name;
|
|
320
|
+
const localVersion = String(pkg.version || '').trim();
|
|
321
|
+
|
|
322
|
+
if (!packageName) {
|
|
323
|
+
throw new Error('package.json 缺少 name');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!SEMVER_RE.test(localVersion)) {
|
|
327
|
+
throw new Error(`当前 package.json 版本号非法: ${localVersion}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(`发布包: ${packageName}`);
|
|
331
|
+
console.log(`本地版本: ${localVersion}`);
|
|
332
|
+
|
|
333
|
+
ensureGitRepo();
|
|
334
|
+
|
|
335
|
+
const releaseBranch = options.branch || 'release';
|
|
336
|
+
const remoteVersion = getCommandOutput(`git tag --list "v*" --sort=-v:refname | head -n 1`).replace(/^v/, '');
|
|
337
|
+
if (remoteVersion) {
|
|
338
|
+
console.log(`最新 git tag: v${remoteVersion}`);
|
|
339
|
+
} else {
|
|
340
|
+
console.log('最新 git tag: 无,将按首次发布处理');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (options.gitChecks !== false && isGitRepo()) {
|
|
344
|
+
ensureCleanGit();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const targetVersion = resolveTargetVersion(localVersion, remoteVersion, release, options.preid);
|
|
348
|
+
const distTag = options.tag || (targetVersion.includes('-') ? 'next' : 'latest');
|
|
349
|
+
const gitTagName = distTag === 'latest' ? `v${targetVersion}` : `v${targetVersion}-${distTag}`;
|
|
350
|
+
console.log(`目标版本: ${targetVersion}`);
|
|
351
|
+
console.log(`发布分支: ${releaseBranch}`);
|
|
352
|
+
console.log(`git 标签: ${gitTagName}`);
|
|
353
|
+
|
|
354
|
+
if (targetVersion !== localVersion) {
|
|
355
|
+
updateVersion(pkgPath, pkg, targetVersion);
|
|
356
|
+
console.log(`已更新 package.json 版本: ${localVersion} -> ${targetVersion}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let publishDir = null;
|
|
360
|
+
let worktreeDir = null;
|
|
361
|
+
try {
|
|
362
|
+
if (!options.skipBuild) {
|
|
363
|
+
if (pkg.scripts?.build) {
|
|
364
|
+
console.log('执行构建...');
|
|
365
|
+
runCommand('npm', ['run', 'build']);
|
|
366
|
+
} else {
|
|
367
|
+
console.log('未定义 build 脚本,跳过构建');
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
console.log('已跳过构建');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const useNpmRules = hasNpmPublishRules(pkg);
|
|
374
|
+
const publishFiles = useNpmRules ? getPackResult().files : getDefaultPublishFiles();
|
|
375
|
+
|
|
376
|
+
if (!useNpmRules) {
|
|
377
|
+
warnDefaultPublishState(pkg, publishFiles);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
publishDir = copyPublishFiles(publishFiles);
|
|
381
|
+
|
|
382
|
+
console.log(`发布规则: ${useNpmRules ? 'npm' : 'default'}`);
|
|
383
|
+
console.log(`发布文件数: ${publishFiles.length}`);
|
|
384
|
+
|
|
385
|
+
if (options.dryRun) {
|
|
386
|
+
console.log('dry-run 模式,不会真正推送到 git');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
console.log('准备 release worktree...');
|
|
391
|
+
worktreeDir = createReleaseWorktree(releaseBranch);
|
|
392
|
+
removeDirContents(worktreeDir);
|
|
393
|
+
copyDirContents(publishDir, worktreeDir);
|
|
394
|
+
|
|
395
|
+
runCommand('git', ['-C', worktreeDir, 'add', '-A']);
|
|
396
|
+
const hasChanges = spawnSync('git', ['-C', worktreeDir, 'diff', '--cached', '--quiet']).status !== 0;
|
|
397
|
+
if (!hasChanges) {
|
|
398
|
+
console.log('release 分支无文件变化,跳过提交');
|
|
399
|
+
} else {
|
|
400
|
+
runCommand('git', ['-C', worktreeDir, 'commit', '-m', `release: ${gitTagName}`]);
|
|
401
|
+
runCommand('git', ['-C', worktreeDir, 'push', 'origin', `HEAD:${releaseBranch}`]);
|
|
402
|
+
console.log(`已推送到分支: ${releaseBranch}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
runCommand('git', ['tag', '-f', gitTagName]);
|
|
406
|
+
runCommand('git', ['push', 'origin', gitTagName, '--force']);
|
|
407
|
+
console.log(`发布完成: ${packageName}@${targetVersion}`);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (targetVersion !== localVersion) {
|
|
410
|
+
updateVersion(pkgPath, pkg, localVersion);
|
|
411
|
+
console.log(`已回滚 package.json 版本到 ${localVersion}`);
|
|
412
|
+
}
|
|
413
|
+
throw error;
|
|
414
|
+
} finally {
|
|
415
|
+
if (publishDir) {
|
|
416
|
+
fs.rmSync(publishDir, { recursive: true, force: true });
|
|
417
|
+
}
|
|
418
|
+
cleanupWorktree(worktreeDir);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (options.gitChecks !== false && isGitRepo()) {
|
|
422
|
+
runCommand('git', ['add', 'package.json']);
|
|
423
|
+
runCommand('git', ['commit', '-m', `release: ${gitTagName}`]);
|
|
424
|
+
console.log(`已记录源码版本变更: ${gitTagName}`);
|
|
425
|
+
}
|
|
426
|
+
}
|