@spaceflow/publish 0.21.2 → 0.22.0
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 +26 -0
- package/dist/index.js +548 -696
- package/package.json +3 -6
- package/src/index.ts +59 -21
- package/src/monorepo.service.ts +0 -2
- package/src/publish.service.ts +14 -10
- package/tsconfig.json +1 -1
- package/src/publish.command.ts +0 -71
- package/src/publish.module.ts +0 -12
package/dist/index.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import * as __rspack_external_fs from "fs";
|
|
2
2
|
import * as __rspack_external_release_it_4c635798 from "release-it";
|
|
3
|
-
import {
|
|
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";
|
|
3
|
+
import { addLocaleResources, ciConfig, defineExtension, t as core_t, z } from "@spaceflow/core";
|
|
8
4
|
import { join } from "path";
|
|
5
|
+
import { execSync } from "child_process";
|
|
9
6
|
var __webpack_modules__ = ({
|
|
10
7
|
421(module) {
|
|
11
8
|
|
|
@@ -64,13 +61,8 @@ var __webpack_exports__ = {};
|
|
|
64
61
|
|
|
65
62
|
// EXPORTS
|
|
66
63
|
__webpack_require__.d(__webpack_exports__, {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
D5: () => (/* reexport */ PublishCommand),
|
|
70
|
-
i8: () => (/* reexport */ PublishModule),
|
|
71
|
-
jX: () => (/* binding */ PublishExtension),
|
|
72
|
-
DU: () => (/* reexport */ PublishService),
|
|
73
|
-
bH: () => (/* binding */ publishMetadata)
|
|
64
|
+
A: () => (/* binding */ src),
|
|
65
|
+
c: () => (/* binding */ extension)
|
|
74
66
|
});
|
|
75
67
|
|
|
76
68
|
;// CONCATENATED MODULE: external "@spaceflow/core"
|
|
@@ -89,469 +81,201 @@ var en_publish_namespaceObject = JSON.parse('{"description":"CI publish command
|
|
|
89
81
|
};
|
|
90
82
|
addLocaleResources("publish", publishLocales);
|
|
91
83
|
|
|
92
|
-
;// CONCATENATED MODULE:
|
|
93
|
-
|
|
94
|
-
;// CONCATENATED MODULE: external "@nestjs/config"
|
|
95
|
-
|
|
96
|
-
;// CONCATENATED MODULE: external "nest-commander"
|
|
84
|
+
;// CONCATENATED MODULE: ./src/publish.config.ts
|
|
97
85
|
|
|
98
|
-
|
|
86
|
+
/** publish 命令配置 schema */ const publishSchema = z.object({
|
|
87
|
+
/** monorepo 发布模式配置 */ monorepo: z.object({
|
|
88
|
+
/** 是否启用 monorepo 发布模式 */ enabled: z.boolean().default(false),
|
|
89
|
+
/** 是否传递依赖变更(依赖的包变更时,依赖方也发布) */ propagateDeps: z.boolean().default(true)
|
|
90
|
+
}).optional(),
|
|
91
|
+
changelog: z.object({
|
|
92
|
+
/** changelog 文件输出目录 */ infileDir: z.string().default(".").optional(),
|
|
93
|
+
preset: z.object({
|
|
94
|
+
/** preset 名称,默认 conventionalcommits */ name: z.string().default("conventionalcommits").optional(),
|
|
95
|
+
/** commit type 到 section 的映射 */ type: z.array(z.object({
|
|
96
|
+
type: z.string(),
|
|
97
|
+
section: z.string()
|
|
98
|
+
})).default([])
|
|
99
|
+
}).optional()
|
|
100
|
+
}).optional(),
|
|
101
|
+
/** npm 发布配置 */ npm: z.object({
|
|
102
|
+
/** 是否发布到 npm registry */ publish: z.boolean().default(false),
|
|
103
|
+
/** 包管理器,npm 或 pnpm */ packageManager: z["enum"]([
|
|
104
|
+
"npm",
|
|
105
|
+
"pnpm"
|
|
106
|
+
]).default("npm"),
|
|
107
|
+
/** npm registry 地址 */ registry: z.string().optional(),
|
|
108
|
+
/** npm tag,如 latest、beta、next */ tag: z.string().default("latest"),
|
|
109
|
+
/** 是否忽略 package.json 中的版本号 */ ignoreVersion: z.boolean().default(true),
|
|
110
|
+
/** npm version 命令额外参数 */ versionArgs: z.array(z.string()).default([
|
|
111
|
+
"--workspaces false"
|
|
112
|
+
]),
|
|
113
|
+
/** npm/pnpm publish 命令额外参数 */ publishArgs: z.array(z.string()).default([])
|
|
114
|
+
}).optional(),
|
|
115
|
+
release: z.object({
|
|
116
|
+
host: z.string().default("localhost"),
|
|
117
|
+
assetSourcemap: z.object({
|
|
118
|
+
path: z.string(),
|
|
119
|
+
name: z.string()
|
|
120
|
+
}).optional(),
|
|
121
|
+
assets: z.array(z.object({
|
|
122
|
+
path: z.string(),
|
|
123
|
+
name: z.string(),
|
|
124
|
+
type: z.string()
|
|
125
|
+
})).default([])
|
|
126
|
+
}).optional(),
|
|
127
|
+
/** git 配置 */ git: z.object({
|
|
128
|
+
/** 允许发布的分支列表 */ requireBranch: z.array(z.string()).default([
|
|
129
|
+
"main",
|
|
130
|
+
"dev",
|
|
131
|
+
"develop"
|
|
132
|
+
]),
|
|
133
|
+
/** 分支锁定时允许推送的用户名白名单(如 CI 机器人) */ pushWhitelistUsernames: z.array(z.string()).default([]),
|
|
134
|
+
/** 是否在发布时锁定分支 */ lockBranch: z.boolean().default(true)
|
|
135
|
+
}).optional(),
|
|
136
|
+
/** release-it hooks 配置,如 before:bump, after:bump 等 */ hooks: z.record(z.string(), z.union([
|
|
137
|
+
z.string(),
|
|
138
|
+
z.array(z.string())
|
|
139
|
+
])).optional()
|
|
140
|
+
});
|
|
99
141
|
|
|
100
|
-
// EXTERNAL MODULE: external "fs"
|
|
101
|
-
var external_fs_ = __webpack_require__(421);
|
|
102
142
|
;// CONCATENATED MODULE: external "path"
|
|
103
143
|
|
|
104
|
-
;// CONCATENATED MODULE:
|
|
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
|
-
}
|
|
144
|
+
;// CONCATENATED MODULE: external "child_process"
|
|
114
145
|
|
|
146
|
+
;// CONCATENATED MODULE: ./src/publish.service.ts
|
|
115
147
|
|
|
116
148
|
|
|
117
149
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
151
|
+
const releaseItModule = __webpack_require__(547);
|
|
152
|
+
const releaseIt = typeof releaseItModule === "function" ? releaseItModule : releaseItModule.default;
|
|
153
|
+
class PublishService {
|
|
154
|
+
gitProvider;
|
|
155
|
+
config;
|
|
156
|
+
configReader;
|
|
157
|
+
monorepoService;
|
|
158
|
+
cleanupOnExit = null;
|
|
159
|
+
uncaughtExceptionHandler = null;
|
|
160
|
+
branchUnlocked = false;
|
|
161
|
+
constructor(gitProvider, config, configReader, monorepoService){
|
|
162
|
+
this.gitProvider = gitProvider;
|
|
163
|
+
this.config = config;
|
|
164
|
+
this.configReader = configReader;
|
|
165
|
+
this.monorepoService = monorepoService;
|
|
122
166
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// 为每个包单独检测变更(基于各自的最新 tag)
|
|
131
|
-
const changedPackages = this.getChangedPackages(allPackages, dryRun);
|
|
132
|
-
if (dryRun) {
|
|
133
|
-
console.log(`📦 直接变更的包: ${changedPackages.map((p)=>p.name).join(", ") || "无"}`);
|
|
167
|
+
getContextFromEnv(options) {
|
|
168
|
+
this.gitProvider.validateConfig();
|
|
169
|
+
const ciConf = ciConfig();
|
|
170
|
+
const repository = ciConf.repository;
|
|
171
|
+
const branch = ciConf.refName;
|
|
172
|
+
if (!repository) {
|
|
173
|
+
throw new Error("缺少配置 ci.repository (环境变量 GITHUB_REPOSITORY)");
|
|
134
174
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (dryRun) {
|
|
138
|
-
console.log(`🔄 需要发布的包(含依赖传递): ${packagesToPublish.map((p)=>p.name).join(", ") || "无"}`);
|
|
175
|
+
if (!branch) {
|
|
176
|
+
throw new Error("缺少配置 ci.refName (环境变量 GITHUB_REF_NAME)");
|
|
139
177
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
console.log(`📋 发布顺序: ${sortedPackages.map((p)=>p.name).join(" -> ") || "无"}`);
|
|
178
|
+
const [owner, repo] = repository.split("/");
|
|
179
|
+
if (!owner || !repo) {
|
|
180
|
+
throw new Error(`ci.repository 格式不正确,期望 "owner/repo",实际: "${repository}"`);
|
|
144
181
|
}
|
|
145
182
|
return {
|
|
146
|
-
|
|
147
|
-
|
|
183
|
+
owner,
|
|
184
|
+
repo,
|
|
185
|
+
branch,
|
|
186
|
+
dryRun: options.dryRun ?? false,
|
|
187
|
+
prerelease: options.prerelease,
|
|
188
|
+
ci: options.ci,
|
|
189
|
+
rehearsal: options.rehearsal ?? false
|
|
148
190
|
};
|
|
149
191
|
}
|
|
192
|
+
async execute(context) {
|
|
193
|
+
const publishConf = this.configReader.getPluginConfig("publish");
|
|
194
|
+
const monorepoConf = publishConf.monorepo;
|
|
195
|
+
// CI 环境下自动 fetch tags,确保 release-it 能正确计算版本
|
|
196
|
+
if (context.ci) {
|
|
197
|
+
await this.ensureTagsFetched();
|
|
198
|
+
}
|
|
199
|
+
// 检查是否启用 monorepo 模式
|
|
200
|
+
if (monorepoConf?.enabled) {
|
|
201
|
+
return this.executeMonorepo(context, publishConf);
|
|
202
|
+
}
|
|
203
|
+
// 单包发布模式
|
|
204
|
+
return this.executeSinglePackage(context, publishConf);
|
|
205
|
+
}
|
|
150
206
|
/**
|
|
151
|
-
*
|
|
152
|
-
*/
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
207
|
+
* Monorepo 发布模式:扫描变更包,按依赖顺序发布
|
|
208
|
+
*/ async executeMonorepo(context, publishConf) {
|
|
209
|
+
const { dryRun } = context;
|
|
210
|
+
console.log("\n📦 Monorepo 发布模式");
|
|
211
|
+
console.log("=".repeat(50));
|
|
212
|
+
const propagateDeps = publishConf.monorepo?.propagateDeps ?? true;
|
|
213
|
+
// 分析变更包
|
|
214
|
+
const analysis = await this.monorepoService.analyze(dryRun, propagateDeps);
|
|
215
|
+
if (analysis.packagesToPublish.length === 0) {
|
|
216
|
+
console.log("\n✅ 没有需要发布的包");
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
message: "没有需要发布的包"
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
console.log(`\n🚀 将发布 ${analysis.packagesToPublish.length} 个包`);
|
|
223
|
+
await this.handleBegin(context, publishConf);
|
|
224
|
+
try {
|
|
225
|
+
// 按顺序发布每个包
|
|
226
|
+
for(let i = 0; i < analysis.packagesToPublish.length; i++){
|
|
227
|
+
const pkg = analysis.packagesToPublish[i];
|
|
228
|
+
console.log(`\n[${i + 1}/${analysis.packagesToPublish.length}] 发布 ${pkg.name}`);
|
|
229
|
+
console.log("-".repeat(40));
|
|
230
|
+
await this.executePackageRelease(context, publishConf, pkg);
|
|
161
231
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
232
|
+
await this.handleEnd(context, publishConf);
|
|
233
|
+
return {
|
|
234
|
+
success: true,
|
|
235
|
+
message: `成功发布 ${analysis.packagesToPublish.length} 个包`
|
|
236
|
+
};
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error("\n❌ Monorepo 发布失败:", error instanceof Error ? error.message : error);
|
|
239
|
+
try {
|
|
240
|
+
await this.handleEnd(context, publishConf);
|
|
241
|
+
} catch (unlockError) {
|
|
242
|
+
console.error("⚠️ 解锁分支失败:", unlockError instanceof Error ? unlockError.message : unlockError);
|
|
171
243
|
}
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
message: "Monorepo 发布失败"
|
|
247
|
+
};
|
|
172
248
|
}
|
|
173
|
-
return {
|
|
174
|
-
packages: packages.length > 0 ? packages : undefined
|
|
175
|
-
};
|
|
176
249
|
}
|
|
177
250
|
/**
|
|
178
|
-
*
|
|
179
|
-
*/
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
182
|
-
|
|
251
|
+
* 发布单个包(monorepo 模式)
|
|
252
|
+
*/ async executePackageRelease(context, publishConf, pkg) {
|
|
253
|
+
const { dryRun, prerelease, ci, rehearsal } = context;
|
|
254
|
+
if (rehearsal) {
|
|
255
|
+
console.log(`🎭 [REHEARSAL] 将发布包: ${pkg.name} (${pkg.dir})`);
|
|
256
|
+
} else if (dryRun) {
|
|
257
|
+
console.log(`🔍 [DRY-RUN] 将发布包: ${pkg.name} (${pkg.dir})`);
|
|
183
258
|
}
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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);
|
|
259
|
+
const pkgDir = join(process.cwd(), pkg.dir);
|
|
260
|
+
const originalCwd = process.cwd();
|
|
261
|
+
const config = this.buildReleaseItConfig({
|
|
262
|
+
dryRun,
|
|
263
|
+
prerelease,
|
|
264
|
+
ci,
|
|
265
|
+
rehearsal,
|
|
266
|
+
pkgDir,
|
|
267
|
+
pkgName: pkg.name,
|
|
268
|
+
pkgBase: pkg.dir.split("/").pop() || pkg.dir,
|
|
269
|
+
publishConf
|
|
270
|
+
});
|
|
271
|
+
// 切换到包目录运行 release-it,确保读取正确的 package.json
|
|
272
|
+
process.chdir(pkgDir);
|
|
273
|
+
try {
|
|
274
|
+
await releaseIt(config);
|
|
275
|
+
console.log(`✅ ${pkg.name} 发布完成`);
|
|
276
|
+
} finally{
|
|
277
|
+
// 恢复原工作目录
|
|
278
|
+
process.chdir(originalCwd);
|
|
555
279
|
}
|
|
556
280
|
}
|
|
557
281
|
/**
|
|
@@ -887,274 +611,402 @@ class PublishService {
|
|
|
887
611
|
}
|
|
888
612
|
}
|
|
889
613
|
/**
|
|
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;
|
|
614
|
+
* 同步解锁分支(用于进程退出时的清理)
|
|
615
|
+
*/ unlockBranchSync(context, publishConf) {
|
|
616
|
+
const { owner, repo, branch, dryRun } = context;
|
|
617
|
+
const shouldLockBranch = publishConf.git?.lockBranch ?? true;
|
|
618
|
+
if (!shouldLockBranch || dryRun) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
this.gitProvider.unlockBranchSync(owner, repo, branch);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* 确保 git tags 已获取(CI 环境中 shallow clone 可能缺失 tags)
|
|
625
|
+
* 这对于 release-it 正确计算版本号至关重要
|
|
626
|
+
*/ async ensureTagsFetched() {
|
|
627
|
+
try {
|
|
628
|
+
// 检查是否有 tags
|
|
629
|
+
const existingTags = execSync("git tag --list 2>/dev/null || echo ''", {
|
|
630
|
+
encoding: "utf-8"
|
|
631
|
+
}).trim();
|
|
632
|
+
if (!existingTags) {
|
|
633
|
+
console.log("🏷️ 正在获取 git tags...");
|
|
634
|
+
execSync("git fetch --tags --force", {
|
|
635
|
+
stdio: "inherit"
|
|
636
|
+
});
|
|
637
|
+
console.log("✅ Git tags 已获取");
|
|
638
|
+
}
|
|
639
|
+
} catch (error) {
|
|
640
|
+
console.warn("⚠️ 获取 git tags 失败:", error instanceof Error ? error.message : error);
|
|
641
|
+
console.warn(" 版本计算可能不准确,建议在 CI checkout 时添加 fetch-depth: 0 和 fetch-tags: true");
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// EXTERNAL MODULE: external "fs"
|
|
647
|
+
var external_fs_ = __webpack_require__(421);
|
|
648
|
+
;// CONCATENATED MODULE: ./src/monorepo.service.ts
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
class MonorepoService {
|
|
653
|
+
cwd;
|
|
654
|
+
constructor(){
|
|
655
|
+
this.cwd = process.cwd();
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* 分析 monorepo 变更,返回需要发布的包列表(拓扑排序后)
|
|
659
|
+
* @param dryRun 是否为 dry-run 模式
|
|
660
|
+
* @param propagateDeps 是否传递依赖变更(依赖的包变更时,依赖方也发布)
|
|
661
|
+
*/ async analyze(dryRun, propagateDeps = true) {
|
|
662
|
+
const workspacePackages = this.getWorkspacePackages();
|
|
663
|
+
const allPackages = this.getAllPackageInfos(workspacePackages);
|
|
664
|
+
// 为每个包单独检测变更(基于各自的最新 tag)
|
|
665
|
+
const changedPackages = this.getChangedPackages(allPackages, dryRun);
|
|
666
|
+
if (dryRun) {
|
|
667
|
+
console.log(`📦 直接变更的包: ${changedPackages.map((p)=>p.name).join(", ") || "无"}`);
|
|
668
|
+
}
|
|
669
|
+
// 计算依赖传递,找出所有需要发布的包
|
|
670
|
+
const packagesToPublish = propagateDeps ? this.calculateAffectedPackages(changedPackages, allPackages) : changedPackages;
|
|
671
|
+
if (dryRun) {
|
|
672
|
+
console.log(`🔄 需要发布的包(含依赖传递): ${packagesToPublish.map((p)=>p.name).join(", ") || "无"}`);
|
|
673
|
+
}
|
|
674
|
+
// 拓扑排序
|
|
675
|
+
const sortedPackages = this.topologicalSort(packagesToPublish, allPackages);
|
|
676
|
+
if (dryRun) {
|
|
677
|
+
console.log(`📋 发布顺序: ${sortedPackages.map((p)=>p.name).join(" -> ") || "无"}`);
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
changedPackages,
|
|
681
|
+
packagesToPublish: sortedPackages
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* 简单解析 pnpm-workspace.yaml(只提取 packages 数组)
|
|
686
|
+
*/ parseSimpleYaml(content) {
|
|
687
|
+
const packages = [];
|
|
688
|
+
const lines = content.split("\n");
|
|
689
|
+
let inPackages = false;
|
|
690
|
+
for (const line of lines){
|
|
691
|
+
const trimmed = line.trim();
|
|
692
|
+
if (trimmed === "packages:") {
|
|
693
|
+
inPackages = true;
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (inPackages) {
|
|
697
|
+
if (trimmed.startsWith("- ")) {
|
|
698
|
+
// 提取包路径,去除引号
|
|
699
|
+
let pkg = trimmed.slice(2).trim();
|
|
700
|
+
pkg = pkg.replace(/^["']|["']$/g, "");
|
|
701
|
+
packages.push(pkg);
|
|
702
|
+
} else if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("-")) {
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
packages: packages.length > 0 ? packages : undefined
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* 从 pnpm-workspace.yaml 读取 workspace 包配置
|
|
713
|
+
*/ getWorkspacePackages() {
|
|
714
|
+
const workspaceFile = join(this.cwd, "pnpm-workspace.yaml");
|
|
715
|
+
if (!(0,external_fs_.existsSync)(workspaceFile)) {
|
|
716
|
+
throw new Error("未找到 pnpm-workspace.yaml 文件");
|
|
717
|
+
}
|
|
718
|
+
const content = (0,external_fs_.readFileSync)(workspaceFile, "utf-8");
|
|
719
|
+
const config = this.parseSimpleYaml(content);
|
|
720
|
+
if (!config.packages || !Array.isArray(config.packages)) {
|
|
721
|
+
throw new Error("pnpm-workspace.yaml 中未配置 packages");
|
|
722
|
+
}
|
|
723
|
+
return config.packages;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* 展开 workspace 包配置,获取所有实际的包目录
|
|
727
|
+
*/ expandWorkspacePatterns(patterns) {
|
|
728
|
+
const dirs = [];
|
|
729
|
+
for (const pattern of patterns){
|
|
730
|
+
if (pattern.includes("*")) {
|
|
731
|
+
// 使用 glob 展开,这里简化处理,只支持 extensions/* 这种模式
|
|
732
|
+
const baseDir = pattern.replace("/*", "");
|
|
733
|
+
const basePath = join(this.cwd, baseDir);
|
|
734
|
+
if ((0,external_fs_.existsSync)(basePath)) {
|
|
735
|
+
const { readdirSync, statSync } = __webpack_require__(421);
|
|
736
|
+
const entries = readdirSync(basePath);
|
|
737
|
+
for (const entry of entries){
|
|
738
|
+
const entryPath = join(basePath, entry);
|
|
739
|
+
if (statSync(entryPath).isDirectory()) {
|
|
740
|
+
const pkgJson = join(entryPath, "package.json");
|
|
741
|
+
if ((0,external_fs_.existsSync)(pkgJson)) {
|
|
742
|
+
dirs.push(join(baseDir, entry));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
} else {
|
|
748
|
+
// 直接目录
|
|
749
|
+
const pkgJson = join(this.cwd, pattern, "package.json");
|
|
750
|
+
if ((0,external_fs_.existsSync)(pkgJson)) {
|
|
751
|
+
dirs.push(pattern);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return dirs;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* 获取所有包的详细信息(排除私有包)
|
|
759
|
+
*/ getAllPackageInfos(patterns) {
|
|
760
|
+
const dirs = this.expandWorkspacePatterns(patterns);
|
|
761
|
+
const packages = [];
|
|
762
|
+
for (const dir of dirs){
|
|
763
|
+
const pkgJsonPath = join(this.cwd, dir, "package.json");
|
|
764
|
+
if (!(0,external_fs_.existsSync)(pkgJsonPath)) continue;
|
|
765
|
+
const pkgJson = JSON.parse((0,external_fs_.readFileSync)(pkgJsonPath, "utf-8"));
|
|
766
|
+
// 跳过私有包
|
|
767
|
+
if (pkgJson.private === true) continue;
|
|
768
|
+
const workspaceDeps = this.extractWorkspaceDeps(pkgJson);
|
|
769
|
+
packages.push({
|
|
770
|
+
dir,
|
|
771
|
+
name: pkgJson.name,
|
|
772
|
+
version: pkgJson.version,
|
|
773
|
+
workspaceDeps
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
return packages;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* 提取包的 workspace 依赖
|
|
780
|
+
*/ extractWorkspaceDeps(pkgJson) {
|
|
781
|
+
const deps = [];
|
|
782
|
+
const allDeps = {
|
|
783
|
+
...pkgJson.dependencies,
|
|
784
|
+
...pkgJson.devDependencies,
|
|
785
|
+
...pkgJson.peerDependencies
|
|
786
|
+
};
|
|
787
|
+
for (const [name, version] of Object.entries(allDeps)){
|
|
788
|
+
if (version && (version.startsWith("workspace:") || version === "*")) {
|
|
789
|
+
deps.push(name);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return deps;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* 检测每个包的变更(基于各自的最新 tag)
|
|
796
|
+
*/ getChangedPackages(allPackages, dryRun) {
|
|
797
|
+
const changedPackages = [];
|
|
798
|
+
for (const pkg of allPackages){
|
|
799
|
+
const hasChanges = this.hasPackageChanges(pkg);
|
|
800
|
+
if (hasChanges) {
|
|
801
|
+
changedPackages.push(pkg);
|
|
802
|
+
}
|
|
803
|
+
if (dryRun) {
|
|
804
|
+
console.log(` ${hasChanges ? "✅" : "⭕"} ${pkg.name}`);
|
|
805
|
+
}
|
|
896
806
|
}
|
|
897
|
-
|
|
807
|
+
return changedPackages;
|
|
898
808
|
}
|
|
899
809
|
/**
|
|
900
|
-
*
|
|
901
|
-
|
|
902
|
-
*/ async ensureTagsFetched() {
|
|
810
|
+
* 检测单个包是否有变更(基于该包的最新 tag)
|
|
811
|
+
*/ hasPackageChanges(pkg) {
|
|
903
812
|
try {
|
|
904
|
-
//
|
|
905
|
-
const
|
|
813
|
+
// 获取该包的最新 tag(格式: @scope/pkg@version 或 pkg@version)
|
|
814
|
+
const tagPattern = `${pkg.name}@*`;
|
|
815
|
+
const latestTag = execSync(`git describe --tags --abbrev=0 --match "${tagPattern}" 2>/dev/null || echo ''`, {
|
|
816
|
+
cwd: this.cwd,
|
|
906
817
|
encoding: "utf-8"
|
|
907
818
|
}).trim();
|
|
908
|
-
if (!
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
819
|
+
if (!latestTag) {
|
|
820
|
+
// 没有 tag,说明是新包,需要发布
|
|
821
|
+
console.log(`📌 ${pkg.name}: 无 tag,需要发布`);
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
// 检测从该 tag 到 HEAD,该包目录下是否有变更
|
|
825
|
+
const diffOutput = execSync(`git diff --name-only "${latestTag}"..HEAD -- "${pkg.dir}"`, {
|
|
826
|
+
cwd: this.cwd,
|
|
827
|
+
encoding: "utf-8"
|
|
828
|
+
}).trim();
|
|
829
|
+
const hasChanges = diffOutput.length > 0;
|
|
830
|
+
if (hasChanges) {
|
|
831
|
+
console.log(`📌 ${pkg.name}: ${latestTag} -> HEAD 有变更`);
|
|
832
|
+
console.log(` 变更文件: ${diffOutput.split("\n").slice(0, 3).join(", ")}${diffOutput.split("\n").length > 3 ? "..." : ""}`);
|
|
914
833
|
}
|
|
834
|
+
return hasChanges;
|
|
915
835
|
} catch (error) {
|
|
916
|
-
|
|
917
|
-
console.
|
|
836
|
+
// 出错时保守处理,认为有变更
|
|
837
|
+
console.log(`📌 ${pkg.name}: 检测出错,保守处理为有变更`);
|
|
838
|
+
console.log(` 错误: ${error instanceof Error ? error.message : error}`);
|
|
839
|
+
return true;
|
|
918
840
|
}
|
|
919
841
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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;
|
|
842
|
+
/**
|
|
843
|
+
* 将变更文件映射到包目录
|
|
844
|
+
*/ mapFilesToPackages(files, patterns) {
|
|
845
|
+
const packageDirs = this.expandWorkspacePatterns(patterns);
|
|
846
|
+
const changedPackages = new Set();
|
|
847
|
+
for (const file of files){
|
|
848
|
+
for (const dir of packageDirs){
|
|
849
|
+
if (file.startsWith(dir + "/") || file === dir) {
|
|
850
|
+
changedPackages.add(dir);
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return changedPackages;
|
|
949
856
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
857
|
+
/**
|
|
858
|
+
* 计算受影响的包(包含依赖传递)
|
|
859
|
+
*/ calculateAffectedPackages(changedPackages, allPackages) {
|
|
860
|
+
const changedNames = new Set(changedPackages.map((p)=>p.name));
|
|
861
|
+
const affectedNames = new Set(changedNames);
|
|
862
|
+
// 构建反向依赖图:谁依赖了我
|
|
863
|
+
const reverseDeps = new Map();
|
|
864
|
+
for (const pkg of allPackages){
|
|
865
|
+
for (const dep of pkg.workspaceDeps){
|
|
866
|
+
if (!reverseDeps.has(dep)) {
|
|
867
|
+
reverseDeps.set(dep, new Set());
|
|
868
|
+
}
|
|
869
|
+
reverseDeps.get(dep).add(pkg.name);
|
|
870
|
+
}
|
|
955
871
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
872
|
+
// BFS 传递依赖
|
|
873
|
+
const queue = [
|
|
874
|
+
...changedNames
|
|
875
|
+
];
|
|
876
|
+
while(queue.length > 0){
|
|
877
|
+
const current = queue.shift();
|
|
878
|
+
const dependents = reverseDeps.get(current);
|
|
879
|
+
if (dependents) {
|
|
880
|
+
for (const dependent of dependents){
|
|
881
|
+
if (!affectedNames.has(dependent)) {
|
|
882
|
+
affectedNames.add(dependent);
|
|
883
|
+
queue.push(dependent);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
964
887
|
}
|
|
888
|
+
return allPackages.filter((p)=>affectedNames.has(p.name));
|
|
965
889
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
890
|
+
/**
|
|
891
|
+
* 拓扑排序:被依赖的包先发布
|
|
892
|
+
*/ topologicalSort(packages, _allPackages) {
|
|
893
|
+
const packageNames = new Set(packages.map((p)=>p.name));
|
|
894
|
+
const nameToPackage = new Map(packages.map((p)=>[
|
|
895
|
+
p.name,
|
|
896
|
+
p
|
|
897
|
+
]));
|
|
898
|
+
// 构建依赖图(只考虑待发布包之间的依赖)
|
|
899
|
+
const inDegree = new Map();
|
|
900
|
+
const graph = new Map();
|
|
901
|
+
for (const pkg of packages){
|
|
902
|
+
inDegree.set(pkg.name, 0);
|
|
903
|
+
graph.set(pkg.name, []);
|
|
904
|
+
}
|
|
905
|
+
for (const pkg of packages){
|
|
906
|
+
for (const dep of pkg.workspaceDeps){
|
|
907
|
+
if (packageNames.has(dep)) {
|
|
908
|
+
graph.get(dep).push(pkg.name);
|
|
909
|
+
inDegree.set(pkg.name, (inDegree.get(pkg.name) || 0) + 1);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
// Kahn's algorithm
|
|
914
|
+
const queue = [];
|
|
915
|
+
for (const [name, degree] of inDegree){
|
|
916
|
+
if (degree === 0) {
|
|
917
|
+
queue.push(name);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const sorted = [];
|
|
921
|
+
while(queue.length > 0){
|
|
922
|
+
const current = queue.shift();
|
|
923
|
+
sorted.push(nameToPackage.get(current));
|
|
924
|
+
for (const neighbor of graph.get(current) || []){
|
|
925
|
+
const newDegree = (inDegree.get(neighbor) || 0) - 1;
|
|
926
|
+
inDegree.set(neighbor, newDegree);
|
|
927
|
+
if (newDegree === 0) {
|
|
928
|
+
queue.push(neighbor);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (sorted.length !== packages.length) {
|
|
933
|
+
throw new Error("检测到循环依赖,无法确定发布顺序");
|
|
934
|
+
}
|
|
935
|
+
return sorted;
|
|
977
936
|
}
|
|
978
937
|
}
|
|
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
938
|
|
|
1122
939
|
;// CONCATENATED MODULE: ./src/index.ts
|
|
1123
940
|
|
|
1124
941
|
|
|
1125
942
|
|
|
1126
943
|
|
|
1127
|
-
|
|
944
|
+
|
|
945
|
+
const extension = defineExtension({
|
|
1128
946
|
name: "publish",
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
],
|
|
947
|
+
version: "1.0.0",
|
|
948
|
+
description: core_t("publish:extensionDescription"),
|
|
1132
949
|
configKey: "publish",
|
|
1133
950
|
configSchema: ()=>publishSchema,
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
951
|
+
commands: [
|
|
952
|
+
{
|
|
953
|
+
name: "publish",
|
|
954
|
+
description: core_t("publish:description"),
|
|
955
|
+
options: [
|
|
956
|
+
{
|
|
957
|
+
flags: "-d, --dry-run",
|
|
958
|
+
description: core_t("common.options.dryRun")
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
flags: "-c, --ci",
|
|
962
|
+
description: core_t("common.options.ci")
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
flags: "-p, --prerelease <tag>",
|
|
966
|
+
description: core_t("publish:options.prerelease")
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
flags: "-r, --rehearsal",
|
|
970
|
+
description: core_t("publish:options.rehearsal")
|
|
971
|
+
}
|
|
972
|
+
],
|
|
973
|
+
run: async (_args, options, ctx)=>{
|
|
974
|
+
const gitProvider = ctx.getService("gitProvider");
|
|
975
|
+
const configReader = ctx.getService("config");
|
|
976
|
+
if (!gitProvider) {
|
|
977
|
+
ctx.output.error("publish 命令需要配置 Git Provider");
|
|
978
|
+
process.exit(1);
|
|
979
|
+
}
|
|
980
|
+
const monorepoService = new MonorepoService();
|
|
981
|
+
const publishService = new PublishService(gitProvider, ctx.config, configReader, monorepoService);
|
|
982
|
+
const publishOptions = {
|
|
983
|
+
dryRun: !!options?.dryRun,
|
|
984
|
+
ci: !!options?.ci,
|
|
985
|
+
prerelease: options?.prerelease,
|
|
986
|
+
rehearsal: !!options?.rehearsal
|
|
987
|
+
};
|
|
988
|
+
if (publishOptions.rehearsal) {
|
|
989
|
+
console.log(core_t("publish:rehearsalMode"));
|
|
990
|
+
} else if (publishOptions.dryRun) {
|
|
991
|
+
console.log(core_t("publish:dryRunMode"));
|
|
992
|
+
}
|
|
993
|
+
try {
|
|
994
|
+
const context = publishService.getContextFromEnv(publishOptions);
|
|
995
|
+
await publishService.execute(context);
|
|
996
|
+
} catch (error) {
|
|
997
|
+
ctx.output.error(core_t("common.executionFailed", {
|
|
998
|
+
error: error instanceof Error ? error.message : error
|
|
999
|
+
}));
|
|
1000
|
+
process.exit(1);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
]
|
|
1005
|
+
});
|
|
1006
|
+
/* export default */ const src = (extension);
|
|
1150
1007
|
|
|
1151
1008
|
})();
|
|
1152
1009
|
|
|
1153
|
-
var
|
|
1154
|
-
var
|
|
1155
|
-
|
|
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 };
|
|
1010
|
+
var __webpack_exports__default = __webpack_exports__.A;
|
|
1011
|
+
var __webpack_exports__extension = __webpack_exports__.c;
|
|
1012
|
+
export { __webpack_exports__default as default, __webpack_exports__extension as extension };
|