@spaceflow/publish 0.21.1
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/CHANGELOG.md +503 -0
- package/README.md +228 -0
- package/dist/index.js +1160 -0
- package/package.json +40 -0
- package/src/index.ts +30 -0
- package/src/locales/en/publish.json +8 -0
- package/src/locales/index.ts +11 -0
- package/src/locales/zh-cn/publish.json +8 -0
- package/src/monorepo.service.ts +376 -0
- package/src/publish.command.ts +71 -0
- package/src/publish.config.ts +90 -0
- package/src/publish.module.ts +12 -0
- package/src/publish.service.ts +602 -0
- package/tsconfig.json +5 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1160 @@
|
|
|
1
|
+
import * as __rspack_external_fs from "fs";
|
|
2
|
+
import * as __rspack_external_release_it_4c635798 from "release-it";
|
|
3
|
+
import { ConfigReaderModule, ConfigReaderService, GitProviderModule, GitProviderService, addLocaleResources, ciConfig, t as core_t, z } from "@spaceflow/core";
|
|
4
|
+
import { Injectable, Module } from "@nestjs/common";
|
|
5
|
+
import { ConfigModule, ConfigService } from "@nestjs/config";
|
|
6
|
+
import { Command, CommandRunner, Option } from "nest-commander";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
var __webpack_modules__ = ({
|
|
10
|
+
421(module) {
|
|
11
|
+
|
|
12
|
+
module.exports = __rspack_external_fs;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
},
|
|
16
|
+
547(module) {
|
|
17
|
+
|
|
18
|
+
module.exports = __rspack_external_release_it_4c635798;
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
});
|
|
24
|
+
// The module cache
|
|
25
|
+
var __webpack_module_cache__ = {};
|
|
26
|
+
|
|
27
|
+
// The require function
|
|
28
|
+
function __webpack_require__(moduleId) {
|
|
29
|
+
|
|
30
|
+
// Check if module is in cache
|
|
31
|
+
var cachedModule = __webpack_module_cache__[moduleId];
|
|
32
|
+
if (cachedModule !== undefined) {
|
|
33
|
+
return cachedModule.exports;
|
|
34
|
+
}
|
|
35
|
+
// Create a new module (and put it into the cache)
|
|
36
|
+
var module = (__webpack_module_cache__[moduleId] = {
|
|
37
|
+
exports: {}
|
|
38
|
+
});
|
|
39
|
+
// Execute the module function
|
|
40
|
+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
41
|
+
|
|
42
|
+
// Return the exports of the module
|
|
43
|
+
return module.exports;
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// webpack/runtime/define_property_getters
|
|
48
|
+
(() => {
|
|
49
|
+
__webpack_require__.d = (exports, definition) => {
|
|
50
|
+
for(var key in definition) {
|
|
51
|
+
if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
|
52
|
+
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
})();
|
|
57
|
+
// webpack/runtime/has_own_property
|
|
58
|
+
(() => {
|
|
59
|
+
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
60
|
+
})();
|
|
61
|
+
var __webpack_exports__ = {};
|
|
62
|
+
// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk.
|
|
63
|
+
(() => {
|
|
64
|
+
|
|
65
|
+
// EXPORTS
|
|
66
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
67
|
+
Vy: () => (/* reexport */ MonorepoService),
|
|
68
|
+
Ay: () => (/* binding */ src),
|
|
69
|
+
D5: () => (/* reexport */ PublishCommand),
|
|
70
|
+
i8: () => (/* reexport */ PublishModule),
|
|
71
|
+
jX: () => (/* binding */ PublishExtension),
|
|
72
|
+
DU: () => (/* reexport */ PublishService),
|
|
73
|
+
bH: () => (/* binding */ publishMetadata)
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
;// CONCATENATED MODULE: external "@spaceflow/core"
|
|
77
|
+
|
|
78
|
+
;// CONCATENATED MODULE: ./src/locales/zh-cn/publish.json
|
|
79
|
+
var publish_namespaceObject = JSON.parse('{"description":"CI 发布命令,用于在发布流程中锁定/解锁分支","options.prerelease":"预发布标签,如 rc、beta、alpha、nightly 等","options.rehearsal":"预演模式:执行 hooks 但不修改文件/git,设置 PUBLISH_REHEARSAL=true 环境变量","rehearsalMode":"🎭 REHEARSAL mode: hooks will execute, but no file/git changes","dryRunMode":"🔍 DRY-RUN mode: no actual execution","extensionDescription":"CI 发布命令,用于在发布流程中锁定/解锁分支"}')
|
|
80
|
+
;// CONCATENATED MODULE: ./src/locales/en/publish.json
|
|
81
|
+
var en_publish_namespaceObject = JSON.parse('{"description":"CI publish command for branch lock/unlock during release","options.prerelease":"Prerelease tag, e.g. rc, beta, alpha, nightly","options.rehearsal":"Rehearsal mode: execute hooks but no file/git changes, sets PUBLISH_REHEARSAL=true","rehearsalMode":"🎭 REHEARSAL mode: hooks will execute, but no file/git changes","dryRunMode":"🔍 DRY-RUN mode: no actual execution","extensionDescription":"CI publish command for branch lock/unlock during release"}')
|
|
82
|
+
;// CONCATENATED MODULE: ./src/locales/index.ts
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
/** publish 命令 i18n 资源 */ const publishLocales = {
|
|
87
|
+
"zh-CN": publish_namespaceObject,
|
|
88
|
+
en: en_publish_namespaceObject
|
|
89
|
+
};
|
|
90
|
+
addLocaleResources("publish", publishLocales);
|
|
91
|
+
|
|
92
|
+
;// CONCATENATED MODULE: external "@nestjs/common"
|
|
93
|
+
|
|
94
|
+
;// CONCATENATED MODULE: external "@nestjs/config"
|
|
95
|
+
|
|
96
|
+
;// CONCATENATED MODULE: external "nest-commander"
|
|
97
|
+
|
|
98
|
+
;// CONCATENATED MODULE: external "child_process"
|
|
99
|
+
|
|
100
|
+
// EXTERNAL MODULE: external "fs"
|
|
101
|
+
var external_fs_ = __webpack_require__(421);
|
|
102
|
+
;// CONCATENATED MODULE: external "path"
|
|
103
|
+
|
|
104
|
+
;// CONCATENATED MODULE: ./src/monorepo.service.ts
|
|
105
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
106
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
107
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
108
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
109
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
110
|
+
}
|
|
111
|
+
function _ts_metadata(k, v) {
|
|
112
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class MonorepoService {
|
|
119
|
+
cwd;
|
|
120
|
+
constructor(){
|
|
121
|
+
this.cwd = process.cwd();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 分析 monorepo 变更,返回需要发布的包列表(拓扑排序后)
|
|
125
|
+
* @param dryRun 是否为 dry-run 模式
|
|
126
|
+
* @param propagateDeps 是否传递依赖变更(依赖的包变更时,依赖方也发布)
|
|
127
|
+
*/ async analyze(dryRun, propagateDeps = true) {
|
|
128
|
+
const workspacePackages = this.getWorkspacePackages();
|
|
129
|
+
const allPackages = this.getAllPackageInfos(workspacePackages);
|
|
130
|
+
// 为每个包单独检测变更(基于各自的最新 tag)
|
|
131
|
+
const changedPackages = this.getChangedPackages(allPackages, dryRun);
|
|
132
|
+
if (dryRun) {
|
|
133
|
+
console.log(`📦 直接变更的包: ${changedPackages.map((p)=>p.name).join(", ") || "无"}`);
|
|
134
|
+
}
|
|
135
|
+
// 计算依赖传递,找出所有需要发布的包
|
|
136
|
+
const packagesToPublish = propagateDeps ? this.calculateAffectedPackages(changedPackages, allPackages) : changedPackages;
|
|
137
|
+
if (dryRun) {
|
|
138
|
+
console.log(`🔄 需要发布的包(含依赖传递): ${packagesToPublish.map((p)=>p.name).join(", ") || "无"}`);
|
|
139
|
+
}
|
|
140
|
+
// 拓扑排序
|
|
141
|
+
const sortedPackages = this.topologicalSort(packagesToPublish, allPackages);
|
|
142
|
+
if (dryRun) {
|
|
143
|
+
console.log(`📋 发布顺序: ${sortedPackages.map((p)=>p.name).join(" -> ") || "无"}`);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
changedPackages,
|
|
147
|
+
packagesToPublish: sortedPackages
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 简单解析 pnpm-workspace.yaml(只提取 packages 数组)
|
|
152
|
+
*/ parseSimpleYaml(content) {
|
|
153
|
+
const packages = [];
|
|
154
|
+
const lines = content.split("\n");
|
|
155
|
+
let inPackages = false;
|
|
156
|
+
for (const line of lines){
|
|
157
|
+
const trimmed = line.trim();
|
|
158
|
+
if (trimmed === "packages:") {
|
|
159
|
+
inPackages = true;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (inPackages) {
|
|
163
|
+
if (trimmed.startsWith("- ")) {
|
|
164
|
+
// 提取包路径,去除引号
|
|
165
|
+
let pkg = trimmed.slice(2).trim();
|
|
166
|
+
pkg = pkg.replace(/^["']|["']$/g, "");
|
|
167
|
+
packages.push(pkg);
|
|
168
|
+
} else if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("-")) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
packages: packages.length > 0 ? packages : undefined
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 从 pnpm-workspace.yaml 读取 workspace 包配置
|
|
179
|
+
*/ getWorkspacePackages() {
|
|
180
|
+
const workspaceFile = join(this.cwd, "pnpm-workspace.yaml");
|
|
181
|
+
if (!(0,external_fs_.existsSync)(workspaceFile)) {
|
|
182
|
+
throw new Error("未找到 pnpm-workspace.yaml 文件");
|
|
183
|
+
}
|
|
184
|
+
const content = (0,external_fs_.readFileSync)(workspaceFile, "utf-8");
|
|
185
|
+
const config = this.parseSimpleYaml(content);
|
|
186
|
+
if (!config.packages || !Array.isArray(config.packages)) {
|
|
187
|
+
throw new Error("pnpm-workspace.yaml 中未配置 packages");
|
|
188
|
+
}
|
|
189
|
+
return config.packages;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 展开 workspace 包配置,获取所有实际的包目录
|
|
193
|
+
*/ expandWorkspacePatterns(patterns) {
|
|
194
|
+
const dirs = [];
|
|
195
|
+
for (const pattern of patterns){
|
|
196
|
+
if (pattern.includes("*")) {
|
|
197
|
+
// 使用 glob 展开,这里简化处理,只支持 extensions/* 这种模式
|
|
198
|
+
const baseDir = pattern.replace("/*", "");
|
|
199
|
+
const basePath = join(this.cwd, baseDir);
|
|
200
|
+
if ((0,external_fs_.existsSync)(basePath)) {
|
|
201
|
+
const { readdirSync, statSync } = __webpack_require__(421);
|
|
202
|
+
const entries = readdirSync(basePath);
|
|
203
|
+
for (const entry of entries){
|
|
204
|
+
const entryPath = join(basePath, entry);
|
|
205
|
+
if (statSync(entryPath).isDirectory()) {
|
|
206
|
+
const pkgJson = join(entryPath, "package.json");
|
|
207
|
+
if ((0,external_fs_.existsSync)(pkgJson)) {
|
|
208
|
+
dirs.push(join(baseDir, entry));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
// 直接目录
|
|
215
|
+
const pkgJson = join(this.cwd, pattern, "package.json");
|
|
216
|
+
if ((0,external_fs_.existsSync)(pkgJson)) {
|
|
217
|
+
dirs.push(pattern);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return dirs;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 获取所有包的详细信息(排除私有包)
|
|
225
|
+
*/ getAllPackageInfos(patterns) {
|
|
226
|
+
const dirs = this.expandWorkspacePatterns(patterns);
|
|
227
|
+
const packages = [];
|
|
228
|
+
for (const dir of dirs){
|
|
229
|
+
const pkgJsonPath = join(this.cwd, dir, "package.json");
|
|
230
|
+
if (!(0,external_fs_.existsSync)(pkgJsonPath)) continue;
|
|
231
|
+
const pkgJson = JSON.parse((0,external_fs_.readFileSync)(pkgJsonPath, "utf-8"));
|
|
232
|
+
// 跳过私有包
|
|
233
|
+
if (pkgJson.private === true) continue;
|
|
234
|
+
const workspaceDeps = this.extractWorkspaceDeps(pkgJson);
|
|
235
|
+
packages.push({
|
|
236
|
+
dir,
|
|
237
|
+
name: pkgJson.name,
|
|
238
|
+
version: pkgJson.version,
|
|
239
|
+
workspaceDeps
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return packages;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 提取包的 workspace 依赖
|
|
246
|
+
*/ extractWorkspaceDeps(pkgJson) {
|
|
247
|
+
const deps = [];
|
|
248
|
+
const allDeps = {
|
|
249
|
+
...pkgJson.dependencies,
|
|
250
|
+
...pkgJson.devDependencies,
|
|
251
|
+
...pkgJson.peerDependencies
|
|
252
|
+
};
|
|
253
|
+
for (const [name, version] of Object.entries(allDeps)){
|
|
254
|
+
if (version && (version.startsWith("workspace:") || version === "*")) {
|
|
255
|
+
deps.push(name);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return deps;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 检测每个包的变更(基于各自的最新 tag)
|
|
262
|
+
*/ getChangedPackages(allPackages, dryRun) {
|
|
263
|
+
const changedPackages = [];
|
|
264
|
+
for (const pkg of allPackages){
|
|
265
|
+
const hasChanges = this.hasPackageChanges(pkg);
|
|
266
|
+
if (hasChanges) {
|
|
267
|
+
changedPackages.push(pkg);
|
|
268
|
+
}
|
|
269
|
+
if (dryRun) {
|
|
270
|
+
console.log(` ${hasChanges ? "✅" : "⭕"} ${pkg.name}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return changedPackages;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 检测单个包是否有变更(基于该包的最新 tag)
|
|
277
|
+
*/ hasPackageChanges(pkg) {
|
|
278
|
+
try {
|
|
279
|
+
// 获取该包的最新 tag(格式: @scope/pkg@version 或 pkg@version)
|
|
280
|
+
const tagPattern = `${pkg.name}@*`;
|
|
281
|
+
const latestTag = execSync(`git describe --tags --abbrev=0 --match "${tagPattern}" 2>/dev/null || echo ''`, {
|
|
282
|
+
cwd: this.cwd,
|
|
283
|
+
encoding: "utf-8"
|
|
284
|
+
}).trim();
|
|
285
|
+
if (!latestTag) {
|
|
286
|
+
// 没有 tag,说明是新包,需要发布
|
|
287
|
+
console.log(`📌 ${pkg.name}: 无 tag,需要发布`);
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
// 检测从该 tag 到 HEAD,该包目录下是否有变更
|
|
291
|
+
const diffOutput = execSync(`git diff --name-only "${latestTag}"..HEAD -- "${pkg.dir}"`, {
|
|
292
|
+
cwd: this.cwd,
|
|
293
|
+
encoding: "utf-8"
|
|
294
|
+
}).trim();
|
|
295
|
+
const hasChanges = diffOutput.length > 0;
|
|
296
|
+
if (hasChanges) {
|
|
297
|
+
console.log(`📌 ${pkg.name}: ${latestTag} -> HEAD 有变更`);
|
|
298
|
+
console.log(` 变更文件: ${diffOutput.split("\n").slice(0, 3).join(", ")}${diffOutput.split("\n").length > 3 ? "..." : ""}`);
|
|
299
|
+
}
|
|
300
|
+
return hasChanges;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
// 出错时保守处理,认为有变更
|
|
303
|
+
console.log(`📌 ${pkg.name}: 检测出错,保守处理为有变更`);
|
|
304
|
+
console.log(` 错误: ${error instanceof Error ? error.message : error}`);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 将变更文件映射到包目录
|
|
310
|
+
*/ mapFilesToPackages(files, patterns) {
|
|
311
|
+
const packageDirs = this.expandWorkspacePatterns(patterns);
|
|
312
|
+
const changedPackages = new Set();
|
|
313
|
+
for (const file of files){
|
|
314
|
+
for (const dir of packageDirs){
|
|
315
|
+
if (file.startsWith(dir + "/") || file === dir) {
|
|
316
|
+
changedPackages.add(dir);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return changedPackages;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* 计算受影响的包(包含依赖传递)
|
|
325
|
+
*/ calculateAffectedPackages(changedPackages, allPackages) {
|
|
326
|
+
const changedNames = new Set(changedPackages.map((p)=>p.name));
|
|
327
|
+
const affectedNames = new Set(changedNames);
|
|
328
|
+
// 构建反向依赖图:谁依赖了我
|
|
329
|
+
const reverseDeps = new Map();
|
|
330
|
+
for (const pkg of allPackages){
|
|
331
|
+
for (const dep of pkg.workspaceDeps){
|
|
332
|
+
if (!reverseDeps.has(dep)) {
|
|
333
|
+
reverseDeps.set(dep, new Set());
|
|
334
|
+
}
|
|
335
|
+
reverseDeps.get(dep).add(pkg.name);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// BFS 传递依赖
|
|
339
|
+
const queue = [
|
|
340
|
+
...changedNames
|
|
341
|
+
];
|
|
342
|
+
while(queue.length > 0){
|
|
343
|
+
const current = queue.shift();
|
|
344
|
+
const dependents = reverseDeps.get(current);
|
|
345
|
+
if (dependents) {
|
|
346
|
+
for (const dependent of dependents){
|
|
347
|
+
if (!affectedNames.has(dependent)) {
|
|
348
|
+
affectedNames.add(dependent);
|
|
349
|
+
queue.push(dependent);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return allPackages.filter((p)=>affectedNames.has(p.name));
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* 拓扑排序:被依赖的包先发布
|
|
358
|
+
*/ topologicalSort(packages, _allPackages) {
|
|
359
|
+
const packageNames = new Set(packages.map((p)=>p.name));
|
|
360
|
+
const nameToPackage = new Map(packages.map((p)=>[
|
|
361
|
+
p.name,
|
|
362
|
+
p
|
|
363
|
+
]));
|
|
364
|
+
// 构建依赖图(只考虑待发布包之间的依赖)
|
|
365
|
+
const inDegree = new Map();
|
|
366
|
+
const graph = new Map();
|
|
367
|
+
for (const pkg of packages){
|
|
368
|
+
inDegree.set(pkg.name, 0);
|
|
369
|
+
graph.set(pkg.name, []);
|
|
370
|
+
}
|
|
371
|
+
for (const pkg of packages){
|
|
372
|
+
for (const dep of pkg.workspaceDeps){
|
|
373
|
+
if (packageNames.has(dep)) {
|
|
374
|
+
graph.get(dep).push(pkg.name);
|
|
375
|
+
inDegree.set(pkg.name, (inDegree.get(pkg.name) || 0) + 1);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Kahn's algorithm
|
|
380
|
+
const queue = [];
|
|
381
|
+
for (const [name, degree] of inDegree){
|
|
382
|
+
if (degree === 0) {
|
|
383
|
+
queue.push(name);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const sorted = [];
|
|
387
|
+
while(queue.length > 0){
|
|
388
|
+
const current = queue.shift();
|
|
389
|
+
sorted.push(nameToPackage.get(current));
|
|
390
|
+
for (const neighbor of graph.get(current) || []){
|
|
391
|
+
const newDegree = (inDegree.get(neighbor) || 0) - 1;
|
|
392
|
+
inDegree.set(neighbor, newDegree);
|
|
393
|
+
if (newDegree === 0) {
|
|
394
|
+
queue.push(neighbor);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (sorted.length !== packages.length) {
|
|
399
|
+
throw new Error("检测到循环依赖,无法确定发布顺序");
|
|
400
|
+
}
|
|
401
|
+
return sorted;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
MonorepoService = _ts_decorate([
|
|
405
|
+
Injectable(),
|
|
406
|
+
_ts_metadata("design:type", Function),
|
|
407
|
+
_ts_metadata("design:paramtypes", [])
|
|
408
|
+
], MonorepoService);
|
|
409
|
+
|
|
410
|
+
;// CONCATENATED MODULE: ./src/publish.service.ts
|
|
411
|
+
function publish_service_ts_decorate(decorators, target, key, desc) {
|
|
412
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
413
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
414
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
415
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
416
|
+
}
|
|
417
|
+
function publish_service_ts_metadata(k, v) {
|
|
418
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
427
|
+
const releaseItModule = __webpack_require__(547);
|
|
428
|
+
const releaseIt = typeof releaseItModule === "function" ? releaseItModule : releaseItModule.default;
|
|
429
|
+
class PublishService {
|
|
430
|
+
gitProvider;
|
|
431
|
+
configService;
|
|
432
|
+
configReader;
|
|
433
|
+
monorepoService;
|
|
434
|
+
cleanupOnExit = null;
|
|
435
|
+
uncaughtExceptionHandler = null;
|
|
436
|
+
branchUnlocked = false;
|
|
437
|
+
constructor(gitProvider, configService, configReader, monorepoService){
|
|
438
|
+
this.gitProvider = gitProvider;
|
|
439
|
+
this.configService = configService;
|
|
440
|
+
this.configReader = configReader;
|
|
441
|
+
this.monorepoService = monorepoService;
|
|
442
|
+
}
|
|
443
|
+
getContextFromEnv(options) {
|
|
444
|
+
this.gitProvider.validateConfig();
|
|
445
|
+
const ciConf = this.configService.get("ci");
|
|
446
|
+
const repository = ciConf?.repository;
|
|
447
|
+
const branch = ciConf?.refName;
|
|
448
|
+
if (!repository) {
|
|
449
|
+
throw new Error("缺少配置 ci.repository (环境变量 GITHUB_REPOSITORY)");
|
|
450
|
+
}
|
|
451
|
+
if (!branch) {
|
|
452
|
+
throw new Error("缺少配置 ci.refName (环境变量 GITHUB_REF_NAME)");
|
|
453
|
+
}
|
|
454
|
+
const [owner, repo] = repository.split("/");
|
|
455
|
+
if (!owner || !repo) {
|
|
456
|
+
throw new Error(`ci.repository 格式不正确,期望 "owner/repo",实际: "${repository}"`);
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
owner,
|
|
460
|
+
repo,
|
|
461
|
+
branch,
|
|
462
|
+
dryRun: options.dryRun ?? false,
|
|
463
|
+
prerelease: options.prerelease,
|
|
464
|
+
ci: options.ci,
|
|
465
|
+
rehearsal: options.rehearsal ?? false
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
async execute(context) {
|
|
469
|
+
const publishConf = this.configReader.getPluginConfig("publish");
|
|
470
|
+
const monorepoConf = publishConf.monorepo;
|
|
471
|
+
// CI 环境下自动 fetch tags,确保 release-it 能正确计算版本
|
|
472
|
+
if (context.ci) {
|
|
473
|
+
await this.ensureTagsFetched();
|
|
474
|
+
}
|
|
475
|
+
// 检查是否启用 monorepo 模式
|
|
476
|
+
if (monorepoConf?.enabled) {
|
|
477
|
+
return this.executeMonorepo(context, publishConf);
|
|
478
|
+
}
|
|
479
|
+
// 单包发布模式
|
|
480
|
+
return this.executeSinglePackage(context, publishConf);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Monorepo 发布模式:扫描变更包,按依赖顺序发布
|
|
484
|
+
*/ async executeMonorepo(context, publishConf) {
|
|
485
|
+
const { dryRun } = context;
|
|
486
|
+
console.log("\n📦 Monorepo 发布模式");
|
|
487
|
+
console.log("=".repeat(50));
|
|
488
|
+
const propagateDeps = publishConf.monorepo?.propagateDeps ?? true;
|
|
489
|
+
// 分析变更包
|
|
490
|
+
const analysis = await this.monorepoService.analyze(dryRun, propagateDeps);
|
|
491
|
+
if (analysis.packagesToPublish.length === 0) {
|
|
492
|
+
console.log("\n✅ 没有需要发布的包");
|
|
493
|
+
return {
|
|
494
|
+
success: true,
|
|
495
|
+
message: "没有需要发布的包"
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
console.log(`\n🚀 将发布 ${analysis.packagesToPublish.length} 个包`);
|
|
499
|
+
await this.handleBegin(context, publishConf);
|
|
500
|
+
try {
|
|
501
|
+
// 按顺序发布每个包
|
|
502
|
+
for(let i = 0; i < analysis.packagesToPublish.length; i++){
|
|
503
|
+
const pkg = analysis.packagesToPublish[i];
|
|
504
|
+
console.log(`\n[${i + 1}/${analysis.packagesToPublish.length}] 发布 ${pkg.name}`);
|
|
505
|
+
console.log("-".repeat(40));
|
|
506
|
+
await this.executePackageRelease(context, publishConf, pkg);
|
|
507
|
+
}
|
|
508
|
+
await this.handleEnd(context, publishConf);
|
|
509
|
+
return {
|
|
510
|
+
success: true,
|
|
511
|
+
message: `成功发布 ${analysis.packagesToPublish.length} 个包`
|
|
512
|
+
};
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.error("\n❌ Monorepo 发布失败:", error instanceof Error ? error.message : error);
|
|
515
|
+
try {
|
|
516
|
+
await this.handleEnd(context, publishConf);
|
|
517
|
+
} catch (unlockError) {
|
|
518
|
+
console.error("⚠️ 解锁分支失败:", unlockError instanceof Error ? unlockError.message : unlockError);
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
success: false,
|
|
522
|
+
message: "Monorepo 发布失败"
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* 发布单个包(monorepo 模式)
|
|
528
|
+
*/ async executePackageRelease(context, publishConf, pkg) {
|
|
529
|
+
const { dryRun, prerelease, ci, rehearsal } = context;
|
|
530
|
+
if (rehearsal) {
|
|
531
|
+
console.log(`🎭 [REHEARSAL] 将发布包: ${pkg.name} (${pkg.dir})`);
|
|
532
|
+
} else if (dryRun) {
|
|
533
|
+
console.log(`🔍 [DRY-RUN] 将发布包: ${pkg.name} (${pkg.dir})`);
|
|
534
|
+
}
|
|
535
|
+
const pkgDir = join(process.cwd(), pkg.dir);
|
|
536
|
+
const originalCwd = process.cwd();
|
|
537
|
+
const config = this.buildReleaseItConfig({
|
|
538
|
+
dryRun,
|
|
539
|
+
prerelease,
|
|
540
|
+
ci,
|
|
541
|
+
rehearsal,
|
|
542
|
+
pkgDir,
|
|
543
|
+
pkgName: pkg.name,
|
|
544
|
+
pkgBase: pkg.dir.split("/").pop() || pkg.dir,
|
|
545
|
+
publishConf
|
|
546
|
+
});
|
|
547
|
+
// 切换到包目录运行 release-it,确保读取正确的 package.json
|
|
548
|
+
process.chdir(pkgDir);
|
|
549
|
+
try {
|
|
550
|
+
await releaseIt(config);
|
|
551
|
+
console.log(`✅ ${pkg.name} 发布完成`);
|
|
552
|
+
} finally{
|
|
553
|
+
// 恢复原工作目录
|
|
554
|
+
process.chdir(originalCwd);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* 单包发布模式
|
|
559
|
+
*/ async executeSinglePackage(context, publishConf) {
|
|
560
|
+
const { dryRun, prerelease, ci, rehearsal } = context;
|
|
561
|
+
await this.handleBegin(context, publishConf);
|
|
562
|
+
try {
|
|
563
|
+
const config = this.buildReleaseItConfig({
|
|
564
|
+
dryRun,
|
|
565
|
+
prerelease,
|
|
566
|
+
ci,
|
|
567
|
+
rehearsal,
|
|
568
|
+
pkgDir: process.cwd(),
|
|
569
|
+
pkgBase: process.cwd().split("/").pop() || ".",
|
|
570
|
+
publishConf
|
|
571
|
+
});
|
|
572
|
+
await releaseIt(config);
|
|
573
|
+
} catch (error) {
|
|
574
|
+
console.error("执行失败:", error instanceof Error ? error.message : error);
|
|
575
|
+
try {
|
|
576
|
+
await this.handleEnd(context, publishConf);
|
|
577
|
+
} catch (unlockError) {
|
|
578
|
+
console.error("⚠️ 解锁分支失败:", unlockError instanceof Error ? unlockError.message : unlockError);
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
success: false,
|
|
582
|
+
message: "执行失败"
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
await this.handleEnd(context, publishConf);
|
|
586
|
+
return {
|
|
587
|
+
success: true,
|
|
588
|
+
message: "执行完成",
|
|
589
|
+
protection: null
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* 构建 release-it 配置(公共方法)
|
|
594
|
+
*/ buildReleaseItConfig(opts) {
|
|
595
|
+
const { dryRun, prerelease, ci, rehearsal, pkgName, pkgBase, publishConf } = opts;
|
|
596
|
+
const changelogConf = publishConf.changelog;
|
|
597
|
+
const releaseConf = publishConf.release;
|
|
598
|
+
const npmConf = publishConf.npm;
|
|
599
|
+
const gitConf = publishConf.git;
|
|
600
|
+
// 预演模式:设置环境变量,hooks 可以通过它判断当前模式
|
|
601
|
+
if (rehearsal) {
|
|
602
|
+
process.env.PUBLISH_REHEARSAL = "true";
|
|
603
|
+
}
|
|
604
|
+
const isMonorepo = !!pkgName;
|
|
605
|
+
const tagMatchOpts = !prerelease ? {
|
|
606
|
+
tagExclude: `*[-]*`
|
|
607
|
+
} : {};
|
|
608
|
+
// monorepo: @scope/pkg@1.0.0, 单包: v1.0.0
|
|
609
|
+
const tagPrefix = isMonorepo ? `${pkgName}@` : "v";
|
|
610
|
+
const tagName = isMonorepo ? `${pkgName}@\${version}` : "v${version}";
|
|
611
|
+
const releaseName = isMonorepo ? `${pkgName}@\${version}` : "v${version}";
|
|
612
|
+
const releaseTitle = isMonorepo ? `🎉 ${pkgName}@\${version}` : "🎉 v${version}";
|
|
613
|
+
// monorepo 模式下在包目录运行,git commitsPath 为 "."
|
|
614
|
+
const commitsPath = ".";
|
|
615
|
+
const commitMessage = isMonorepo ? `chore(${pkgBase}): released version \${version} [no ci]` : "chore: released version v${version} [no ci]";
|
|
616
|
+
// 预演模式:禁用文件/git 修改,但保留 hooks
|
|
617
|
+
// dryRun 模式:完全跳过所有操作(包括 hooks)
|
|
618
|
+
const skipWrite = dryRun || rehearsal;
|
|
619
|
+
return {
|
|
620
|
+
"dry-run": dryRun,
|
|
621
|
+
d: dryRun,
|
|
622
|
+
ci: ci || dryRun,
|
|
623
|
+
plugins: {
|
|
624
|
+
// 预演模式下禁用 changelog 写入
|
|
625
|
+
...!skipWrite && changelogConf ? {
|
|
626
|
+
"@release-it/conventional-changelog": {
|
|
627
|
+
// 现在在包目录下运行,使用相对路径
|
|
628
|
+
infile: join(changelogConf.infileDir || ".", `CHANGELOG${!prerelease ? "" : "-" + prerelease.toUpperCase()}.md`),
|
|
629
|
+
preset: {
|
|
630
|
+
name: changelogConf.preset?.name || "conventionalcommits",
|
|
631
|
+
types: changelogConf.preset?.type || []
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
} : {},
|
|
635
|
+
// 预演模式下禁用 release 创建
|
|
636
|
+
...!skipWrite && releaseConf ? {
|
|
637
|
+
"release-it-gitea": {
|
|
638
|
+
releaseTitle,
|
|
639
|
+
releaseNotes: this.formatReleaseNotes,
|
|
640
|
+
assets: this.buildReleaseAssets(releaseConf)
|
|
641
|
+
}
|
|
642
|
+
} : {}
|
|
643
|
+
},
|
|
644
|
+
git: {
|
|
645
|
+
// 预演模式:禁用 push/commit/tag
|
|
646
|
+
push: !skipWrite,
|
|
647
|
+
commit: !skipWrite,
|
|
648
|
+
tag: !skipWrite,
|
|
649
|
+
tagName,
|
|
650
|
+
commitsPath,
|
|
651
|
+
commitMessage,
|
|
652
|
+
requireCommits: false,
|
|
653
|
+
requireCommitsFail: false,
|
|
654
|
+
getLatestTagFromAllRefs: true,
|
|
655
|
+
requireBranch: gitConf?.requireBranch ?? [
|
|
656
|
+
"main",
|
|
657
|
+
"dev",
|
|
658
|
+
"develop"
|
|
659
|
+
],
|
|
660
|
+
requireCleanWorkingDir: !skipWrite,
|
|
661
|
+
...isMonorepo ? {
|
|
662
|
+
tagMatch: `${tagPrefix}*`
|
|
663
|
+
} : {},
|
|
664
|
+
...tagMatchOpts
|
|
665
|
+
},
|
|
666
|
+
// 预演模式:禁用 npm
|
|
667
|
+
// 如果使用 pnpm,禁用内置 npm 发布,但保留版本更新功能
|
|
668
|
+
npm: skipWrite ? false : {
|
|
669
|
+
// pnpm 模式:禁用 publish(通过 hooks 实现),但保留版本更新
|
|
670
|
+
publish: npmConf?.packageManager === "pnpm" ? false : npmConf?.publish ?? false,
|
|
671
|
+
ignoreVersion: npmConf?.ignoreVersion ?? true,
|
|
672
|
+
tag: prerelease || npmConf?.tag || "latest",
|
|
673
|
+
versionArgs: npmConf?.versionArgs ?? [
|
|
674
|
+
"--workspaces false"
|
|
675
|
+
],
|
|
676
|
+
publishArgs: npmConf?.publishArgs ?? [],
|
|
677
|
+
...npmConf?.registry ? {
|
|
678
|
+
publishConfig: {
|
|
679
|
+
registry: npmConf.registry
|
|
680
|
+
}
|
|
681
|
+
} : {}
|
|
682
|
+
},
|
|
683
|
+
github: {
|
|
684
|
+
release: false,
|
|
685
|
+
releaseName: `Release ${releaseName}`,
|
|
686
|
+
autoGenerate: true,
|
|
687
|
+
skipChecks: true,
|
|
688
|
+
host: releaseConf?.host || "localhost"
|
|
689
|
+
},
|
|
690
|
+
// 合并用户 hooks 和内部 pnpm 发布 hook
|
|
691
|
+
hooks: this.buildHooks({
|
|
692
|
+
userHooks: publishConf.hooks,
|
|
693
|
+
npmConf,
|
|
694
|
+
prerelease,
|
|
695
|
+
skipWrite,
|
|
696
|
+
dryRun,
|
|
697
|
+
rehearsal
|
|
698
|
+
})
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* 格式化 release notes
|
|
703
|
+
*/ formatReleaseNotes(t) {
|
|
704
|
+
const lines = t.changelog.split("\n");
|
|
705
|
+
const cateLines = lines.filter((line)=>line.startsWith("###") || line.startsWith("* "));
|
|
706
|
+
const cateMap = {};
|
|
707
|
+
let currentCate = "";
|
|
708
|
+
cateLines.forEach((line)=>{
|
|
709
|
+
if (line.startsWith("###")) {
|
|
710
|
+
currentCate = line;
|
|
711
|
+
cateMap[currentCate] = cateMap[currentCate] || [];
|
|
712
|
+
} else {
|
|
713
|
+
cateMap[currentCate].push(line);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
return Object.entries(cateMap).map(([cate, catLines])=>`${cate}\n\n${catLines.join("\n")}\n`).join("\n");
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* 构建 release assets 配置
|
|
720
|
+
*/ buildReleaseAssets(releaseConf) {
|
|
721
|
+
const assets = releaseConf.assetSourcemap ? [
|
|
722
|
+
{
|
|
723
|
+
path: releaseConf.assetSourcemap.path,
|
|
724
|
+
name: releaseConf.assetSourcemap.name,
|
|
725
|
+
type: "zip"
|
|
726
|
+
}
|
|
727
|
+
] : [];
|
|
728
|
+
return assets.concat(releaseConf.assets || []);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* 构建 hooks 配置,合并用户 hooks 和内部 pnpm 发布逻辑
|
|
732
|
+
*/ buildHooks(opts) {
|
|
733
|
+
const { userHooks, npmConf, prerelease, skipWrite, dryRun, rehearsal } = opts;
|
|
734
|
+
// dryRun 模式下不执行任何 hooks
|
|
735
|
+
if (dryRun && !rehearsal) {
|
|
736
|
+
return undefined;
|
|
737
|
+
}
|
|
738
|
+
// 复制用户 hooks
|
|
739
|
+
let hooks = {
|
|
740
|
+
...userHooks
|
|
741
|
+
};
|
|
742
|
+
// rehearsal 模式下过滤 after 前缀的 hooks(不执行实际的发布后操作)
|
|
743
|
+
if (rehearsal) {
|
|
744
|
+
hooks = Object.fromEntries(Object.entries(hooks).filter(([key])=>!key.startsWith("after:")));
|
|
745
|
+
// rehearsal 模式下也不添加 pnpm publish
|
|
746
|
+
return Object.keys(hooks).length > 0 ? hooks : undefined;
|
|
747
|
+
}
|
|
748
|
+
// 如果使用 pnpm 且需要发布
|
|
749
|
+
if (npmConf?.packageManager === "pnpm" && npmConf?.publish && !skipWrite) {
|
|
750
|
+
const tag = prerelease || npmConf.tag || "latest";
|
|
751
|
+
const publishArgs = npmConf.publishArgs ?? [];
|
|
752
|
+
const registry = npmConf.registry;
|
|
753
|
+
// 构建 pnpm publish 命令
|
|
754
|
+
// monorepo 模式下已切换到包目录,不需要 -C 参数
|
|
755
|
+
let publishCmd = `pnpm publish --tag ${tag} --no-git-checks`;
|
|
756
|
+
if (registry) {
|
|
757
|
+
publishCmd += ` --registry ${registry}`;
|
|
758
|
+
}
|
|
759
|
+
if (publishArgs.length > 0) {
|
|
760
|
+
publishCmd += ` ${publishArgs.join(" ")}`;
|
|
761
|
+
}
|
|
762
|
+
// 合并到 after:bump hook
|
|
763
|
+
const existingAfterBump = hooks["after:bump"];
|
|
764
|
+
if (existingAfterBump) {
|
|
765
|
+
hooks["after:bump"] = Array.isArray(existingAfterBump) ? [
|
|
766
|
+
...existingAfterBump,
|
|
767
|
+
publishCmd
|
|
768
|
+
] : [
|
|
769
|
+
existingAfterBump,
|
|
770
|
+
publishCmd
|
|
771
|
+
];
|
|
772
|
+
} else {
|
|
773
|
+
hooks["after:bump"] = publishCmd;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return Object.keys(hooks).length > 0 ? hooks : undefined;
|
|
777
|
+
}
|
|
778
|
+
async handleBegin(context, publishConf) {
|
|
779
|
+
const { owner, repo, branch, dryRun } = context;
|
|
780
|
+
const shouldLockBranch = publishConf.git?.lockBranch ?? true;
|
|
781
|
+
if (!shouldLockBranch) {
|
|
782
|
+
console.log(`⏭️ 跳过分支锁定(已禁用)`);
|
|
783
|
+
return {
|
|
784
|
+
success: true,
|
|
785
|
+
message: "分支锁定已禁用",
|
|
786
|
+
protection: null
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
const pushWhitelistUsernames = [
|
|
790
|
+
...publishConf.git?.pushWhitelistUsernames ?? []
|
|
791
|
+
];
|
|
792
|
+
if (dryRun) {
|
|
793
|
+
console.log(`🔍 [DRY-RUN] 将锁定分支: ${owner}/${repo}#${branch}`);
|
|
794
|
+
return {
|
|
795
|
+
success: true,
|
|
796
|
+
message: "DRY-RUN: 分支锁定已跳过",
|
|
797
|
+
protection: null
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
console.log(`🔒 正在锁定分支: ${owner}/${repo}#${branch}`);
|
|
801
|
+
const protection = await this.gitProvider.lockBranch(owner, repo, branch, {
|
|
802
|
+
pushWhitelistUsernames
|
|
803
|
+
});
|
|
804
|
+
console.log(`✅ 分支已锁定`);
|
|
805
|
+
console.log(` 规则名称: ${protection.rule_name || protection.branch_name}`);
|
|
806
|
+
if (pushWhitelistUsernames?.length) {
|
|
807
|
+
console.log(` 允许推送用户: ${pushWhitelistUsernames.join(", ")}`);
|
|
808
|
+
} else {
|
|
809
|
+
console.log(` 允许推送: ${protection.enable_push ? "是" : "否"}`);
|
|
810
|
+
}
|
|
811
|
+
// 注册进程退出时的清理函数,确保即使 release-it 调用 process.exit() 也能解锁分支
|
|
812
|
+
this.branchUnlocked = false;
|
|
813
|
+
this.cleanupOnExit = ()=>{
|
|
814
|
+
if (this.branchUnlocked) return;
|
|
815
|
+
this.branchUnlocked = true;
|
|
816
|
+
console.log("\n🔓 进程退出,正在同步解锁分支...");
|
|
817
|
+
try {
|
|
818
|
+
this.unlockBranchSync(context, publishConf);
|
|
819
|
+
} catch (e) {
|
|
820
|
+
console.error("⚠️ 同步解锁分支失败:", e instanceof Error ? e.message : e);
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
this.uncaughtExceptionHandler = (err)=>{
|
|
824
|
+
console.error("\n❌ 未捕获的异常:", err.message);
|
|
825
|
+
if (this.cleanupOnExit) this.cleanupOnExit();
|
|
826
|
+
process.exit(1);
|
|
827
|
+
};
|
|
828
|
+
process.on("exit", this.cleanupOnExit);
|
|
829
|
+
process.on("SIGINT", this.cleanupOnExit);
|
|
830
|
+
process.on("SIGTERM", this.cleanupOnExit);
|
|
831
|
+
process.on("uncaughtException", this.uncaughtExceptionHandler);
|
|
832
|
+
return {
|
|
833
|
+
success: true,
|
|
834
|
+
message: "分支锁定完成",
|
|
835
|
+
protection
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
async handleEnd(context, publishConf) {
|
|
839
|
+
const { owner, repo, branch, dryRun } = context;
|
|
840
|
+
const shouldLockBranch = publishConf.git?.lockBranch ?? true;
|
|
841
|
+
if (!shouldLockBranch) {
|
|
842
|
+
return {
|
|
843
|
+
success: true,
|
|
844
|
+
message: "分支锁定已禁用,无需解锁",
|
|
845
|
+
protection: null
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
if (dryRun) {
|
|
849
|
+
console.log(`🔍 [DRY-RUN] 将解锁分支: ${owner}/${repo}#${branch}`);
|
|
850
|
+
return {
|
|
851
|
+
success: true,
|
|
852
|
+
message: "DRY-RUN: 分支解锁已跳过",
|
|
853
|
+
protection: null
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
console.log(`🔓 正在解锁分支: ${owner}/${repo}#${branch}`);
|
|
857
|
+
const protection = await this.gitProvider.unlockBranch(owner, repo, branch);
|
|
858
|
+
// 标记已解锁,防止清理函数重复执行
|
|
859
|
+
this.branchUnlocked = true;
|
|
860
|
+
// 移除事件监听器
|
|
861
|
+
if (this.cleanupOnExit) {
|
|
862
|
+
process.removeListener("exit", this.cleanupOnExit);
|
|
863
|
+
process.removeListener("SIGINT", this.cleanupOnExit);
|
|
864
|
+
process.removeListener("SIGTERM", this.cleanupOnExit);
|
|
865
|
+
this.cleanupOnExit = null;
|
|
866
|
+
}
|
|
867
|
+
if (this.uncaughtExceptionHandler) {
|
|
868
|
+
process.removeListener("uncaughtException", this.uncaughtExceptionHandler);
|
|
869
|
+
this.uncaughtExceptionHandler = null;
|
|
870
|
+
}
|
|
871
|
+
if (protection) {
|
|
872
|
+
console.log(`✅ 分支已解锁`);
|
|
873
|
+
console.log(` 规则名称: ${protection.rule_name || protection.branch_name}`);
|
|
874
|
+
console.log(` 允许推送: ${protection.enable_push ? "是" : "否"}`);
|
|
875
|
+
return {
|
|
876
|
+
success: true,
|
|
877
|
+
message: "分支解锁完成",
|
|
878
|
+
protection
|
|
879
|
+
};
|
|
880
|
+
} else {
|
|
881
|
+
console.log(`✅ 分支本身没有保护规则,无需解锁`);
|
|
882
|
+
return {
|
|
883
|
+
success: true,
|
|
884
|
+
message: "分支本身没有保护规则,无需解锁",
|
|
885
|
+
protection: null
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* 同步解锁分支(用于进程退出时的清理)
|
|
891
|
+
*/ unlockBranchSync(context, publishConf) {
|
|
892
|
+
const { owner, repo, branch, dryRun } = context;
|
|
893
|
+
const shouldLockBranch = publishConf.git?.lockBranch ?? true;
|
|
894
|
+
if (!shouldLockBranch || dryRun) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
this.gitProvider.unlockBranchSync(owner, repo, branch);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* 确保 git tags 已获取(CI 环境中 shallow clone 可能缺失 tags)
|
|
901
|
+
* 这对于 release-it 正确计算版本号至关重要
|
|
902
|
+
*/ async ensureTagsFetched() {
|
|
903
|
+
try {
|
|
904
|
+
// 检查是否有 tags
|
|
905
|
+
const existingTags = execSync("git tag --list 2>/dev/null || echo ''", {
|
|
906
|
+
encoding: "utf-8"
|
|
907
|
+
}).trim();
|
|
908
|
+
if (!existingTags) {
|
|
909
|
+
console.log("🏷️ 正在获取 git tags...");
|
|
910
|
+
execSync("git fetch --tags --force", {
|
|
911
|
+
stdio: "inherit"
|
|
912
|
+
});
|
|
913
|
+
console.log("✅ Git tags 已获取");
|
|
914
|
+
}
|
|
915
|
+
} catch (error) {
|
|
916
|
+
console.warn("⚠️ 获取 git tags 失败:", error instanceof Error ? error.message : error);
|
|
917
|
+
console.warn(" 版本计算可能不准确,建议在 CI checkout 时添加 fetch-depth: 0 和 fetch-tags: true");
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
PublishService = publish_service_ts_decorate([
|
|
922
|
+
Injectable(),
|
|
923
|
+
publish_service_ts_metadata("design:type", Function),
|
|
924
|
+
publish_service_ts_metadata("design:paramtypes", [
|
|
925
|
+
typeof GitProviderService === "undefined" ? Object : GitProviderService,
|
|
926
|
+
typeof ConfigService === "undefined" ? Object : ConfigService,
|
|
927
|
+
typeof ConfigReaderService === "undefined" ? Object : ConfigReaderService,
|
|
928
|
+
typeof MonorepoService === "undefined" ? Object : MonorepoService
|
|
929
|
+
])
|
|
930
|
+
], PublishService);
|
|
931
|
+
|
|
932
|
+
;// CONCATENATED MODULE: ./src/publish.command.ts
|
|
933
|
+
function publish_command_ts_decorate(decorators, target, key, desc) {
|
|
934
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
935
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
936
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
937
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
938
|
+
}
|
|
939
|
+
function publish_command_ts_metadata(k, v) {
|
|
940
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
class PublishCommand extends CommandRunner {
|
|
946
|
+
publishService;
|
|
947
|
+
constructor(publishService){
|
|
948
|
+
super(), this.publishService = publishService;
|
|
949
|
+
}
|
|
950
|
+
async run(_passedParams, options) {
|
|
951
|
+
if (options.rehearsal) {
|
|
952
|
+
console.log(core_t("publish:rehearsalMode"));
|
|
953
|
+
} else if (options.dryRun) {
|
|
954
|
+
console.log(core_t("publish:dryRunMode"));
|
|
955
|
+
}
|
|
956
|
+
try {
|
|
957
|
+
const context = this.publishService.getContextFromEnv(options);
|
|
958
|
+
await this.publishService.execute(context);
|
|
959
|
+
} catch (error) {
|
|
960
|
+
console.error(core_t("common.executionFailed", {
|
|
961
|
+
error: error instanceof Error ? error.message : error
|
|
962
|
+
}));
|
|
963
|
+
process.exit(1);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
parseDryRun(val) {
|
|
967
|
+
return val;
|
|
968
|
+
}
|
|
969
|
+
parseCi(val) {
|
|
970
|
+
return val;
|
|
971
|
+
}
|
|
972
|
+
parsePrerelease(val) {
|
|
973
|
+
return val;
|
|
974
|
+
}
|
|
975
|
+
parseRehearsal(val) {
|
|
976
|
+
return val;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
publish_command_ts_decorate([
|
|
980
|
+
Option({
|
|
981
|
+
flags: "-d, --dry-run",
|
|
982
|
+
description: core_t("common.options.dryRun")
|
|
983
|
+
}),
|
|
984
|
+
publish_command_ts_metadata("design:type", Function),
|
|
985
|
+
publish_command_ts_metadata("design:paramtypes", [
|
|
986
|
+
Boolean
|
|
987
|
+
]),
|
|
988
|
+
publish_command_ts_metadata("design:returntype", Boolean)
|
|
989
|
+
], PublishCommand.prototype, "parseDryRun", null);
|
|
990
|
+
publish_command_ts_decorate([
|
|
991
|
+
Option({
|
|
992
|
+
flags: "-c, --ci",
|
|
993
|
+
description: core_t("common.options.ci")
|
|
994
|
+
}),
|
|
995
|
+
publish_command_ts_metadata("design:type", Function),
|
|
996
|
+
publish_command_ts_metadata("design:paramtypes", [
|
|
997
|
+
Boolean
|
|
998
|
+
]),
|
|
999
|
+
publish_command_ts_metadata("design:returntype", Boolean)
|
|
1000
|
+
], PublishCommand.prototype, "parseCi", null);
|
|
1001
|
+
publish_command_ts_decorate([
|
|
1002
|
+
Option({
|
|
1003
|
+
flags: "-p, --prerelease <tag>",
|
|
1004
|
+
description: core_t("publish:options.prerelease")
|
|
1005
|
+
}),
|
|
1006
|
+
publish_command_ts_metadata("design:type", Function),
|
|
1007
|
+
publish_command_ts_metadata("design:paramtypes", [
|
|
1008
|
+
String
|
|
1009
|
+
]),
|
|
1010
|
+
publish_command_ts_metadata("design:returntype", String)
|
|
1011
|
+
], PublishCommand.prototype, "parsePrerelease", null);
|
|
1012
|
+
publish_command_ts_decorate([
|
|
1013
|
+
Option({
|
|
1014
|
+
flags: "-r, --rehearsal",
|
|
1015
|
+
description: core_t("publish:options.rehearsal")
|
|
1016
|
+
}),
|
|
1017
|
+
publish_command_ts_metadata("design:type", Function),
|
|
1018
|
+
publish_command_ts_metadata("design:paramtypes", [
|
|
1019
|
+
Boolean
|
|
1020
|
+
]),
|
|
1021
|
+
publish_command_ts_metadata("design:returntype", Boolean)
|
|
1022
|
+
], PublishCommand.prototype, "parseRehearsal", null);
|
|
1023
|
+
PublishCommand = publish_command_ts_decorate([
|
|
1024
|
+
Command({
|
|
1025
|
+
name: "publish",
|
|
1026
|
+
description: core_t("publish:description")
|
|
1027
|
+
}),
|
|
1028
|
+
publish_command_ts_metadata("design:type", Function),
|
|
1029
|
+
publish_command_ts_metadata("design:paramtypes", [
|
|
1030
|
+
typeof PublishService === "undefined" ? Object : PublishService
|
|
1031
|
+
])
|
|
1032
|
+
], PublishCommand);
|
|
1033
|
+
|
|
1034
|
+
;// CONCATENATED MODULE: ./src/publish.module.ts
|
|
1035
|
+
function publish_module_ts_decorate(decorators, target, key, desc) {
|
|
1036
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1037
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1038
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1039
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
class PublishModule {
|
|
1048
|
+
}
|
|
1049
|
+
PublishModule = publish_module_ts_decorate([
|
|
1050
|
+
Module({
|
|
1051
|
+
imports: [
|
|
1052
|
+
ConfigModule.forFeature(ciConfig),
|
|
1053
|
+
GitProviderModule.forFeature(),
|
|
1054
|
+
ConfigReaderModule
|
|
1055
|
+
],
|
|
1056
|
+
providers: [
|
|
1057
|
+
PublishCommand,
|
|
1058
|
+
PublishService,
|
|
1059
|
+
MonorepoService
|
|
1060
|
+
]
|
|
1061
|
+
})
|
|
1062
|
+
], PublishModule);
|
|
1063
|
+
|
|
1064
|
+
;// CONCATENATED MODULE: ./src/publish.config.ts
|
|
1065
|
+
|
|
1066
|
+
/** publish 命令配置 schema */ const publishSchema = z.object({
|
|
1067
|
+
/** monorepo 发布模式配置 */ monorepo: z.object({
|
|
1068
|
+
/** 是否启用 monorepo 发布模式 */ enabled: z.boolean().default(false),
|
|
1069
|
+
/** 是否传递依赖变更(依赖的包变更时,依赖方也发布) */ propagateDeps: z.boolean().default(true)
|
|
1070
|
+
}).optional(),
|
|
1071
|
+
changelog: z.object({
|
|
1072
|
+
/** changelog 文件输出目录 */ infileDir: z.string().default(".").optional(),
|
|
1073
|
+
preset: z.object({
|
|
1074
|
+
/** preset 名称,默认 conventionalcommits */ name: z.string().default("conventionalcommits").optional(),
|
|
1075
|
+
/** commit type 到 section 的映射 */ type: z.array(z.object({
|
|
1076
|
+
type: z.string(),
|
|
1077
|
+
section: z.string()
|
|
1078
|
+
})).default([])
|
|
1079
|
+
}).optional()
|
|
1080
|
+
}).optional(),
|
|
1081
|
+
/** npm 发布配置 */ npm: z.object({
|
|
1082
|
+
/** 是否发布到 npm registry */ publish: z.boolean().default(false),
|
|
1083
|
+
/** 包管理器,npm 或 pnpm */ packageManager: z["enum"]([
|
|
1084
|
+
"npm",
|
|
1085
|
+
"pnpm"
|
|
1086
|
+
]).default("npm"),
|
|
1087
|
+
/** npm registry 地址 */ registry: z.string().optional(),
|
|
1088
|
+
/** npm tag,如 latest、beta、next */ tag: z.string().default("latest"),
|
|
1089
|
+
/** 是否忽略 package.json 中的版本号 */ ignoreVersion: z.boolean().default(true),
|
|
1090
|
+
/** npm version 命令额外参数 */ versionArgs: z.array(z.string()).default([
|
|
1091
|
+
"--workspaces false"
|
|
1092
|
+
]),
|
|
1093
|
+
/** npm/pnpm publish 命令额外参数 */ publishArgs: z.array(z.string()).default([])
|
|
1094
|
+
}).optional(),
|
|
1095
|
+
release: z.object({
|
|
1096
|
+
host: z.string().default("localhost"),
|
|
1097
|
+
assetSourcemap: z.object({
|
|
1098
|
+
path: z.string(),
|
|
1099
|
+
name: z.string()
|
|
1100
|
+
}).optional(),
|
|
1101
|
+
assets: z.array(z.object({
|
|
1102
|
+
path: z.string(),
|
|
1103
|
+
name: z.string(),
|
|
1104
|
+
type: z.string()
|
|
1105
|
+
})).default([])
|
|
1106
|
+
}).optional(),
|
|
1107
|
+
/** git 配置 */ git: z.object({
|
|
1108
|
+
/** 允许发布的分支列表 */ requireBranch: z.array(z.string()).default([
|
|
1109
|
+
"main",
|
|
1110
|
+
"dev",
|
|
1111
|
+
"develop"
|
|
1112
|
+
]),
|
|
1113
|
+
/** 分支锁定时允许推送的用户名白名单(如 CI 机器人) */ pushWhitelistUsernames: z.array(z.string()).default([]),
|
|
1114
|
+
/** 是否在发布时锁定分支 */ lockBranch: z.boolean().default(true)
|
|
1115
|
+
}).optional(),
|
|
1116
|
+
/** release-it hooks 配置,如 before:bump, after:bump 等 */ hooks: z.record(z.string(), z.union([
|
|
1117
|
+
z.string(),
|
|
1118
|
+
z.array(z.string())
|
|
1119
|
+
])).optional()
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
;// CONCATENATED MODULE: ./src/index.ts
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
/** publish Extension 元数据 */ const publishMetadata = {
|
|
1128
|
+
name: "publish",
|
|
1129
|
+
commands: [
|
|
1130
|
+
"publish"
|
|
1131
|
+
],
|
|
1132
|
+
configKey: "publish",
|
|
1133
|
+
configSchema: ()=>publishSchema,
|
|
1134
|
+
version: "1.0.0",
|
|
1135
|
+
description: core_t("publish:extensionDescription")
|
|
1136
|
+
};
|
|
1137
|
+
class PublishExtension {
|
|
1138
|
+
getMetadata() {
|
|
1139
|
+
return publishMetadata;
|
|
1140
|
+
}
|
|
1141
|
+
getModule() {
|
|
1142
|
+
return PublishModule;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
/* export default */ const src = (PublishExtension);
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
})();
|
|
1152
|
+
|
|
1153
|
+
var __webpack_exports__MonorepoService = __webpack_exports__.Vy;
|
|
1154
|
+
var __webpack_exports__PublishCommand = __webpack_exports__.D5;
|
|
1155
|
+
var __webpack_exports__PublishExtension = __webpack_exports__.jX;
|
|
1156
|
+
var __webpack_exports__PublishModule = __webpack_exports__.i8;
|
|
1157
|
+
var __webpack_exports__PublishService = __webpack_exports__.DU;
|
|
1158
|
+
var __webpack_exports__default = __webpack_exports__.Ay;
|
|
1159
|
+
var __webpack_exports__publishMetadata = __webpack_exports__.bH;
|
|
1160
|
+
export { __webpack_exports__MonorepoService as MonorepoService, __webpack_exports__PublishCommand as PublishCommand, __webpack_exports__PublishExtension as PublishExtension, __webpack_exports__PublishModule as PublishModule, __webpack_exports__PublishService as PublishService, __webpack_exports__default as default, __webpack_exports__publishMetadata as publishMetadata };
|