@releasekit/publish 0.3.0 → 0.4.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/README.md +14 -3
- package/dist/chunk-ZMMZ7S6G.js +1864 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +80 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +38 -0
- package/docs/github-releases.md +177 -0
- package/package.json +2 -1
|
@@ -0,0 +1,1864 @@
|
|
|
1
|
+
// ../core/dist/index.js
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
function readPackageVersion(importMetaUrl) {
|
|
7
|
+
try {
|
|
8
|
+
const dir = path.dirname(fileURLToPath(importMetaUrl));
|
|
9
|
+
const packageJsonPath = path.resolve(dir, "../package.json");
|
|
10
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
11
|
+
return packageJson.version ?? "0.0.0";
|
|
12
|
+
} catch {
|
|
13
|
+
return "0.0.0";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
var LOG_LEVELS = {
|
|
17
|
+
error: 0,
|
|
18
|
+
warn: 1,
|
|
19
|
+
info: 2,
|
|
20
|
+
debug: 3,
|
|
21
|
+
trace: 4
|
|
22
|
+
};
|
|
23
|
+
var PREFIXES = {
|
|
24
|
+
error: "[ERROR]",
|
|
25
|
+
warn: "[WARN]",
|
|
26
|
+
info: "[INFO]",
|
|
27
|
+
debug: "[DEBUG]",
|
|
28
|
+
trace: "[TRACE]"
|
|
29
|
+
};
|
|
30
|
+
var COLORS = {
|
|
31
|
+
error: chalk.red,
|
|
32
|
+
warn: chalk.yellow,
|
|
33
|
+
info: chalk.blue,
|
|
34
|
+
debug: chalk.gray,
|
|
35
|
+
trace: chalk.dim
|
|
36
|
+
};
|
|
37
|
+
var currentLevel = "info";
|
|
38
|
+
var quietMode = false;
|
|
39
|
+
function setLogLevel(level) {
|
|
40
|
+
currentLevel = level;
|
|
41
|
+
}
|
|
42
|
+
function setJsonMode(_json) {
|
|
43
|
+
}
|
|
44
|
+
function shouldLog(level) {
|
|
45
|
+
if (quietMode && level !== "error") return false;
|
|
46
|
+
return LOG_LEVELS[level] <= LOG_LEVELS[currentLevel];
|
|
47
|
+
}
|
|
48
|
+
function log(message, level = "info") {
|
|
49
|
+
if (!shouldLog(level)) return;
|
|
50
|
+
const formatted = COLORS[level](`${PREFIXES[level]} ${message}`);
|
|
51
|
+
console.error(formatted);
|
|
52
|
+
}
|
|
53
|
+
function warn(message) {
|
|
54
|
+
log(message, "warn");
|
|
55
|
+
}
|
|
56
|
+
function info(message) {
|
|
57
|
+
log(message, "info");
|
|
58
|
+
}
|
|
59
|
+
function success(message) {
|
|
60
|
+
if (!shouldLog("info")) return;
|
|
61
|
+
console.error(chalk.green(`[SUCCESS] ${message}`));
|
|
62
|
+
}
|
|
63
|
+
function debug(message) {
|
|
64
|
+
log(message, "debug");
|
|
65
|
+
}
|
|
66
|
+
var ReleaseKitError = class _ReleaseKitError extends Error {
|
|
67
|
+
constructor(message) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = this.constructor.name;
|
|
70
|
+
}
|
|
71
|
+
logError() {
|
|
72
|
+
log(this.message, "error");
|
|
73
|
+
if (this.suggestions.length > 0) {
|
|
74
|
+
log("\nSuggested solutions:", "info");
|
|
75
|
+
for (const [i, suggestion] of this.suggestions.entries()) {
|
|
76
|
+
log(`${i + 1}. ${suggestion}`, "info");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
static isReleaseKitError(error2) {
|
|
81
|
+
return error2 instanceof _ReleaseKitError;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var EXIT_CODES = {
|
|
85
|
+
SUCCESS: 0,
|
|
86
|
+
GENERAL_ERROR: 1,
|
|
87
|
+
CONFIG_ERROR: 2,
|
|
88
|
+
INPUT_ERROR: 3,
|
|
89
|
+
TEMPLATE_ERROR: 4,
|
|
90
|
+
LLM_ERROR: 5,
|
|
91
|
+
GITHUB_ERROR: 6,
|
|
92
|
+
GIT_ERROR: 7,
|
|
93
|
+
VERSION_ERROR: 8,
|
|
94
|
+
PUBLISH_ERROR: 9
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// ../config/dist/index.js
|
|
98
|
+
import * as fs2 from "fs";
|
|
99
|
+
import * as path2 from "path";
|
|
100
|
+
import * as TOML from "smol-toml";
|
|
101
|
+
import * as fs3 from "fs";
|
|
102
|
+
import * as path3 from "path";
|
|
103
|
+
import { z as z2 } from "zod";
|
|
104
|
+
import { z } from "zod";
|
|
105
|
+
import * as fs22 from "fs";
|
|
106
|
+
import * as os from "os";
|
|
107
|
+
import * as path22 from "path";
|
|
108
|
+
function parseCargoToml(cargoPath) {
|
|
109
|
+
const content = fs2.readFileSync(cargoPath, "utf-8");
|
|
110
|
+
return TOML.parse(content);
|
|
111
|
+
}
|
|
112
|
+
var ConfigError = class extends ReleaseKitError {
|
|
113
|
+
code = "CONFIG_ERROR";
|
|
114
|
+
suggestions;
|
|
115
|
+
constructor(message, suggestions) {
|
|
116
|
+
super(message);
|
|
117
|
+
this.suggestions = suggestions ?? [
|
|
118
|
+
"Check that releasekit.config.json exists and is valid JSON",
|
|
119
|
+
"Run with --verbose for more details"
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
function mergeGitConfig(topLevel, packageLevel) {
|
|
124
|
+
if (!topLevel && !packageLevel) return void 0;
|
|
125
|
+
const base = topLevel ?? {
|
|
126
|
+
remote: "origin",
|
|
127
|
+
branch: "main",
|
|
128
|
+
pushMethod: "auto"
|
|
129
|
+
};
|
|
130
|
+
if (!packageLevel) return base;
|
|
131
|
+
return {
|
|
132
|
+
remote: packageLevel.remote ?? base.remote,
|
|
133
|
+
branch: packageLevel.branch ?? base.branch,
|
|
134
|
+
pushMethod: packageLevel.pushMethod ?? base.pushMethod,
|
|
135
|
+
httpsTokenEnv: packageLevel.httpsTokenEnv ?? base.httpsTokenEnv,
|
|
136
|
+
push: packageLevel.push,
|
|
137
|
+
skipHooks: packageLevel.skipHooks ?? base.skipHooks
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
var MAX_JSONC_LENGTH = 1e5;
|
|
141
|
+
function parseJsonc(content) {
|
|
142
|
+
if (content.length > MAX_JSONC_LENGTH) {
|
|
143
|
+
throw new Error(`JSONC content too long: ${content.length} characters (max ${MAX_JSONC_LENGTH})`);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(content);
|
|
147
|
+
} catch {
|
|
148
|
+
const cleaned = content.replace(/\/\/[^\r\n]{0,10000}$/gm, "").replace(/\/\*[\s\S]{0,50000}?\*\//g, "").trim();
|
|
149
|
+
return JSON.parse(cleaned);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
var GitConfigSchema = z.object({
|
|
153
|
+
remote: z.string().default("origin"),
|
|
154
|
+
branch: z.string().default("main"),
|
|
155
|
+
pushMethod: z.enum(["auto", "ssh", "https"]).default("auto"),
|
|
156
|
+
/**
|
|
157
|
+
* Optional env var name containing a GitHub token for HTTPS pushes.
|
|
158
|
+
* When set, publish steps can use this token without mutating git remotes.
|
|
159
|
+
*/
|
|
160
|
+
httpsTokenEnv: z.string().optional(),
|
|
161
|
+
push: z.boolean().optional(),
|
|
162
|
+
skipHooks: z.boolean().optional()
|
|
163
|
+
});
|
|
164
|
+
var MonorepoConfigSchema = z.object({
|
|
165
|
+
mode: z.enum(["root", "packages", "both"]).optional(),
|
|
166
|
+
rootPath: z.string().optional(),
|
|
167
|
+
packagesPath: z.string().optional(),
|
|
168
|
+
mainPackage: z.string().optional()
|
|
169
|
+
});
|
|
170
|
+
var BranchPatternSchema = z.object({
|
|
171
|
+
pattern: z.string(),
|
|
172
|
+
releaseType: z.enum(["major", "minor", "patch", "prerelease"])
|
|
173
|
+
});
|
|
174
|
+
var VersionCargoConfigSchema = z.object({
|
|
175
|
+
enabled: z.boolean().default(true),
|
|
176
|
+
paths: z.array(z.string()).optional()
|
|
177
|
+
});
|
|
178
|
+
var VersionConfigSchema = z.object({
|
|
179
|
+
tagTemplate: z.string().default("v{version}"),
|
|
180
|
+
packageSpecificTags: z.boolean().default(false),
|
|
181
|
+
preset: z.string().default("conventional"),
|
|
182
|
+
sync: z.boolean().default(true),
|
|
183
|
+
packages: z.array(z.string()).default([]),
|
|
184
|
+
mainPackage: z.string().optional(),
|
|
185
|
+
updateInternalDependencies: z.enum(["major", "minor", "patch", "no-internal-update"]).default("minor"),
|
|
186
|
+
skip: z.array(z.string()).optional(),
|
|
187
|
+
commitMessage: z.string().optional(),
|
|
188
|
+
versionStrategy: z.enum(["branchPattern", "commitMessage"]).default("commitMessage"),
|
|
189
|
+
branchPatterns: z.array(BranchPatternSchema).optional(),
|
|
190
|
+
defaultReleaseType: z.enum(["major", "minor", "patch", "prerelease"]).optional(),
|
|
191
|
+
mismatchStrategy: z.enum(["error", "warn", "ignore", "prefer-package", "prefer-git"]).default("warn"),
|
|
192
|
+
versionPrefix: z.string().default(""),
|
|
193
|
+
prereleaseIdentifier: z.string().optional(),
|
|
194
|
+
strictReachable: z.boolean().default(false),
|
|
195
|
+
cargo: VersionCargoConfigSchema.optional()
|
|
196
|
+
});
|
|
197
|
+
var NpmConfigSchema = z.object({
|
|
198
|
+
enabled: z.boolean().default(true),
|
|
199
|
+
auth: z.enum(["auto", "oidc", "token"]).default("auto"),
|
|
200
|
+
provenance: z.boolean().default(true),
|
|
201
|
+
access: z.enum(["public", "restricted"]).default("public"),
|
|
202
|
+
registry: z.string().default("https://registry.npmjs.org"),
|
|
203
|
+
copyFiles: z.array(z.string()).default(["LICENSE"]),
|
|
204
|
+
tag: z.string().default("latest")
|
|
205
|
+
});
|
|
206
|
+
var CargoPublishConfigSchema = z.object({
|
|
207
|
+
enabled: z.boolean().default(false),
|
|
208
|
+
noVerify: z.boolean().default(false),
|
|
209
|
+
publishOrder: z.array(z.string()).default([]),
|
|
210
|
+
clean: z.boolean().default(false)
|
|
211
|
+
});
|
|
212
|
+
var PublishGitConfigSchema = z.object({
|
|
213
|
+
push: z.boolean().default(true),
|
|
214
|
+
pushMethod: z.enum(["auto", "ssh", "https"]).optional(),
|
|
215
|
+
remote: z.string().optional(),
|
|
216
|
+
branch: z.string().optional(),
|
|
217
|
+
httpsTokenEnv: z.string().optional(),
|
|
218
|
+
skipHooks: z.boolean().optional()
|
|
219
|
+
});
|
|
220
|
+
var GitHubReleaseConfigSchema = z.object({
|
|
221
|
+
enabled: z.boolean().default(true),
|
|
222
|
+
draft: z.boolean().default(true),
|
|
223
|
+
perPackage: z.boolean().default(true),
|
|
224
|
+
prerelease: z.union([z.literal("auto"), z.boolean()]).default("auto"),
|
|
225
|
+
/**
|
|
226
|
+
* Controls the source for the GitHub release body.
|
|
227
|
+
* - 'auto': Use release notes if enabled, else changelog, else GitHub auto-generated.
|
|
228
|
+
* - 'releaseNotes': Use LLM-generated release notes (requires notes.releaseNotes.enabled: true).
|
|
229
|
+
* - 'changelog': Use formatted changelog entries.
|
|
230
|
+
* - 'generated': Use GitHub's auto-generated notes.
|
|
231
|
+
* - 'none': No body.
|
|
232
|
+
*/
|
|
233
|
+
body: z.enum(["auto", "releaseNotes", "changelog", "generated", "none"]).default("auto")
|
|
234
|
+
});
|
|
235
|
+
var VerifyRegistryConfigSchema = z.object({
|
|
236
|
+
enabled: z.boolean().default(true),
|
|
237
|
+
maxAttempts: z.number().int().positive().default(5),
|
|
238
|
+
initialDelay: z.number().int().positive().default(15e3),
|
|
239
|
+
backoffMultiplier: z.number().positive().default(2)
|
|
240
|
+
});
|
|
241
|
+
var VerifyConfigSchema = z.object({
|
|
242
|
+
npm: VerifyRegistryConfigSchema.default({
|
|
243
|
+
enabled: true,
|
|
244
|
+
maxAttempts: 5,
|
|
245
|
+
initialDelay: 15e3,
|
|
246
|
+
backoffMultiplier: 2
|
|
247
|
+
}),
|
|
248
|
+
cargo: VerifyRegistryConfigSchema.default({
|
|
249
|
+
enabled: true,
|
|
250
|
+
maxAttempts: 10,
|
|
251
|
+
initialDelay: 3e4,
|
|
252
|
+
backoffMultiplier: 2
|
|
253
|
+
})
|
|
254
|
+
});
|
|
255
|
+
var PublishConfigSchema = z.object({
|
|
256
|
+
git: PublishGitConfigSchema.optional(),
|
|
257
|
+
npm: NpmConfigSchema.default({
|
|
258
|
+
enabled: true,
|
|
259
|
+
auth: "auto",
|
|
260
|
+
provenance: true,
|
|
261
|
+
access: "public",
|
|
262
|
+
registry: "https://registry.npmjs.org",
|
|
263
|
+
copyFiles: ["LICENSE"],
|
|
264
|
+
tag: "latest"
|
|
265
|
+
}),
|
|
266
|
+
cargo: CargoPublishConfigSchema.default({
|
|
267
|
+
enabled: false,
|
|
268
|
+
noVerify: false,
|
|
269
|
+
publishOrder: [],
|
|
270
|
+
clean: false
|
|
271
|
+
}),
|
|
272
|
+
githubRelease: GitHubReleaseConfigSchema.default({
|
|
273
|
+
enabled: true,
|
|
274
|
+
draft: true,
|
|
275
|
+
perPackage: true,
|
|
276
|
+
prerelease: "auto",
|
|
277
|
+
body: "auto"
|
|
278
|
+
}),
|
|
279
|
+
verify: VerifyConfigSchema.default({
|
|
280
|
+
npm: {
|
|
281
|
+
enabled: true,
|
|
282
|
+
maxAttempts: 5,
|
|
283
|
+
initialDelay: 15e3,
|
|
284
|
+
backoffMultiplier: 2
|
|
285
|
+
},
|
|
286
|
+
cargo: {
|
|
287
|
+
enabled: true,
|
|
288
|
+
maxAttempts: 10,
|
|
289
|
+
initialDelay: 3e4,
|
|
290
|
+
backoffMultiplier: 2
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
});
|
|
294
|
+
var TemplateConfigSchema = z.object({
|
|
295
|
+
path: z.string().optional(),
|
|
296
|
+
engine: z.enum(["handlebars", "liquid", "ejs"]).optional()
|
|
297
|
+
});
|
|
298
|
+
var LocationModeSchema = z.enum(["root", "packages", "both"]);
|
|
299
|
+
var ChangelogConfigSchema = z.object({
|
|
300
|
+
mode: LocationModeSchema.optional(),
|
|
301
|
+
file: z.string().optional(),
|
|
302
|
+
templates: TemplateConfigSchema.optional()
|
|
303
|
+
});
|
|
304
|
+
var LLMOptionsSchema = z.object({
|
|
305
|
+
timeout: z.number().optional(),
|
|
306
|
+
maxTokens: z.number().optional(),
|
|
307
|
+
temperature: z.number().optional()
|
|
308
|
+
});
|
|
309
|
+
var LLMRetryConfigSchema = z.object({
|
|
310
|
+
maxAttempts: z.number().int().positive().optional(),
|
|
311
|
+
initialDelay: z.number().nonnegative().optional(),
|
|
312
|
+
maxDelay: z.number().positive().optional(),
|
|
313
|
+
backoffFactor: z.number().positive().optional()
|
|
314
|
+
});
|
|
315
|
+
var LLMTasksConfigSchema = z.object({
|
|
316
|
+
summarize: z.boolean().optional(),
|
|
317
|
+
enhance: z.boolean().optional(),
|
|
318
|
+
categorize: z.boolean().optional(),
|
|
319
|
+
releaseNotes: z.boolean().optional()
|
|
320
|
+
});
|
|
321
|
+
var LLMCategorySchema = z.object({
|
|
322
|
+
name: z.string(),
|
|
323
|
+
description: z.string(),
|
|
324
|
+
scopes: z.array(z.string()).optional()
|
|
325
|
+
});
|
|
326
|
+
var ScopeRulesSchema = z.object({
|
|
327
|
+
allowed: z.array(z.string()).optional(),
|
|
328
|
+
caseSensitive: z.boolean().default(false),
|
|
329
|
+
invalidScopeAction: z.enum(["remove", "keep", "fallback"]).default("remove"),
|
|
330
|
+
fallbackScope: z.string().optional()
|
|
331
|
+
});
|
|
332
|
+
var ScopeConfigSchema = z.object({
|
|
333
|
+
mode: z.enum(["restricted", "packages", "none", "unrestricted"]).default("unrestricted"),
|
|
334
|
+
rules: ScopeRulesSchema.optional()
|
|
335
|
+
});
|
|
336
|
+
var LLMPromptOverridesSchema = z.object({
|
|
337
|
+
enhance: z.string().optional(),
|
|
338
|
+
categorize: z.string().optional(),
|
|
339
|
+
enhanceAndCategorize: z.string().optional(),
|
|
340
|
+
summarize: z.string().optional(),
|
|
341
|
+
releaseNotes: z.string().optional()
|
|
342
|
+
});
|
|
343
|
+
var LLMPromptsConfigSchema = z.object({
|
|
344
|
+
instructions: LLMPromptOverridesSchema.optional(),
|
|
345
|
+
templates: LLMPromptOverridesSchema.optional()
|
|
346
|
+
});
|
|
347
|
+
var LLMConfigSchema = z.object({
|
|
348
|
+
provider: z.string(),
|
|
349
|
+
model: z.string(),
|
|
350
|
+
baseURL: z.string().optional(),
|
|
351
|
+
apiKey: z.string().optional(),
|
|
352
|
+
options: LLMOptionsSchema.optional(),
|
|
353
|
+
concurrency: z.number().int().positive().optional(),
|
|
354
|
+
retry: LLMRetryConfigSchema.optional(),
|
|
355
|
+
tasks: LLMTasksConfigSchema.optional(),
|
|
356
|
+
categories: z.array(LLMCategorySchema).optional(),
|
|
357
|
+
style: z.string().optional(),
|
|
358
|
+
scopes: ScopeConfigSchema.optional(),
|
|
359
|
+
prompts: LLMPromptsConfigSchema.optional()
|
|
360
|
+
});
|
|
361
|
+
var ReleaseNotesConfigSchema = z.object({
|
|
362
|
+
mode: LocationModeSchema.optional(),
|
|
363
|
+
file: z.string().optional(),
|
|
364
|
+
templates: TemplateConfigSchema.optional(),
|
|
365
|
+
llm: LLMConfigSchema.optional()
|
|
366
|
+
});
|
|
367
|
+
var NotesInputConfigSchema = z.object({
|
|
368
|
+
source: z.string().optional(),
|
|
369
|
+
file: z.string().optional()
|
|
370
|
+
});
|
|
371
|
+
var NotesConfigSchema = z.object({
|
|
372
|
+
changelog: z.union([z.literal(false), ChangelogConfigSchema]).optional(),
|
|
373
|
+
releaseNotes: z.union([z.literal(false), ReleaseNotesConfigSchema]).optional(),
|
|
374
|
+
updateStrategy: z.enum(["prepend", "regenerate"]).optional()
|
|
375
|
+
});
|
|
376
|
+
var CILabelsConfigSchema = z.object({
|
|
377
|
+
stable: z.string().default("release:stable"),
|
|
378
|
+
prerelease: z.string().default("release:prerelease"),
|
|
379
|
+
skip: z.string().default("release:skip"),
|
|
380
|
+
major: z.string().default("release:major"),
|
|
381
|
+
minor: z.string().default("release:minor"),
|
|
382
|
+
patch: z.string().default("release:patch")
|
|
383
|
+
});
|
|
384
|
+
var CIConfigSchema = z.object({
|
|
385
|
+
releaseStrategy: z.enum(["manual", "direct", "standing-pr", "scheduled"]).default("direct"),
|
|
386
|
+
releaseTrigger: z.enum(["commit", "label"]).default("label"),
|
|
387
|
+
prPreview: z.boolean().default(true),
|
|
388
|
+
autoRelease: z.boolean().default(false),
|
|
389
|
+
/**
|
|
390
|
+
* Commit message prefixes that should not trigger a release.
|
|
391
|
+
* Defaults to `['chore: release ']` to match the release commit template
|
|
392
|
+
* (`chore: release ${packageName} v${version}`) and provide a
|
|
393
|
+
* secondary loop-prevention guard alongside `[skip ci]`.
|
|
394
|
+
*/
|
|
395
|
+
skipPatterns: z.array(z.string()).default(["chore: release "]),
|
|
396
|
+
minChanges: z.number().int().positive().default(1),
|
|
397
|
+
labels: CILabelsConfigSchema.default({
|
|
398
|
+
stable: "release:stable",
|
|
399
|
+
prerelease: "release:prerelease",
|
|
400
|
+
skip: "release:skip",
|
|
401
|
+
major: "release:major",
|
|
402
|
+
minor: "release:minor",
|
|
403
|
+
patch: "release:patch"
|
|
404
|
+
})
|
|
405
|
+
});
|
|
406
|
+
var ReleaseCIConfigSchema = z.object({
|
|
407
|
+
skipPatterns: z.array(z.string().min(1)).optional(),
|
|
408
|
+
minChanges: z.number().int().positive().optional(),
|
|
409
|
+
/** Set to `false` to disable GitHub release creation in CI. */
|
|
410
|
+
githubRelease: z.literal(false).optional(),
|
|
411
|
+
/** Set to `false` to disable changelog generation in CI. */
|
|
412
|
+
notes: z.literal(false).optional()
|
|
413
|
+
});
|
|
414
|
+
var ReleaseConfigSchema = z.object({
|
|
415
|
+
/**
|
|
416
|
+
* Optional steps to enable. The version step always runs; only 'notes' and
|
|
417
|
+
* 'publish' can be opted out. Omitting a step is equivalent to --skip-<step>.
|
|
418
|
+
*/
|
|
419
|
+
steps: z.array(z.enum(["notes", "publish"])).min(1).optional(),
|
|
420
|
+
ci: ReleaseCIConfigSchema.optional()
|
|
421
|
+
});
|
|
422
|
+
var ReleaseKitConfigSchema = z.object({
|
|
423
|
+
git: GitConfigSchema.optional(),
|
|
424
|
+
monorepo: MonorepoConfigSchema.optional(),
|
|
425
|
+
version: VersionConfigSchema.optional(),
|
|
426
|
+
publish: PublishConfigSchema.optional(),
|
|
427
|
+
notes: NotesConfigSchema.optional(),
|
|
428
|
+
ci: CIConfigSchema.optional(),
|
|
429
|
+
release: ReleaseConfigSchema.optional()
|
|
430
|
+
});
|
|
431
|
+
var MAX_INPUT_LENGTH = 1e4;
|
|
432
|
+
function substituteVariables(value) {
|
|
433
|
+
if (value.length > MAX_INPUT_LENGTH) {
|
|
434
|
+
throw new Error(`Input too long: ${value.length} characters (max ${MAX_INPUT_LENGTH})`);
|
|
435
|
+
}
|
|
436
|
+
const envPattern = /\{env:([^}]{1,1000})\}/g;
|
|
437
|
+
const filePattern = /\{file:([^}]{1,1000})\}/g;
|
|
438
|
+
let result = value;
|
|
439
|
+
result = result.replace(envPattern, (_, varName) => {
|
|
440
|
+
return process.env[varName] ?? "";
|
|
441
|
+
});
|
|
442
|
+
result = result.replace(filePattern, (_, filePath) => {
|
|
443
|
+
const expandedPath = filePath.startsWith("~") ? path22.join(os.homedir(), filePath.slice(1)) : filePath;
|
|
444
|
+
try {
|
|
445
|
+
return fs22.readFileSync(expandedPath, "utf-8").trim();
|
|
446
|
+
} catch {
|
|
447
|
+
return "";
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
var SOLE_REFERENCE_PATTERN = /^\{(?:env|file):[^}]+\}$/;
|
|
453
|
+
function substituteInObject(obj) {
|
|
454
|
+
if (typeof obj === "string") {
|
|
455
|
+
const result = substituteVariables(obj);
|
|
456
|
+
if (result === "" && SOLE_REFERENCE_PATTERN.test(obj)) {
|
|
457
|
+
return void 0;
|
|
458
|
+
}
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
if (Array.isArray(obj)) {
|
|
462
|
+
return obj.map((item) => substituteInObject(item));
|
|
463
|
+
}
|
|
464
|
+
if (obj && typeof obj === "object") {
|
|
465
|
+
const result = {};
|
|
466
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
467
|
+
result[key] = substituteInObject(value);
|
|
468
|
+
}
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
return obj;
|
|
472
|
+
}
|
|
473
|
+
var AUTH_DIR = path22.join(os.homedir(), ".config", "releasekit");
|
|
474
|
+
var AUTH_FILE = path22.join(AUTH_DIR, "auth.json");
|
|
475
|
+
var CONFIG_FILE = "releasekit.config.json";
|
|
476
|
+
function loadConfigFile(configPath) {
|
|
477
|
+
if (!fs3.existsSync(configPath)) {
|
|
478
|
+
return {};
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const content = fs3.readFileSync(configPath, "utf-8");
|
|
482
|
+
const parsed = parseJsonc(content);
|
|
483
|
+
const substituted = substituteInObject(parsed);
|
|
484
|
+
return ReleaseKitConfigSchema.parse(substituted);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
if (error instanceof z2.ZodError) {
|
|
487
|
+
const issues = error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
488
|
+
throw new ConfigError(`Config validation errors:
|
|
489
|
+
${issues}`);
|
|
490
|
+
}
|
|
491
|
+
if (error instanceof SyntaxError) {
|
|
492
|
+
throw new ConfigError(`Invalid JSON in config file: ${error.message}`);
|
|
493
|
+
}
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function loadConfig(options) {
|
|
498
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
499
|
+
const configPath = options?.configPath ?? path3.join(cwd, CONFIG_FILE);
|
|
500
|
+
return loadConfigFile(configPath);
|
|
501
|
+
}
|
|
502
|
+
function loadPublishConfig(options) {
|
|
503
|
+
const config = loadConfig(options);
|
|
504
|
+
if (!config.publish) return void 0;
|
|
505
|
+
const mergedGit = mergeGitConfig(config.git, config.publish.git);
|
|
506
|
+
return {
|
|
507
|
+
...config.publish,
|
|
508
|
+
git: mergedGit ? {
|
|
509
|
+
push: mergedGit.push ?? true,
|
|
510
|
+
pushMethod: mergedGit.pushMethod,
|
|
511
|
+
remote: mergedGit.remote,
|
|
512
|
+
branch: mergedGit.branch,
|
|
513
|
+
httpsTokenEnv: mergedGit.httpsTokenEnv,
|
|
514
|
+
skipHooks: mergedGit.skipHooks
|
|
515
|
+
} : void 0
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/types.ts
|
|
520
|
+
function getDefaultConfig() {
|
|
521
|
+
return {
|
|
522
|
+
npm: {
|
|
523
|
+
enabled: true,
|
|
524
|
+
auth: "auto",
|
|
525
|
+
provenance: true,
|
|
526
|
+
access: "public",
|
|
527
|
+
registry: "https://registry.npmjs.org",
|
|
528
|
+
copyFiles: ["LICENSE"],
|
|
529
|
+
tag: "latest"
|
|
530
|
+
},
|
|
531
|
+
cargo: {
|
|
532
|
+
enabled: false,
|
|
533
|
+
noVerify: false,
|
|
534
|
+
publishOrder: [],
|
|
535
|
+
clean: false
|
|
536
|
+
},
|
|
537
|
+
git: {
|
|
538
|
+
push: true,
|
|
539
|
+
pushMethod: "auto",
|
|
540
|
+
remote: "origin",
|
|
541
|
+
branch: void 0,
|
|
542
|
+
httpsTokenEnv: void 0,
|
|
543
|
+
skipHooks: false
|
|
544
|
+
},
|
|
545
|
+
githubRelease: {
|
|
546
|
+
enabled: true,
|
|
547
|
+
draft: true,
|
|
548
|
+
perPackage: true,
|
|
549
|
+
prerelease: "auto",
|
|
550
|
+
body: "auto"
|
|
551
|
+
},
|
|
552
|
+
verify: {
|
|
553
|
+
npm: {
|
|
554
|
+
enabled: true,
|
|
555
|
+
maxAttempts: 5,
|
|
556
|
+
initialDelay: 15e3,
|
|
557
|
+
backoffMultiplier: 2
|
|
558
|
+
},
|
|
559
|
+
cargo: {
|
|
560
|
+
enabled: true,
|
|
561
|
+
maxAttempts: 10,
|
|
562
|
+
initialDelay: 3e4,
|
|
563
|
+
backoffMultiplier: 2
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function toPublishConfig(config) {
|
|
569
|
+
const defaults = getDefaultConfig();
|
|
570
|
+
if (!config) return defaults;
|
|
571
|
+
return {
|
|
572
|
+
npm: {
|
|
573
|
+
enabled: config.npm?.enabled ?? defaults.npm.enabled,
|
|
574
|
+
auth: config.npm?.auth ?? defaults.npm.auth,
|
|
575
|
+
provenance: config.npm?.provenance ?? defaults.npm.provenance,
|
|
576
|
+
access: config.npm?.access ?? defaults.npm.access,
|
|
577
|
+
registry: config.npm?.registry ?? defaults.npm.registry,
|
|
578
|
+
copyFiles: config.npm?.copyFiles ?? defaults.npm.copyFiles,
|
|
579
|
+
tag: config.npm?.tag ?? defaults.npm.tag
|
|
580
|
+
},
|
|
581
|
+
cargo: {
|
|
582
|
+
enabled: config.cargo?.enabled ?? defaults.cargo.enabled,
|
|
583
|
+
noVerify: config.cargo?.noVerify ?? defaults.cargo.noVerify,
|
|
584
|
+
publishOrder: config.cargo?.publishOrder ?? defaults.cargo.publishOrder,
|
|
585
|
+
clean: config.cargo?.clean ?? defaults.cargo.clean
|
|
586
|
+
},
|
|
587
|
+
git: config.git ? {
|
|
588
|
+
push: config.git.push ?? defaults.git.push,
|
|
589
|
+
pushMethod: config.git.pushMethod ?? defaults.git.pushMethod,
|
|
590
|
+
remote: config.git.remote ?? defaults.git.remote,
|
|
591
|
+
branch: config.git.branch ?? defaults.git.branch,
|
|
592
|
+
httpsTokenEnv: config.git.httpsTokenEnv ?? defaults.git.httpsTokenEnv,
|
|
593
|
+
skipHooks: config.git.skipHooks ?? defaults.git.skipHooks
|
|
594
|
+
} : defaults.git,
|
|
595
|
+
githubRelease: {
|
|
596
|
+
enabled: config.githubRelease?.enabled ?? defaults.githubRelease.enabled,
|
|
597
|
+
draft: config.githubRelease?.draft ?? defaults.githubRelease.draft,
|
|
598
|
+
perPackage: config.githubRelease?.perPackage ?? defaults.githubRelease.perPackage,
|
|
599
|
+
prerelease: config.githubRelease?.prerelease ?? defaults.githubRelease.prerelease,
|
|
600
|
+
body: config.githubRelease?.body ?? defaults.githubRelease.body
|
|
601
|
+
},
|
|
602
|
+
verify: {
|
|
603
|
+
npm: {
|
|
604
|
+
enabled: config.verify?.npm?.enabled ?? defaults.verify.npm.enabled,
|
|
605
|
+
maxAttempts: config.verify?.npm?.maxAttempts ?? defaults.verify.npm.maxAttempts,
|
|
606
|
+
initialDelay: config.verify?.npm?.initialDelay ?? defaults.verify.npm.initialDelay,
|
|
607
|
+
backoffMultiplier: config.verify?.npm?.backoffMultiplier ?? defaults.verify.npm.backoffMultiplier
|
|
608
|
+
},
|
|
609
|
+
cargo: {
|
|
610
|
+
enabled: config.verify?.cargo?.enabled ?? defaults.verify.cargo.enabled,
|
|
611
|
+
maxAttempts: config.verify?.cargo?.maxAttempts ?? defaults.verify.cargo.maxAttempts,
|
|
612
|
+
initialDelay: config.verify?.cargo?.initialDelay ?? defaults.verify.cargo.initialDelay,
|
|
613
|
+
backoffMultiplier: config.verify?.cargo?.backoffMultiplier ?? defaults.verify.cargo.backoffMultiplier
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/config.ts
|
|
620
|
+
function loadConfig2(options) {
|
|
621
|
+
const baseConfig = loadPublishConfig(options);
|
|
622
|
+
return toPublishConfig(baseConfig);
|
|
623
|
+
}
|
|
624
|
+
function getDefaultConfig2() {
|
|
625
|
+
return toPublishConfig(void 0);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/errors/index.ts
|
|
629
|
+
var BasePublishError = class _BasePublishError extends ReleaseKitError {
|
|
630
|
+
code;
|
|
631
|
+
suggestions;
|
|
632
|
+
constructor(message, code, suggestions) {
|
|
633
|
+
super(message);
|
|
634
|
+
this.code = code;
|
|
635
|
+
this.suggestions = suggestions ?? [];
|
|
636
|
+
}
|
|
637
|
+
static isPublishError(error) {
|
|
638
|
+
return error instanceof _BasePublishError;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
var PublishError = class extends BasePublishError {
|
|
642
|
+
};
|
|
643
|
+
var PipelineError = class extends BasePublishError {
|
|
644
|
+
partialOutput;
|
|
645
|
+
failedStage;
|
|
646
|
+
cause;
|
|
647
|
+
constructor(message, failedStage, partialOutput, cause) {
|
|
648
|
+
super(message, "PIPELINE_STAGE_ERROR" /* PIPELINE_STAGE_ERROR */, [
|
|
649
|
+
"Check the partial output for results from stages that completed before the failure",
|
|
650
|
+
"Use --json to get structured error output with partial results"
|
|
651
|
+
]);
|
|
652
|
+
this.failedStage = failedStage;
|
|
653
|
+
this.partialOutput = partialOutput;
|
|
654
|
+
this.cause = cause;
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
var PublishErrorCode = /* @__PURE__ */ ((PublishErrorCode2) => {
|
|
658
|
+
PublishErrorCode2["INPUT_PARSE_ERROR"] = "INPUT_PARSE_ERROR";
|
|
659
|
+
PublishErrorCode2["INPUT_VALIDATION_ERROR"] = "INPUT_VALIDATION_ERROR";
|
|
660
|
+
PublishErrorCode2["CONFIG_ERROR"] = "CONFIG_ERROR";
|
|
661
|
+
PublishErrorCode2["GIT_COMMIT_ERROR"] = "GIT_COMMIT_ERROR";
|
|
662
|
+
PublishErrorCode2["GIT_TAG_ERROR"] = "GIT_TAG_ERROR";
|
|
663
|
+
PublishErrorCode2["GIT_PUSH_ERROR"] = "GIT_PUSH_ERROR";
|
|
664
|
+
PublishErrorCode2["NPM_PUBLISH_ERROR"] = "NPM_PUBLISH_ERROR";
|
|
665
|
+
PublishErrorCode2["NPM_AUTH_ERROR"] = "NPM_AUTH_ERROR";
|
|
666
|
+
PublishErrorCode2["CARGO_PUBLISH_ERROR"] = "CARGO_PUBLISH_ERROR";
|
|
667
|
+
PublishErrorCode2["CARGO_AUTH_ERROR"] = "CARGO_AUTH_ERROR";
|
|
668
|
+
PublishErrorCode2["VERIFICATION_FAILED"] = "VERIFICATION_FAILED";
|
|
669
|
+
PublishErrorCode2["GITHUB_RELEASE_ERROR"] = "GITHUB_RELEASE_ERROR";
|
|
670
|
+
PublishErrorCode2["FILE_COPY_ERROR"] = "FILE_COPY_ERROR";
|
|
671
|
+
PublishErrorCode2["CARGO_TOML_ERROR"] = "CARGO_TOML_ERROR";
|
|
672
|
+
PublishErrorCode2["PIPELINE_STAGE_ERROR"] = "PIPELINE_STAGE_ERROR";
|
|
673
|
+
return PublishErrorCode2;
|
|
674
|
+
})(PublishErrorCode || {});
|
|
675
|
+
function createPublishError(code, details) {
|
|
676
|
+
const messages = {
|
|
677
|
+
["INPUT_PARSE_ERROR" /* INPUT_PARSE_ERROR */]: "Failed to parse version output",
|
|
678
|
+
["INPUT_VALIDATION_ERROR" /* INPUT_VALIDATION_ERROR */]: "Version output validation failed",
|
|
679
|
+
["CONFIG_ERROR" /* CONFIG_ERROR */]: "Invalid publish configuration",
|
|
680
|
+
["GIT_COMMIT_ERROR" /* GIT_COMMIT_ERROR */]: "Failed to create git commit",
|
|
681
|
+
["GIT_TAG_ERROR" /* GIT_TAG_ERROR */]: "Failed to create git tag",
|
|
682
|
+
["GIT_PUSH_ERROR" /* GIT_PUSH_ERROR */]: "Failed to push to remote",
|
|
683
|
+
["NPM_PUBLISH_ERROR" /* NPM_PUBLISH_ERROR */]: "Failed to publish to npm",
|
|
684
|
+
["NPM_AUTH_ERROR" /* NPM_AUTH_ERROR */]: "NPM authentication failed",
|
|
685
|
+
["CARGO_PUBLISH_ERROR" /* CARGO_PUBLISH_ERROR */]: "Failed to publish to crates.io",
|
|
686
|
+
["CARGO_AUTH_ERROR" /* CARGO_AUTH_ERROR */]: "Cargo authentication failed",
|
|
687
|
+
["VERIFICATION_FAILED" /* VERIFICATION_FAILED */]: "Package verification failed",
|
|
688
|
+
["GITHUB_RELEASE_ERROR" /* GITHUB_RELEASE_ERROR */]: "Failed to create GitHub release",
|
|
689
|
+
["FILE_COPY_ERROR" /* FILE_COPY_ERROR */]: "Failed to copy files",
|
|
690
|
+
["CARGO_TOML_ERROR" /* CARGO_TOML_ERROR */]: "Failed to update Cargo.toml",
|
|
691
|
+
["PIPELINE_STAGE_ERROR" /* PIPELINE_STAGE_ERROR */]: "Pipeline stage failed"
|
|
692
|
+
};
|
|
693
|
+
const suggestions = {
|
|
694
|
+
["INPUT_PARSE_ERROR" /* INPUT_PARSE_ERROR */]: [
|
|
695
|
+
"Ensure the input is valid JSON from @releasekit/version --json",
|
|
696
|
+
"Check that stdin is piped correctly or --input path is valid"
|
|
697
|
+
],
|
|
698
|
+
["INPUT_VALIDATION_ERROR" /* INPUT_VALIDATION_ERROR */]: [
|
|
699
|
+
"Ensure the input matches the expected VersionOutput schema",
|
|
700
|
+
"Run @releasekit/version with --json to generate valid output"
|
|
701
|
+
],
|
|
702
|
+
["CONFIG_ERROR" /* CONFIG_ERROR */]: [
|
|
703
|
+
"Validate publish.config.json syntax",
|
|
704
|
+
"Check configuration against the schema",
|
|
705
|
+
"Review documentation for valid configuration options"
|
|
706
|
+
],
|
|
707
|
+
["GIT_COMMIT_ERROR" /* GIT_COMMIT_ERROR */]: [
|
|
708
|
+
"Ensure there are staged changes to commit",
|
|
709
|
+
"Check git user.name and user.email are configured",
|
|
710
|
+
"Verify you have write access to the repository"
|
|
711
|
+
],
|
|
712
|
+
["GIT_TAG_ERROR" /* GIT_TAG_ERROR */]: [
|
|
713
|
+
"Check if the tag already exists: git tag -l <tag>",
|
|
714
|
+
"Delete existing tag if needed: git tag -d <tag>"
|
|
715
|
+
],
|
|
716
|
+
["GIT_PUSH_ERROR" /* GIT_PUSH_ERROR */]: [
|
|
717
|
+
"Verify remote repository access",
|
|
718
|
+
"Check SSH key or deploy key configuration",
|
|
719
|
+
"Ensure the branch is not protected or you have push access"
|
|
720
|
+
],
|
|
721
|
+
["NPM_PUBLISH_ERROR" /* NPM_PUBLISH_ERROR */]: [
|
|
722
|
+
"Check npm registry availability",
|
|
723
|
+
"Verify package name is not already taken by another owner",
|
|
724
|
+
"Ensure package version has not already been published"
|
|
725
|
+
],
|
|
726
|
+
["NPM_AUTH_ERROR" /* NPM_AUTH_ERROR */]: [
|
|
727
|
+
"Set NPM_TOKEN environment variable for token-based auth",
|
|
728
|
+
"Enable OIDC trusted publishing in GitHub Actions for provenance",
|
|
729
|
+
"Run npm login for local publishing"
|
|
730
|
+
],
|
|
731
|
+
["CARGO_PUBLISH_ERROR" /* CARGO_PUBLISH_ERROR */]: [
|
|
732
|
+
"Check crates.io registry availability",
|
|
733
|
+
"Verify crate name ownership on crates.io",
|
|
734
|
+
"Ensure Cargo.toml metadata is complete (description, license, etc.)"
|
|
735
|
+
],
|
|
736
|
+
["CARGO_AUTH_ERROR" /* CARGO_AUTH_ERROR */]: [
|
|
737
|
+
"Set CARGO_REGISTRY_TOKEN environment variable",
|
|
738
|
+
"Generate a token at https://crates.io/settings/tokens"
|
|
739
|
+
],
|
|
740
|
+
["VERIFICATION_FAILED" /* VERIFICATION_FAILED */]: [
|
|
741
|
+
"Registry propagation may take longer than expected",
|
|
742
|
+
"Try increasing verify.maxAttempts or verify.initialDelay in config",
|
|
743
|
+
"Check registry status pages for outages"
|
|
744
|
+
],
|
|
745
|
+
["GITHUB_RELEASE_ERROR" /* GITHUB_RELEASE_ERROR */]: [
|
|
746
|
+
"Ensure gh CLI is installed and authenticated",
|
|
747
|
+
"Verify GITHUB_TOKEN has contents:write permission",
|
|
748
|
+
"Check that the tag exists in the remote repository"
|
|
749
|
+
],
|
|
750
|
+
["FILE_COPY_ERROR" /* FILE_COPY_ERROR */]: ["Verify the source file exists in the project root", "Check file permissions"],
|
|
751
|
+
["CARGO_TOML_ERROR" /* CARGO_TOML_ERROR */]: [
|
|
752
|
+
"Ensure Cargo.toml exists and is valid TOML",
|
|
753
|
+
"Check that the [package] section has a version field"
|
|
754
|
+
],
|
|
755
|
+
["PIPELINE_STAGE_ERROR" /* PIPELINE_STAGE_ERROR */]: [
|
|
756
|
+
"Check the partial output for results from stages that completed before the failure",
|
|
757
|
+
"Use --json to get structured error output with partial results"
|
|
758
|
+
]
|
|
759
|
+
};
|
|
760
|
+
const baseMessage = messages[code];
|
|
761
|
+
const fullMessage = details ? `${baseMessage}: ${details}` : baseMessage;
|
|
762
|
+
return new PublishError(fullMessage, code, suggestions[code]);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/utils/exec.ts
|
|
766
|
+
import { execFile } from "child_process";
|
|
767
|
+
function redactArg(arg) {
|
|
768
|
+
try {
|
|
769
|
+
const url = new URL(arg);
|
|
770
|
+
if (url.username || url.password) {
|
|
771
|
+
url.username = url.username ? "***" : "";
|
|
772
|
+
url.password = url.password ? "***" : "";
|
|
773
|
+
return url.toString();
|
|
774
|
+
}
|
|
775
|
+
} catch {
|
|
776
|
+
}
|
|
777
|
+
return arg;
|
|
778
|
+
}
|
|
779
|
+
async function execCommand(file, args, options = {}) {
|
|
780
|
+
const displayCommand = options.label ?? [file, ...args.map(redactArg)].join(" ");
|
|
781
|
+
if (options.dryRun) {
|
|
782
|
+
info(`[DRY RUN] Would execute: ${displayCommand}`);
|
|
783
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
784
|
+
}
|
|
785
|
+
debug(`Executing: ${displayCommand}`);
|
|
786
|
+
return new Promise((resolve6, reject) => {
|
|
787
|
+
execFile(
|
|
788
|
+
file,
|
|
789
|
+
args,
|
|
790
|
+
{
|
|
791
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
792
|
+
cwd: options.cwd,
|
|
793
|
+
env: options.env ? { ...process.env, ...options.env } : void 0
|
|
794
|
+
},
|
|
795
|
+
(error, stdout, stderr) => {
|
|
796
|
+
if (error) {
|
|
797
|
+
reject(
|
|
798
|
+
Object.assign(new Error(error.message), {
|
|
799
|
+
stdout: stdout.toString(),
|
|
800
|
+
stderr: stderr.toString(),
|
|
801
|
+
exitCode: error.code ?? 1
|
|
802
|
+
})
|
|
803
|
+
);
|
|
804
|
+
} else {
|
|
805
|
+
resolve6({
|
|
806
|
+
stdout: stdout.toString(),
|
|
807
|
+
stderr: stderr.toString(),
|
|
808
|
+
exitCode: 0
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
async function execCommandSafe(file, args, options = {}) {
|
|
816
|
+
try {
|
|
817
|
+
return await execCommand(file, args, options);
|
|
818
|
+
} catch (error) {
|
|
819
|
+
if (error && typeof error === "object" && "stdout" in error) {
|
|
820
|
+
const execError = error;
|
|
821
|
+
return {
|
|
822
|
+
stdout: execError.stdout ?? "",
|
|
823
|
+
stderr: execError.stderr ?? "",
|
|
824
|
+
exitCode: execError.exitCode ?? 1
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
return { stdout: "", stderr: error instanceof Error ? error.message : String(error), exitCode: 1 };
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/utils/auth.ts
|
|
832
|
+
function detectNpmAuth() {
|
|
833
|
+
if (process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
|
|
834
|
+
return "oidc";
|
|
835
|
+
}
|
|
836
|
+
if (process.env.NPM_TOKEN || process.env.NODE_AUTH_TOKEN) {
|
|
837
|
+
return "token";
|
|
838
|
+
}
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
function hasCargoAuth() {
|
|
842
|
+
return !!process.env.CARGO_REGISTRY_TOKEN;
|
|
843
|
+
}
|
|
844
|
+
async function detectGitPushMethod(remote, cwd) {
|
|
845
|
+
const result = await execCommand("git", ["remote", "get-url", remote], { cwd });
|
|
846
|
+
const url = result.stdout.trim();
|
|
847
|
+
if (url.startsWith("git@") || url.startsWith("ssh://")) {
|
|
848
|
+
return "ssh";
|
|
849
|
+
}
|
|
850
|
+
return "https";
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/utils/cargo.ts
|
|
854
|
+
import * as fs4 from "fs";
|
|
855
|
+
import * as TOML2 from "smol-toml";
|
|
856
|
+
function updateCargoVersion(cargoPath, newVersion) {
|
|
857
|
+
try {
|
|
858
|
+
const cargo = parseCargoToml(cargoPath);
|
|
859
|
+
if (cargo.package) {
|
|
860
|
+
cargo.package.version = newVersion;
|
|
861
|
+
fs4.writeFileSync(cargoPath, TOML2.stringify(cargo));
|
|
862
|
+
}
|
|
863
|
+
} catch (error) {
|
|
864
|
+
throw createPublishError(
|
|
865
|
+
"CARGO_TOML_ERROR" /* CARGO_TOML_ERROR */,
|
|
866
|
+
`${cargoPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
function extractPathDeps(manifest) {
|
|
871
|
+
const pathDeps = [];
|
|
872
|
+
const deps = manifest.dependencies;
|
|
873
|
+
if (deps) {
|
|
874
|
+
for (const dep of Object.values(deps)) {
|
|
875
|
+
if (dep && typeof dep === "object" && "path" in dep) {
|
|
876
|
+
pathDeps.push(dep.path);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return pathDeps;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/utils/semver.ts
|
|
884
|
+
import semver from "semver";
|
|
885
|
+
function isPrerelease(version) {
|
|
886
|
+
return semver.prerelease(version) !== null;
|
|
887
|
+
}
|
|
888
|
+
function getDistTag(version, defaultTag = "latest") {
|
|
889
|
+
const pre = semver.prerelease(version);
|
|
890
|
+
if (pre && pre.length > 0) {
|
|
891
|
+
const identifier = pre[0];
|
|
892
|
+
return typeof identifier === "string" ? identifier : "next";
|
|
893
|
+
}
|
|
894
|
+
return defaultTag;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/utils/package-manager.ts
|
|
898
|
+
import * as fs5 from "fs";
|
|
899
|
+
import * as path4 from "path";
|
|
900
|
+
function detectPackageManager(cwd) {
|
|
901
|
+
if (fs5.existsSync(path4.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
902
|
+
if (fs5.existsSync(path4.join(cwd, "yarn.lock"))) return "yarn";
|
|
903
|
+
return "npm";
|
|
904
|
+
}
|
|
905
|
+
function buildPublishCommand(pm, packageName, _packageDir, options) {
|
|
906
|
+
const args = ["publish"];
|
|
907
|
+
let file;
|
|
908
|
+
if (pm === "pnpm") {
|
|
909
|
+
file = "pnpm";
|
|
910
|
+
args.push("--filter", packageName, "--access", options.access, "--tag", options.tag);
|
|
911
|
+
if (options.noGitChecks) args.push("--no-git-checks");
|
|
912
|
+
} else {
|
|
913
|
+
file = "npm";
|
|
914
|
+
args.push("--access", options.access, "--tag", options.tag);
|
|
915
|
+
}
|
|
916
|
+
if (options.provenance) {
|
|
917
|
+
args.push("--provenance");
|
|
918
|
+
}
|
|
919
|
+
return { file, args };
|
|
920
|
+
}
|
|
921
|
+
function buildViewCommand(pm, packageName, version) {
|
|
922
|
+
const file = pm === "pnpm" ? "pnpm" : "npm";
|
|
923
|
+
return { file, args: ["view", `${packageName}@${version}`, "version", "--json"] };
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/stages/cargo-publish.ts
|
|
927
|
+
import * as fs6 from "fs";
|
|
928
|
+
import * as path5 from "path";
|
|
929
|
+
async function runCargoPublishStage(ctx) {
|
|
930
|
+
const { input, config, cliOptions, cwd } = ctx;
|
|
931
|
+
const dryRun = cliOptions.dryRun;
|
|
932
|
+
if (!config.cargo.enabled) {
|
|
933
|
+
debug("Cargo publishing disabled in config");
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (!hasCargoAuth() && !dryRun) {
|
|
937
|
+
throw createPublishError("CARGO_AUTH_ERROR" /* CARGO_AUTH_ERROR */, "CARGO_REGISTRY_TOKEN not set");
|
|
938
|
+
}
|
|
939
|
+
const crates = findCrates(
|
|
940
|
+
input.updates.map((u) => ({ dir: path5.dirname(path5.resolve(cwd, u.filePath)), ...u })),
|
|
941
|
+
cwd
|
|
942
|
+
);
|
|
943
|
+
if (crates.length === 0) {
|
|
944
|
+
debug("No Cargo crates found to publish");
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
const ordered = orderCrates(crates, config.cargo.publishOrder);
|
|
948
|
+
for (const crate of ordered) {
|
|
949
|
+
const result = {
|
|
950
|
+
packageName: crate.name,
|
|
951
|
+
version: crate.version,
|
|
952
|
+
registry: "cargo",
|
|
953
|
+
success: false,
|
|
954
|
+
skipped: false
|
|
955
|
+
};
|
|
956
|
+
const searchResult = await execCommandSafe("cargo", ["search", crate.name, "--limit", "1"], { cwd, dryRun: false });
|
|
957
|
+
if (searchResult.exitCode === 0 && searchResult.stdout.includes(`"${crate.version}"`)) {
|
|
958
|
+
result.alreadyPublished = true;
|
|
959
|
+
result.skipped = true;
|
|
960
|
+
result.success = true;
|
|
961
|
+
result.reason = "Already published on crates.io";
|
|
962
|
+
ctx.output.cargo.push(result);
|
|
963
|
+
warn(`${crate.name}@${crate.version} is already published on crates.io, skipping`);
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
if (config.cargo.clean) {
|
|
967
|
+
await execCommand("cargo", ["clean"], { cwd: crate.dir, dryRun, label: `cargo clean (${crate.name})` });
|
|
968
|
+
}
|
|
969
|
+
const publishArgs = ["publish", "--manifest-path", crate.manifestPath];
|
|
970
|
+
if (config.cargo.noVerify) {
|
|
971
|
+
publishArgs.push("--no-verify");
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
await execCommand("cargo", publishArgs, {
|
|
975
|
+
cwd,
|
|
976
|
+
dryRun,
|
|
977
|
+
label: `cargo publish ${crate.name}@${crate.version}`
|
|
978
|
+
});
|
|
979
|
+
result.success = true;
|
|
980
|
+
if (!dryRun) {
|
|
981
|
+
success(`Published ${crate.name}@${crate.version} to crates.io`);
|
|
982
|
+
}
|
|
983
|
+
ctx.output.cargo.push(result);
|
|
984
|
+
} catch (error) {
|
|
985
|
+
result.reason = error instanceof Error ? error.message : String(error);
|
|
986
|
+
ctx.output.cargo.push(result);
|
|
987
|
+
throw createPublishError(
|
|
988
|
+
"CARGO_PUBLISH_ERROR" /* CARGO_PUBLISH_ERROR */,
|
|
989
|
+
`${crate.name}@${crate.version}: ${result.reason}`
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function findCrates(updates, _cwd) {
|
|
995
|
+
const crates = [];
|
|
996
|
+
for (const update of updates) {
|
|
997
|
+
const cargoPath = path5.join(update.dir, "Cargo.toml");
|
|
998
|
+
if (!fs6.existsSync(cargoPath)) {
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
try {
|
|
1002
|
+
const cargo = parseCargoToml(cargoPath);
|
|
1003
|
+
if (!cargo.package?.name) {
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
const pathDeps = extractPathDeps(cargo);
|
|
1007
|
+
crates.push({
|
|
1008
|
+
name: cargo.package.name,
|
|
1009
|
+
version: update.newVersion,
|
|
1010
|
+
dir: update.dir,
|
|
1011
|
+
manifestPath: cargoPath,
|
|
1012
|
+
pathDeps
|
|
1013
|
+
});
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return crates;
|
|
1018
|
+
}
|
|
1019
|
+
function orderCrates(crates, explicitOrder) {
|
|
1020
|
+
if (explicitOrder.length > 0) {
|
|
1021
|
+
const ordered = [];
|
|
1022
|
+
const byName = new Map(crates.map((c) => [c.name, c]));
|
|
1023
|
+
for (const name of explicitOrder) {
|
|
1024
|
+
const crate = byName.get(name);
|
|
1025
|
+
if (crate) {
|
|
1026
|
+
ordered.push(crate);
|
|
1027
|
+
byName.delete(name);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
for (const crate of byName.values()) {
|
|
1031
|
+
ordered.push(crate);
|
|
1032
|
+
}
|
|
1033
|
+
return ordered;
|
|
1034
|
+
}
|
|
1035
|
+
return topologicalSort(crates);
|
|
1036
|
+
}
|
|
1037
|
+
function topologicalSort(crates) {
|
|
1038
|
+
const nameSet = new Set(crates.map((c) => c.name));
|
|
1039
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1040
|
+
const crateMap = new Map(crates.map((c) => [c.name, c]));
|
|
1041
|
+
for (const crate of crates) {
|
|
1042
|
+
graph.set(crate.name, []);
|
|
1043
|
+
}
|
|
1044
|
+
for (const crate of crates) {
|
|
1045
|
+
for (const depPath of crate.pathDeps) {
|
|
1046
|
+
const resolvedDir = path5.resolve(crate.dir, depPath);
|
|
1047
|
+
for (const other of crates) {
|
|
1048
|
+
if (path5.resolve(other.dir) === resolvedDir && nameSet.has(other.name)) {
|
|
1049
|
+
graph.get(crate.name)?.push(other.name);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
1055
|
+
for (const name of nameSet) {
|
|
1056
|
+
inDegree.set(name, 0);
|
|
1057
|
+
}
|
|
1058
|
+
for (const deps of graph.values()) {
|
|
1059
|
+
for (const dep of deps) {
|
|
1060
|
+
inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
const queue = [];
|
|
1064
|
+
for (const [name, degree] of inDegree) {
|
|
1065
|
+
if (degree === 0) {
|
|
1066
|
+
queue.push(name);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
const result = [];
|
|
1070
|
+
while (queue.length > 0) {
|
|
1071
|
+
const name = queue.shift();
|
|
1072
|
+
if (!name) break;
|
|
1073
|
+
const crate = crateMap.get(name);
|
|
1074
|
+
if (crate) {
|
|
1075
|
+
result.push(crate);
|
|
1076
|
+
}
|
|
1077
|
+
for (const dep of graph.get(name) ?? []) {
|
|
1078
|
+
const newDegree = (inDegree.get(dep) ?? 1) - 1;
|
|
1079
|
+
inDegree.set(dep, newDegree);
|
|
1080
|
+
if (newDegree === 0) {
|
|
1081
|
+
queue.push(dep);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
result.reverse();
|
|
1086
|
+
return result;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// src/stages/git-commit.ts
|
|
1090
|
+
import * as path6 from "path";
|
|
1091
|
+
async function runGitCommitStage(ctx) {
|
|
1092
|
+
const { input, config, cliOptions, cwd } = ctx;
|
|
1093
|
+
const dryRun = cliOptions.dryRun;
|
|
1094
|
+
const skipHooks = config.git.skipHooks ?? false;
|
|
1095
|
+
if (!input.commitMessage) {
|
|
1096
|
+
info("No commit message provided, skipping git commit");
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const filePaths = input.updates.map((u) => path6.resolve(cwd, u.filePath));
|
|
1100
|
+
if (ctx.additionalFiles) {
|
|
1101
|
+
filePaths.push(...ctx.additionalFiles.map((f) => path6.resolve(cwd, f)));
|
|
1102
|
+
}
|
|
1103
|
+
if (filePaths.length === 0) {
|
|
1104
|
+
info("No files to commit");
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
try {
|
|
1108
|
+
await execCommand("git", ["add", ...filePaths], {
|
|
1109
|
+
cwd,
|
|
1110
|
+
dryRun,
|
|
1111
|
+
label: `git add ${filePaths.length} file(s)`
|
|
1112
|
+
});
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
throw createPublishError(
|
|
1115
|
+
"GIT_COMMIT_ERROR" /* GIT_COMMIT_ERROR */,
|
|
1116
|
+
`git add failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
const commitArgs = ["commit"];
|
|
1120
|
+
if (skipHooks) {
|
|
1121
|
+
commitArgs.push("--no-verify");
|
|
1122
|
+
}
|
|
1123
|
+
commitArgs.push("-m", input.commitMessage);
|
|
1124
|
+
try {
|
|
1125
|
+
await execCommand("git", commitArgs, {
|
|
1126
|
+
cwd,
|
|
1127
|
+
dryRun,
|
|
1128
|
+
label: `git commit -m "${input.commitMessage}"`
|
|
1129
|
+
});
|
|
1130
|
+
ctx.output.git.committed = true;
|
|
1131
|
+
if (!dryRun) {
|
|
1132
|
+
success("Created git commit");
|
|
1133
|
+
}
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
throw createPublishError(
|
|
1136
|
+
"GIT_COMMIT_ERROR" /* GIT_COMMIT_ERROR */,
|
|
1137
|
+
`git commit failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
for (const tag of input.tags) {
|
|
1141
|
+
try {
|
|
1142
|
+
const tagMessage = `Release ${tag}`;
|
|
1143
|
+
await execCommand("git", ["tag", "-a", tag, "-m", tagMessage], {
|
|
1144
|
+
cwd,
|
|
1145
|
+
dryRun,
|
|
1146
|
+
label: `git tag ${tag}`
|
|
1147
|
+
});
|
|
1148
|
+
ctx.output.git.tags.push(tag);
|
|
1149
|
+
if (!dryRun) {
|
|
1150
|
+
success(`Created tag: ${tag}`);
|
|
1151
|
+
}
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
throw createPublishError(
|
|
1154
|
+
"GIT_TAG_ERROR" /* GIT_TAG_ERROR */,
|
|
1155
|
+
`Failed to create tag ${tag}: ${error instanceof Error ? error.message : String(error)}`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// src/stages/git-push.ts
|
|
1162
|
+
function toGithubAuthedUrl(remoteUrl, token) {
|
|
1163
|
+
try {
|
|
1164
|
+
const url = new URL(remoteUrl);
|
|
1165
|
+
if (url.protocol !== "https:") return void 0;
|
|
1166
|
+
if (url.host !== "github.com") return void 0;
|
|
1167
|
+
url.username = "x-access-token";
|
|
1168
|
+
url.password = token;
|
|
1169
|
+
return url.toString();
|
|
1170
|
+
} catch {
|
|
1171
|
+
return void 0;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
async function runGitPushStage(ctx) {
|
|
1175
|
+
const { config, cliOptions, cwd, output } = ctx;
|
|
1176
|
+
const dryRun = cliOptions.dryRun;
|
|
1177
|
+
if (!config.git.push) {
|
|
1178
|
+
info("Git push disabled in config, skipping");
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
if (!output.git.committed && output.git.tags.length === 0) {
|
|
1182
|
+
info("Nothing to push (no commits or tags created)");
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const { remote } = config.git;
|
|
1186
|
+
let pushMethod = config.git.pushMethod;
|
|
1187
|
+
if (pushMethod === "auto") {
|
|
1188
|
+
try {
|
|
1189
|
+
pushMethod = await detectGitPushMethod(remote, cwd);
|
|
1190
|
+
} catch {
|
|
1191
|
+
pushMethod = "https";
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
const httpsTokenEnv = config.git.httpsTokenEnv;
|
|
1195
|
+
const httpsToken = httpsTokenEnv ? process.env[httpsTokenEnv] : void 0;
|
|
1196
|
+
try {
|
|
1197
|
+
let pushRemote = remote;
|
|
1198
|
+
if (pushMethod === "https" && httpsToken) {
|
|
1199
|
+
const remoteUrlResult = await execCommand("git", ["remote", "get-url", remote], { cwd, dryRun: false });
|
|
1200
|
+
const authed = toGithubAuthedUrl(remoteUrlResult.stdout.trim(), httpsToken);
|
|
1201
|
+
if (authed) {
|
|
1202
|
+
pushRemote = authed;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
let branch;
|
|
1206
|
+
if (output.git.committed) {
|
|
1207
|
+
branch = config.git.branch;
|
|
1208
|
+
if (!branch) {
|
|
1209
|
+
const revResult = await execCommand("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, dryRun: false });
|
|
1210
|
+
branch = revResult.stdout.trim();
|
|
1211
|
+
if (branch === "HEAD") {
|
|
1212
|
+
throw createPublishError(
|
|
1213
|
+
"GIT_PUSH_ERROR" /* GIT_PUSH_ERROR */,
|
|
1214
|
+
"Cannot push: repository is in a detached HEAD state. Set git.branch in your config or pass --branch <name>."
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
await execCommand("git", ["push", pushRemote, branch], {
|
|
1219
|
+
cwd,
|
|
1220
|
+
dryRun,
|
|
1221
|
+
label: `git push ${remote} ${branch}`
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
if (output.git.tags.length > 0) {
|
|
1225
|
+
await execCommand("git", ["push", pushRemote, "--tags"], {
|
|
1226
|
+
cwd,
|
|
1227
|
+
dryRun,
|
|
1228
|
+
label: `git push ${remote} --tags`
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
ctx.output.git.pushed = true;
|
|
1232
|
+
if (!dryRun) {
|
|
1233
|
+
success(`Pushed to ${remote}${branch ? `/${branch}` : ""}`);
|
|
1234
|
+
}
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
if (error instanceof PublishError) {
|
|
1237
|
+
throw error;
|
|
1238
|
+
}
|
|
1239
|
+
throw createPublishError(
|
|
1240
|
+
"GIT_PUSH_ERROR" /* GIT_PUSH_ERROR */,
|
|
1241
|
+
`${error instanceof Error ? error.message : String(error)}`
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/stages/github-release.ts
|
|
1247
|
+
function resolveNotes(bodySource, tag, changelogs, releaseNotesEnabled, pipelineNotes) {
|
|
1248
|
+
if (bodySource === "none") {
|
|
1249
|
+
return { useGithubNotes: false };
|
|
1250
|
+
}
|
|
1251
|
+
if (bodySource === "generated") {
|
|
1252
|
+
return { useGithubNotes: true };
|
|
1253
|
+
}
|
|
1254
|
+
if (bodySource === "releaseNotes") {
|
|
1255
|
+
if (!releaseNotesEnabled) {
|
|
1256
|
+
warn("releaseNotes is not enabled in notes config but body is set to releaseNotes");
|
|
1257
|
+
return { useGithubNotes: true };
|
|
1258
|
+
}
|
|
1259
|
+
if (pipelineNotes) {
|
|
1260
|
+
const body = findNotesForTag(tag, pipelineNotes);
|
|
1261
|
+
if (body) return { body, useGithubNotes: false };
|
|
1262
|
+
}
|
|
1263
|
+
warn("No release notes found in pipeline output, falling back to GitHub auto-notes");
|
|
1264
|
+
return { useGithubNotes: true };
|
|
1265
|
+
}
|
|
1266
|
+
if (bodySource === "changelog") {
|
|
1267
|
+
const packageBody2 = formatChangelogForTag(tag, changelogs);
|
|
1268
|
+
if (packageBody2) {
|
|
1269
|
+
return { body: packageBody2, useGithubNotes: false };
|
|
1270
|
+
}
|
|
1271
|
+
warn("No changelog found for tag, falling back to GitHub auto-notes");
|
|
1272
|
+
return { useGithubNotes: true };
|
|
1273
|
+
}
|
|
1274
|
+
if (releaseNotesEnabled && pipelineNotes) {
|
|
1275
|
+
const body = findNotesForTag(tag, pipelineNotes);
|
|
1276
|
+
if (body) return { body, useGithubNotes: false };
|
|
1277
|
+
}
|
|
1278
|
+
const packageBody = formatChangelogForTag(tag, changelogs);
|
|
1279
|
+
if (packageBody) {
|
|
1280
|
+
return { body: packageBody, useGithubNotes: false };
|
|
1281
|
+
}
|
|
1282
|
+
return { useGithubNotes: true };
|
|
1283
|
+
}
|
|
1284
|
+
function isVersionOnlyTag(tag) {
|
|
1285
|
+
return /^v?\d+\.\d+\.\d+/.test(tag);
|
|
1286
|
+
}
|
|
1287
|
+
function getTitleFromTag(tag) {
|
|
1288
|
+
const atIndex = tag.lastIndexOf("@");
|
|
1289
|
+
if (atIndex === -1) {
|
|
1290
|
+
return tag;
|
|
1291
|
+
}
|
|
1292
|
+
const packageName = tag.slice(0, atIndex);
|
|
1293
|
+
const version = tag.slice(atIndex + 1);
|
|
1294
|
+
return `${packageName} @ ${version}`;
|
|
1295
|
+
}
|
|
1296
|
+
function findNotesForTag(tag, notes) {
|
|
1297
|
+
for (const [packageName, body] of Object.entries(notes)) {
|
|
1298
|
+
if (tag.startsWith(`${packageName}@`) && body.trim()) {
|
|
1299
|
+
return body;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
const entries = Object.values(notes).filter((b) => b.trim());
|
|
1303
|
+
if (entries.length === 1 && isVersionOnlyTag(tag)) return entries[0];
|
|
1304
|
+
return void 0;
|
|
1305
|
+
}
|
|
1306
|
+
function formatChangelogForTag(tag, changelogs) {
|
|
1307
|
+
if (changelogs.length === 0) return void 0;
|
|
1308
|
+
const changelog = changelogs.find((c) => tag.startsWith(`${c.packageName}@`));
|
|
1309
|
+
const target = changelog ?? (changelogs.length === 1 && isVersionOnlyTag(tag) ? changelogs[0] : void 0);
|
|
1310
|
+
if (!target || target.entries.length === 0) return void 0;
|
|
1311
|
+
const lines = [];
|
|
1312
|
+
for (const entry of target.entries) {
|
|
1313
|
+
const scope = entry.scope ? `**${entry.scope}:** ` : "";
|
|
1314
|
+
lines.push(`- ${scope}${entry.description}`);
|
|
1315
|
+
}
|
|
1316
|
+
return lines.join("\n");
|
|
1317
|
+
}
|
|
1318
|
+
async function runGithubReleaseStage(ctx) {
|
|
1319
|
+
const { config, cliOptions, output } = ctx;
|
|
1320
|
+
const dryRun = cliOptions.dryRun;
|
|
1321
|
+
if (!config.githubRelease.enabled) {
|
|
1322
|
+
debug("GitHub releases disabled in config");
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
const tags = output.git.tags.length > 0 ? output.git.tags : ctx.input.tags;
|
|
1326
|
+
if (tags.length === 0) {
|
|
1327
|
+
info("No tags available for GitHub release");
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
const firstTag = tags[0];
|
|
1331
|
+
if (!firstTag) return;
|
|
1332
|
+
const tagsToRelease = config.githubRelease.perPackage ? tags : [firstTag];
|
|
1333
|
+
for (const tag of tagsToRelease) {
|
|
1334
|
+
const MAX_TAG_LENGTH = 1e3;
|
|
1335
|
+
const truncatedTag = tag.length > MAX_TAG_LENGTH ? tag.slice(0, MAX_TAG_LENGTH) : tag;
|
|
1336
|
+
const versionMatch = truncatedTag.match(/(\d{1,20}\.\d{1,20}\.\d{1,20}(?:[-+.]?[a-zA-Z0-9.-]{0,100})?)$/);
|
|
1337
|
+
const version = versionMatch?.[1] ?? "";
|
|
1338
|
+
const isPreRel = config.githubRelease.prerelease === "auto" ? version ? isPrerelease(version) : false : config.githubRelease.prerelease;
|
|
1339
|
+
const result = {
|
|
1340
|
+
tag,
|
|
1341
|
+
draft: config.githubRelease.draft,
|
|
1342
|
+
prerelease: isPreRel,
|
|
1343
|
+
success: false
|
|
1344
|
+
};
|
|
1345
|
+
const ghArgs = ["release", "create", tag];
|
|
1346
|
+
ghArgs.push("--title", getTitleFromTag(tag));
|
|
1347
|
+
if (config.githubRelease.draft) {
|
|
1348
|
+
ghArgs.push("--draft");
|
|
1349
|
+
}
|
|
1350
|
+
if (isPreRel) {
|
|
1351
|
+
ghArgs.push("--prerelease");
|
|
1352
|
+
}
|
|
1353
|
+
const releaseNotesEnabled = !!(ctx.releaseNotes && Object.keys(ctx.releaseNotes).length > 0);
|
|
1354
|
+
const { body, useGithubNotes } = resolveNotes(
|
|
1355
|
+
config.githubRelease.body,
|
|
1356
|
+
tag,
|
|
1357
|
+
ctx.input.changelogs,
|
|
1358
|
+
releaseNotesEnabled,
|
|
1359
|
+
ctx.releaseNotes
|
|
1360
|
+
);
|
|
1361
|
+
if (body) {
|
|
1362
|
+
ghArgs.push("--notes", body);
|
|
1363
|
+
} else if (useGithubNotes) {
|
|
1364
|
+
ghArgs.push("--generate-notes");
|
|
1365
|
+
}
|
|
1366
|
+
try {
|
|
1367
|
+
const execResult = await execCommand("gh", ghArgs, {
|
|
1368
|
+
dryRun,
|
|
1369
|
+
label: `gh release create ${tag}`
|
|
1370
|
+
});
|
|
1371
|
+
result.success = true;
|
|
1372
|
+
if (!dryRun && execResult.stdout.trim()) {
|
|
1373
|
+
result.url = execResult.stdout.trim();
|
|
1374
|
+
}
|
|
1375
|
+
if (!dryRun) {
|
|
1376
|
+
success(`Created GitHub release for ${tag}`);
|
|
1377
|
+
}
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
result.reason = error instanceof Error ? error.message : String(error);
|
|
1380
|
+
warn(`Failed to create GitHub release for ${tag}: ${result.reason}`);
|
|
1381
|
+
}
|
|
1382
|
+
ctx.output.githubReleases.push(result);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// src/stages/npm-publish.ts
|
|
1387
|
+
import * as fs8 from "fs";
|
|
1388
|
+
import * as path8 from "path";
|
|
1389
|
+
|
|
1390
|
+
// src/utils/npm-env.ts
|
|
1391
|
+
import * as fs7 from "fs";
|
|
1392
|
+
import * as os2 from "os";
|
|
1393
|
+
import * as path7 from "path";
|
|
1394
|
+
function writeTempNpmrc(contents) {
|
|
1395
|
+
const dir = fs7.mkdtempSync(path7.join(os2.tmpdir(), "releasekit-npmrc-"));
|
|
1396
|
+
const npmrcPath = path7.join(dir, ".npmrc");
|
|
1397
|
+
fs7.writeFileSync(npmrcPath, contents, "utf-8");
|
|
1398
|
+
return {
|
|
1399
|
+
npmrcPath,
|
|
1400
|
+
cleanup: () => {
|
|
1401
|
+
try {
|
|
1402
|
+
fs7.rmSync(dir, { recursive: true, force: true });
|
|
1403
|
+
} catch {
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
function createNpmSubprocessIsolation(options) {
|
|
1409
|
+
const { authMethod, registryUrl } = options;
|
|
1410
|
+
const baseEnv = {};
|
|
1411
|
+
if (!authMethod) return { env: baseEnv, cleanup: () => {
|
|
1412
|
+
} };
|
|
1413
|
+
const token = process.env.NPM_TOKEN ?? process.env.NODE_AUTH_TOKEN;
|
|
1414
|
+
const registryHost = (() => {
|
|
1415
|
+
try {
|
|
1416
|
+
return new URL(registryUrl).host;
|
|
1417
|
+
} catch {
|
|
1418
|
+
return "registry.npmjs.org";
|
|
1419
|
+
}
|
|
1420
|
+
})();
|
|
1421
|
+
const lines = [`registry=${registryUrl}`];
|
|
1422
|
+
if (authMethod === "token" && token) {
|
|
1423
|
+
lines.push(`//${registryHost}/:_authToken=${token}`);
|
|
1424
|
+
}
|
|
1425
|
+
lines.push("");
|
|
1426
|
+
const { npmrcPath, cleanup } = writeTempNpmrc(lines.join("\n"));
|
|
1427
|
+
debug(`Using isolated npm userconfig: ${npmrcPath}`);
|
|
1428
|
+
const isOidc = authMethod === "oidc";
|
|
1429
|
+
return {
|
|
1430
|
+
env: {
|
|
1431
|
+
...baseEnv,
|
|
1432
|
+
// Ensure npm and tools that read npm_config_* pick up our temp file
|
|
1433
|
+
NPM_CONFIG_USERCONFIG: npmrcPath,
|
|
1434
|
+
npm_config_userconfig: npmrcPath,
|
|
1435
|
+
// Auth-specific hardening
|
|
1436
|
+
...isOidc ? {
|
|
1437
|
+
// Prevent any ambient token from overriding OIDC trusted publishing
|
|
1438
|
+
NODE_AUTH_TOKEN: void 0,
|
|
1439
|
+
NPM_TOKEN: void 0
|
|
1440
|
+
} : {
|
|
1441
|
+
// Ensure CLIs that expect NODE_AUTH_TOKEN can still work
|
|
1442
|
+
NODE_AUTH_TOKEN: token
|
|
1443
|
+
}
|
|
1444
|
+
},
|
|
1445
|
+
cleanup
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/stages/npm-publish.ts
|
|
1450
|
+
async function runNpmPublishStage(ctx) {
|
|
1451
|
+
const { input, config, cliOptions, cwd } = ctx;
|
|
1452
|
+
const dryRun = cliOptions.dryRun;
|
|
1453
|
+
if (!config.npm.enabled) {
|
|
1454
|
+
info("NPM publishing disabled in config");
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const authMethod = config.npm.auth === "auto" ? detectNpmAuth() : config.npm.auth;
|
|
1458
|
+
if (!authMethod && !dryRun) {
|
|
1459
|
+
throw createPublishError("NPM_AUTH_ERROR" /* NPM_AUTH_ERROR */, "No NPM authentication method detected");
|
|
1460
|
+
}
|
|
1461
|
+
const useProvenance = config.npm.provenance && authMethod === "oidc";
|
|
1462
|
+
const npmIsolation = createNpmSubprocessIsolation({
|
|
1463
|
+
authMethod,
|
|
1464
|
+
registryUrl: config.npm.registry
|
|
1465
|
+
});
|
|
1466
|
+
try {
|
|
1467
|
+
for (const update of input.updates) {
|
|
1468
|
+
const result = {
|
|
1469
|
+
packageName: update.packageName,
|
|
1470
|
+
version: update.newVersion,
|
|
1471
|
+
registry: "npm",
|
|
1472
|
+
success: false,
|
|
1473
|
+
skipped: false
|
|
1474
|
+
};
|
|
1475
|
+
const pkgJsonPath = path8.resolve(cwd, update.filePath);
|
|
1476
|
+
try {
|
|
1477
|
+
const pkgContent = fs8.readFileSync(pkgJsonPath, "utf-8");
|
|
1478
|
+
const pkgJson = JSON.parse(pkgContent);
|
|
1479
|
+
if (pkgJson.private) {
|
|
1480
|
+
result.skipped = true;
|
|
1481
|
+
result.success = true;
|
|
1482
|
+
result.reason = "Package is private";
|
|
1483
|
+
ctx.output.npm.push(result);
|
|
1484
|
+
debug(`Skipping private package: ${update.packageName}`);
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
} catch {
|
|
1488
|
+
if (update.filePath.endsWith("Cargo.toml")) {
|
|
1489
|
+
result.skipped = true;
|
|
1490
|
+
result.success = true;
|
|
1491
|
+
result.reason = "Not an npm package";
|
|
1492
|
+
ctx.output.npm.push(result);
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
const { file: viewFile, args: viewArgs } = buildViewCommand(
|
|
1497
|
+
ctx.packageManager,
|
|
1498
|
+
update.packageName,
|
|
1499
|
+
update.newVersion
|
|
1500
|
+
);
|
|
1501
|
+
const viewResult = await execCommandSafe(viewFile, viewArgs, {
|
|
1502
|
+
cwd,
|
|
1503
|
+
dryRun: false,
|
|
1504
|
+
// Always check, even in dry-run
|
|
1505
|
+
env: npmIsolation.env
|
|
1506
|
+
});
|
|
1507
|
+
if (viewResult.exitCode === 0 && viewResult.stdout.trim()) {
|
|
1508
|
+
result.alreadyPublished = true;
|
|
1509
|
+
result.skipped = true;
|
|
1510
|
+
result.success = true;
|
|
1511
|
+
result.reason = "Already published";
|
|
1512
|
+
ctx.output.npm.push(result);
|
|
1513
|
+
warn(`${update.packageName}@${update.newVersion} is already published, skipping`);
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
const distTag = getDistTag(update.newVersion, config.npm.tag);
|
|
1517
|
+
const pkgDir = path8.dirname(path8.resolve(cwd, update.filePath));
|
|
1518
|
+
const { file: pubFile, args: pubArgs } = buildPublishCommand(ctx.packageManager, update.packageName, pkgDir, {
|
|
1519
|
+
access: config.npm.access,
|
|
1520
|
+
tag: distTag,
|
|
1521
|
+
provenance: useProvenance,
|
|
1522
|
+
noGitChecks: true
|
|
1523
|
+
});
|
|
1524
|
+
try {
|
|
1525
|
+
await execCommand(pubFile, pubArgs, {
|
|
1526
|
+
cwd,
|
|
1527
|
+
dryRun,
|
|
1528
|
+
label: `npm publish ${update.packageName}@${update.newVersion}`,
|
|
1529
|
+
env: npmIsolation.env
|
|
1530
|
+
});
|
|
1531
|
+
result.success = true;
|
|
1532
|
+
if (!dryRun) {
|
|
1533
|
+
success(`Published ${update.packageName}@${update.newVersion} to npm`);
|
|
1534
|
+
}
|
|
1535
|
+
ctx.output.npm.push(result);
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
result.reason = error instanceof Error ? error.message : String(error);
|
|
1538
|
+
ctx.output.npm.push(result);
|
|
1539
|
+
throw createPublishError(
|
|
1540
|
+
"NPM_PUBLISH_ERROR" /* NPM_PUBLISH_ERROR */,
|
|
1541
|
+
`${update.packageName}@${update.newVersion}: ${result.reason}`
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
} finally {
|
|
1546
|
+
npmIsolation.cleanup();
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// src/stages/prepare.ts
|
|
1551
|
+
import * as fs9 from "fs";
|
|
1552
|
+
import * as path9 from "path";
|
|
1553
|
+
async function runPrepareStage(ctx) {
|
|
1554
|
+
const { input, config, cliOptions, cwd } = ctx;
|
|
1555
|
+
if (config.npm.enabled && config.npm.copyFiles.length > 0) {
|
|
1556
|
+
for (const update of input.updates) {
|
|
1557
|
+
const pkgDir = path9.dirname(path9.resolve(cwd, update.filePath));
|
|
1558
|
+
for (const file of config.npm.copyFiles) {
|
|
1559
|
+
const src = path9.resolve(cwd, file);
|
|
1560
|
+
const dest = path9.join(pkgDir, file);
|
|
1561
|
+
if (!fs9.existsSync(src)) {
|
|
1562
|
+
debug(`Source file not found, skipping copy: ${src}`);
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
if (path9.resolve(path9.dirname(src)) === path9.resolve(pkgDir)) {
|
|
1566
|
+
debug(`Skipping copy of ${file} - same directory as source`);
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (cliOptions.dryRun) {
|
|
1570
|
+
info(`[DRY RUN] Would copy ${src} \u2192 ${dest}`);
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
try {
|
|
1574
|
+
fs9.copyFileSync(src, dest);
|
|
1575
|
+
debug(`Copied ${file} \u2192 ${pkgDir}`);
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
throw createPublishError(
|
|
1578
|
+
"FILE_COPY_ERROR" /* FILE_COPY_ERROR */,
|
|
1579
|
+
`Failed to copy ${src} to ${dest}: ${error instanceof Error ? error.message : String(error)}`
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
if (config.cargo.enabled) {
|
|
1586
|
+
for (const update of input.updates) {
|
|
1587
|
+
const pkgDir = path9.dirname(path9.resolve(cwd, update.filePath));
|
|
1588
|
+
const cargoPath = path9.join(pkgDir, "Cargo.toml");
|
|
1589
|
+
if (!fs9.existsSync(cargoPath)) {
|
|
1590
|
+
continue;
|
|
1591
|
+
}
|
|
1592
|
+
if (cliOptions.dryRun) {
|
|
1593
|
+
info(`[DRY RUN] Would update ${cargoPath} to version ${update.newVersion}`);
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
updateCargoVersion(cargoPath, update.newVersion);
|
|
1597
|
+
debug(`Updated ${cargoPath} to version ${update.newVersion}`);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// src/utils/retry.ts
|
|
1603
|
+
async function withRetry(fn, options, shouldRetry) {
|
|
1604
|
+
let lastError;
|
|
1605
|
+
let delay = options.initialDelay;
|
|
1606
|
+
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
|
|
1607
|
+
try {
|
|
1608
|
+
return await fn();
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
lastError = error;
|
|
1611
|
+
if (shouldRetry && !shouldRetry(error)) {
|
|
1612
|
+
throw error;
|
|
1613
|
+
}
|
|
1614
|
+
if (attempt < options.maxAttempts) {
|
|
1615
|
+
debug(`Attempt ${attempt}/${options.maxAttempts} failed, retrying in ${delay}ms...`);
|
|
1616
|
+
await sleep(delay);
|
|
1617
|
+
delay = Math.floor(delay * options.backoffMultiplier);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
throw lastError;
|
|
1622
|
+
}
|
|
1623
|
+
function sleep(ms) {
|
|
1624
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
// src/stages/verify.ts
|
|
1628
|
+
async function runVerifyStage(ctx) {
|
|
1629
|
+
const { config, cliOptions, output, cwd } = ctx;
|
|
1630
|
+
if (config.verify.npm.enabled) {
|
|
1631
|
+
const published = output.npm.filter((r) => r.success && !r.skipped && !r.alreadyPublished);
|
|
1632
|
+
for (const pkg of published) {
|
|
1633
|
+
const result = {
|
|
1634
|
+
packageName: pkg.packageName,
|
|
1635
|
+
version: pkg.version,
|
|
1636
|
+
registry: "npm",
|
|
1637
|
+
verified: false,
|
|
1638
|
+
attempts: 0
|
|
1639
|
+
};
|
|
1640
|
+
if (cliOptions.dryRun) {
|
|
1641
|
+
info(`[DRY RUN] Would verify ${pkg.packageName}@${pkg.version} on npm`);
|
|
1642
|
+
result.verified = true;
|
|
1643
|
+
ctx.output.verification.push(result);
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
try {
|
|
1647
|
+
await withRetry(async () => {
|
|
1648
|
+
result.attempts++;
|
|
1649
|
+
const { file: viewFile, args: viewArgs } = buildViewCommand(ctx.packageManager, pkg.packageName, pkg.version);
|
|
1650
|
+
const viewResult = await execCommandSafe(viewFile, viewArgs, {
|
|
1651
|
+
cwd,
|
|
1652
|
+
dryRun: false
|
|
1653
|
+
});
|
|
1654
|
+
if (viewResult.exitCode !== 0 || !viewResult.stdout.trim()) {
|
|
1655
|
+
throw new Error(`${pkg.packageName}@${pkg.version} not yet available on npm`);
|
|
1656
|
+
}
|
|
1657
|
+
debug(`Verified ${pkg.packageName}@${pkg.version} on npm`);
|
|
1658
|
+
}, config.verify.npm);
|
|
1659
|
+
result.verified = true;
|
|
1660
|
+
success(`Verified ${pkg.packageName}@${pkg.version} on npm`);
|
|
1661
|
+
} catch {
|
|
1662
|
+
warn(`Failed to verify ${pkg.packageName}@${pkg.version} on npm after ${result.attempts} attempts`);
|
|
1663
|
+
}
|
|
1664
|
+
ctx.output.verification.push(result);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
if (config.verify.cargo.enabled) {
|
|
1668
|
+
const published = output.cargo.filter((r) => r.success && !r.skipped && !r.alreadyPublished);
|
|
1669
|
+
for (const crate of published) {
|
|
1670
|
+
const result = {
|
|
1671
|
+
packageName: crate.packageName,
|
|
1672
|
+
version: crate.version,
|
|
1673
|
+
registry: "cargo",
|
|
1674
|
+
verified: false,
|
|
1675
|
+
attempts: 0
|
|
1676
|
+
};
|
|
1677
|
+
if (cliOptions.dryRun) {
|
|
1678
|
+
info(`[DRY RUN] Would verify ${crate.packageName}@${crate.version} on crates.io`);
|
|
1679
|
+
result.verified = true;
|
|
1680
|
+
ctx.output.verification.push(result);
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
try {
|
|
1684
|
+
await withRetry(async () => {
|
|
1685
|
+
result.attempts++;
|
|
1686
|
+
const response = await fetch(`https://crates.io/api/v1/crates/${crate.packageName}/${crate.version}`);
|
|
1687
|
+
if (!response.ok) {
|
|
1688
|
+
throw new Error(`${crate.packageName}@${crate.version} not yet available on crates.io`);
|
|
1689
|
+
}
|
|
1690
|
+
debug(`Verified ${crate.packageName}@${crate.version} on crates.io`);
|
|
1691
|
+
}, config.verify.cargo);
|
|
1692
|
+
result.verified = true;
|
|
1693
|
+
success(`Verified ${crate.packageName}@${crate.version} on crates.io`);
|
|
1694
|
+
} catch {
|
|
1695
|
+
warn(`Failed to verify ${crate.packageName}@${crate.version} on crates.io after ${result.attempts} attempts`);
|
|
1696
|
+
}
|
|
1697
|
+
ctx.output.verification.push(result);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// src/pipeline/index.ts
|
|
1703
|
+
function inferStageName(error) {
|
|
1704
|
+
if (error instanceof BasePublishError) {
|
|
1705
|
+
const codeToStage = {
|
|
1706
|
+
FILE_COPY_ERROR: "prepare",
|
|
1707
|
+
CARGO_TOML_ERROR: "prepare",
|
|
1708
|
+
GIT_COMMIT_ERROR: "git-commit",
|
|
1709
|
+
GIT_TAG_ERROR: "git-commit",
|
|
1710
|
+
NPM_PUBLISH_ERROR: "npm-publish",
|
|
1711
|
+
NPM_AUTH_ERROR: "npm-publish",
|
|
1712
|
+
CARGO_PUBLISH_ERROR: "cargo-publish",
|
|
1713
|
+
CARGO_AUTH_ERROR: "cargo-publish",
|
|
1714
|
+
VERIFICATION_FAILED: "verify",
|
|
1715
|
+
GIT_PUSH_ERROR: "git-push",
|
|
1716
|
+
GITHUB_RELEASE_ERROR: "github-release"
|
|
1717
|
+
};
|
|
1718
|
+
return codeToStage[error.code] ?? "unknown";
|
|
1719
|
+
}
|
|
1720
|
+
return "unknown";
|
|
1721
|
+
}
|
|
1722
|
+
async function runPipeline(input, config, options) {
|
|
1723
|
+
const cwd = process.cwd();
|
|
1724
|
+
const ctx = {
|
|
1725
|
+
input,
|
|
1726
|
+
config,
|
|
1727
|
+
cliOptions: options,
|
|
1728
|
+
packageManager: detectPackageManager(cwd),
|
|
1729
|
+
cwd,
|
|
1730
|
+
releaseNotes: options.releaseNotes,
|
|
1731
|
+
additionalFiles: options.additionalFiles,
|
|
1732
|
+
output: {
|
|
1733
|
+
dryRun: options.dryRun,
|
|
1734
|
+
git: { committed: false, tags: [], pushed: false },
|
|
1735
|
+
npm: [],
|
|
1736
|
+
cargo: [],
|
|
1737
|
+
verification: [],
|
|
1738
|
+
githubReleases: [],
|
|
1739
|
+
publishSucceeded: false
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
try {
|
|
1743
|
+
await runPrepareStage(ctx);
|
|
1744
|
+
if (options.skipGitCommit && !options.skipGit) {
|
|
1745
|
+
ctx.output.git.committed = !!input.commitMessage;
|
|
1746
|
+
ctx.output.git.tags = [...input.tags];
|
|
1747
|
+
} else if (!options.skipGit) {
|
|
1748
|
+
await runGitCommitStage(ctx);
|
|
1749
|
+
}
|
|
1750
|
+
if (!options.skipPublish) {
|
|
1751
|
+
if (options.registry === "all" || options.registry === "npm") {
|
|
1752
|
+
await runNpmPublishStage(ctx);
|
|
1753
|
+
}
|
|
1754
|
+
if (options.registry === "all" || options.registry === "cargo") {
|
|
1755
|
+
await runCargoPublishStage(ctx);
|
|
1756
|
+
}
|
|
1757
|
+
ctx.output.publishSucceeded = ctx.output.npm.every((r) => r.success) && ctx.output.cargo.every((r) => r.success);
|
|
1758
|
+
}
|
|
1759
|
+
if (!options.skipVerification && !options.skipPublish) {
|
|
1760
|
+
await runVerifyStage(ctx);
|
|
1761
|
+
}
|
|
1762
|
+
if (!options.skipGit && (options.skipPublish || ctx.output.publishSucceeded)) {
|
|
1763
|
+
await runGitPushStage(ctx);
|
|
1764
|
+
}
|
|
1765
|
+
if (!options.skipGithubRelease && ctx.output.git.pushed) {
|
|
1766
|
+
await runGithubReleaseStage(ctx);
|
|
1767
|
+
}
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
const stageName = inferStageName(error);
|
|
1770
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1771
|
+
throw new PipelineError(message, stageName, ctx.output, error instanceof Error ? error : void 0);
|
|
1772
|
+
}
|
|
1773
|
+
return ctx.output;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// src/stages/input.ts
|
|
1777
|
+
import * as fs10 from "fs";
|
|
1778
|
+
import { z as z3 } from "zod";
|
|
1779
|
+
var VersionChangelogEntrySchema = z3.object({
|
|
1780
|
+
type: z3.string(),
|
|
1781
|
+
description: z3.string(),
|
|
1782
|
+
issueIds: z3.array(z3.string()).optional(),
|
|
1783
|
+
scope: z3.string().optional(),
|
|
1784
|
+
originalType: z3.string().optional()
|
|
1785
|
+
});
|
|
1786
|
+
var VersionPackageChangelogSchema = z3.object({
|
|
1787
|
+
packageName: z3.string(),
|
|
1788
|
+
version: z3.string(),
|
|
1789
|
+
previousVersion: z3.string().nullable(),
|
|
1790
|
+
revisionRange: z3.string(),
|
|
1791
|
+
repoUrl: z3.string().nullable(),
|
|
1792
|
+
entries: z3.array(VersionChangelogEntrySchema)
|
|
1793
|
+
});
|
|
1794
|
+
var VersionPackageUpdateSchema = z3.object({
|
|
1795
|
+
packageName: z3.string(),
|
|
1796
|
+
newVersion: z3.string(),
|
|
1797
|
+
filePath: z3.string()
|
|
1798
|
+
});
|
|
1799
|
+
var VersionOutputSchema = z3.object({
|
|
1800
|
+
dryRun: z3.boolean(),
|
|
1801
|
+
updates: z3.array(VersionPackageUpdateSchema),
|
|
1802
|
+
changelogs: z3.array(VersionPackageChangelogSchema),
|
|
1803
|
+
commitMessage: z3.string().optional(),
|
|
1804
|
+
tags: z3.array(z3.string())
|
|
1805
|
+
});
|
|
1806
|
+
async function parseInput(inputPath) {
|
|
1807
|
+
let raw;
|
|
1808
|
+
if (inputPath) {
|
|
1809
|
+
try {
|
|
1810
|
+
raw = fs10.readFileSync(inputPath, "utf-8");
|
|
1811
|
+
} catch {
|
|
1812
|
+
throw createPublishError("INPUT_PARSE_ERROR" /* INPUT_PARSE_ERROR */, `Could not read file: ${inputPath}`);
|
|
1813
|
+
}
|
|
1814
|
+
} else {
|
|
1815
|
+
raw = await readStdin();
|
|
1816
|
+
}
|
|
1817
|
+
let parsed;
|
|
1818
|
+
try {
|
|
1819
|
+
parsed = JSON.parse(raw);
|
|
1820
|
+
} catch {
|
|
1821
|
+
throw createPublishError("INPUT_PARSE_ERROR" /* INPUT_PARSE_ERROR */, "Input is not valid JSON");
|
|
1822
|
+
}
|
|
1823
|
+
const result = VersionOutputSchema.safeParse(parsed);
|
|
1824
|
+
if (!result.success) {
|
|
1825
|
+
const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
1826
|
+
throw createPublishError("INPUT_VALIDATION_ERROR" /* INPUT_VALIDATION_ERROR */, `Schema validation failed:
|
|
1827
|
+
${issues}`);
|
|
1828
|
+
}
|
|
1829
|
+
if (result.data.updates.length === 0) {
|
|
1830
|
+
info("No package updates in version output \u2014 pipeline will be a no-op");
|
|
1831
|
+
}
|
|
1832
|
+
return result.data;
|
|
1833
|
+
}
|
|
1834
|
+
async function readStdin() {
|
|
1835
|
+
const chunks = [];
|
|
1836
|
+
for await (const chunk of process.stdin) {
|
|
1837
|
+
chunks.push(chunk);
|
|
1838
|
+
}
|
|
1839
|
+
return chunks.join("");
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
export {
|
|
1843
|
+
readPackageVersion,
|
|
1844
|
+
setLogLevel,
|
|
1845
|
+
setJsonMode,
|
|
1846
|
+
EXIT_CODES,
|
|
1847
|
+
parseCargoToml,
|
|
1848
|
+
loadConfig2 as loadConfig,
|
|
1849
|
+
getDefaultConfig2 as getDefaultConfig,
|
|
1850
|
+
BasePublishError,
|
|
1851
|
+
PublishError,
|
|
1852
|
+
PipelineError,
|
|
1853
|
+
PublishErrorCode,
|
|
1854
|
+
createPublishError,
|
|
1855
|
+
detectNpmAuth,
|
|
1856
|
+
hasCargoAuth,
|
|
1857
|
+
updateCargoVersion,
|
|
1858
|
+
extractPathDeps,
|
|
1859
|
+
isPrerelease,
|
|
1860
|
+
getDistTag,
|
|
1861
|
+
detectPackageManager,
|
|
1862
|
+
runPipeline,
|
|
1863
|
+
parseInput
|
|
1864
|
+
};
|