@releasekit/publish 0.1.0 → 0.2.0-next.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.
@@ -1,1019 +0,0 @@
1
- // src/errors/index.ts
2
- import { ReleaseKitError } from "@releasekit/core";
3
- var BasePublishError = class _BasePublishError extends ReleaseKitError {
4
- code;
5
- suggestions;
6
- constructor(message, code, suggestions) {
7
- super(message);
8
- this.code = code;
9
- this.suggestions = suggestions ?? [];
10
- }
11
- static isPublishError(error) {
12
- return error instanceof _BasePublishError;
13
- }
14
- };
15
- var PublishError = class extends BasePublishError {
16
- };
17
- var PublishErrorCode = /* @__PURE__ */ ((PublishErrorCode2) => {
18
- PublishErrorCode2["INPUT_PARSE_ERROR"] = "INPUT_PARSE_ERROR";
19
- PublishErrorCode2["INPUT_VALIDATION_ERROR"] = "INPUT_VALIDATION_ERROR";
20
- PublishErrorCode2["CONFIG_ERROR"] = "CONFIG_ERROR";
21
- PublishErrorCode2["GIT_COMMIT_ERROR"] = "GIT_COMMIT_ERROR";
22
- PublishErrorCode2["GIT_TAG_ERROR"] = "GIT_TAG_ERROR";
23
- PublishErrorCode2["GIT_PUSH_ERROR"] = "GIT_PUSH_ERROR";
24
- PublishErrorCode2["NPM_PUBLISH_ERROR"] = "NPM_PUBLISH_ERROR";
25
- PublishErrorCode2["NPM_AUTH_ERROR"] = "NPM_AUTH_ERROR";
26
- PublishErrorCode2["CARGO_PUBLISH_ERROR"] = "CARGO_PUBLISH_ERROR";
27
- PublishErrorCode2["CARGO_AUTH_ERROR"] = "CARGO_AUTH_ERROR";
28
- PublishErrorCode2["VERIFICATION_FAILED"] = "VERIFICATION_FAILED";
29
- PublishErrorCode2["GITHUB_RELEASE_ERROR"] = "GITHUB_RELEASE_ERROR";
30
- PublishErrorCode2["FILE_COPY_ERROR"] = "FILE_COPY_ERROR";
31
- PublishErrorCode2["CARGO_TOML_ERROR"] = "CARGO_TOML_ERROR";
32
- return PublishErrorCode2;
33
- })(PublishErrorCode || {});
34
- function createPublishError(code, details) {
35
- const messages = {
36
- ["INPUT_PARSE_ERROR" /* INPUT_PARSE_ERROR */]: "Failed to parse version output",
37
- ["INPUT_VALIDATION_ERROR" /* INPUT_VALIDATION_ERROR */]: "Version output validation failed",
38
- ["CONFIG_ERROR" /* CONFIG_ERROR */]: "Invalid publish configuration",
39
- ["GIT_COMMIT_ERROR" /* GIT_COMMIT_ERROR */]: "Failed to create git commit",
40
- ["GIT_TAG_ERROR" /* GIT_TAG_ERROR */]: "Failed to create git tag",
41
- ["GIT_PUSH_ERROR" /* GIT_PUSH_ERROR */]: "Failed to push to remote",
42
- ["NPM_PUBLISH_ERROR" /* NPM_PUBLISH_ERROR */]: "Failed to publish to npm",
43
- ["NPM_AUTH_ERROR" /* NPM_AUTH_ERROR */]: "NPM authentication failed",
44
- ["CARGO_PUBLISH_ERROR" /* CARGO_PUBLISH_ERROR */]: "Failed to publish to crates.io",
45
- ["CARGO_AUTH_ERROR" /* CARGO_AUTH_ERROR */]: "Cargo authentication failed",
46
- ["VERIFICATION_FAILED" /* VERIFICATION_FAILED */]: "Package verification failed",
47
- ["GITHUB_RELEASE_ERROR" /* GITHUB_RELEASE_ERROR */]: "Failed to create GitHub release",
48
- ["FILE_COPY_ERROR" /* FILE_COPY_ERROR */]: "Failed to copy files",
49
- ["CARGO_TOML_ERROR" /* CARGO_TOML_ERROR */]: "Failed to update Cargo.toml"
50
- };
51
- const suggestions = {
52
- ["INPUT_PARSE_ERROR" /* INPUT_PARSE_ERROR */]: [
53
- "Ensure the input is valid JSON from @releasekit/version --json",
54
- "Check that stdin is piped correctly or --input path is valid"
55
- ],
56
- ["INPUT_VALIDATION_ERROR" /* INPUT_VALIDATION_ERROR */]: [
57
- "Ensure the input matches the expected VersionOutput schema",
58
- "Run @releasekit/version with --json to generate valid output"
59
- ],
60
- ["CONFIG_ERROR" /* CONFIG_ERROR */]: [
61
- "Validate publish.config.json syntax",
62
- "Check configuration against the schema",
63
- "Review documentation for valid configuration options"
64
- ],
65
- ["GIT_COMMIT_ERROR" /* GIT_COMMIT_ERROR */]: [
66
- "Ensure there are staged changes to commit",
67
- "Check git user.name and user.email are configured",
68
- "Verify you have write access to the repository"
69
- ],
70
- ["GIT_TAG_ERROR" /* GIT_TAG_ERROR */]: [
71
- "Check if the tag already exists: git tag -l <tag>",
72
- "Delete existing tag if needed: git tag -d <tag>"
73
- ],
74
- ["GIT_PUSH_ERROR" /* GIT_PUSH_ERROR */]: [
75
- "Verify remote repository access",
76
- "Check SSH key or deploy key configuration",
77
- "Ensure the branch is not protected or you have push access"
78
- ],
79
- ["NPM_PUBLISH_ERROR" /* NPM_PUBLISH_ERROR */]: [
80
- "Check npm registry availability",
81
- "Verify package name is not already taken by another owner",
82
- "Ensure package version has not already been published"
83
- ],
84
- ["NPM_AUTH_ERROR" /* NPM_AUTH_ERROR */]: [
85
- "Set NPM_TOKEN environment variable for token-based auth",
86
- "Enable OIDC trusted publishing in GitHub Actions for provenance",
87
- "Run npm login for local publishing"
88
- ],
89
- ["CARGO_PUBLISH_ERROR" /* CARGO_PUBLISH_ERROR */]: [
90
- "Check crates.io registry availability",
91
- "Verify crate name ownership on crates.io",
92
- "Ensure Cargo.toml metadata is complete (description, license, etc.)"
93
- ],
94
- ["CARGO_AUTH_ERROR" /* CARGO_AUTH_ERROR */]: [
95
- "Set CARGO_REGISTRY_TOKEN environment variable",
96
- "Generate a token at https://crates.io/settings/tokens"
97
- ],
98
- ["VERIFICATION_FAILED" /* VERIFICATION_FAILED */]: [
99
- "Registry propagation may take longer than expected",
100
- "Try increasing verify.maxAttempts or verify.initialDelay in config",
101
- "Check registry status pages for outages"
102
- ],
103
- ["GITHUB_RELEASE_ERROR" /* GITHUB_RELEASE_ERROR */]: [
104
- "Ensure gh CLI is installed and authenticated",
105
- "Verify GITHUB_TOKEN has contents:write permission",
106
- "Check that the tag exists in the remote repository"
107
- ],
108
- ["FILE_COPY_ERROR" /* FILE_COPY_ERROR */]: [
109
- "Verify the source file exists in the project root",
110
- "Check file permissions"
111
- ],
112
- ["CARGO_TOML_ERROR" /* CARGO_TOML_ERROR */]: [
113
- "Ensure Cargo.toml exists and is valid TOML",
114
- "Check that the [package] section has a version field"
115
- ]
116
- };
117
- const baseMessage = messages[code];
118
- const fullMessage = details ? `${baseMessage}: ${details}` : baseMessage;
119
- return new PublishError(fullMessage, code, suggestions[code]);
120
- }
121
-
122
- // src/config.ts
123
- import * as fs from "fs";
124
- import * as path from "path";
125
- import { z } from "zod";
126
- var VerifyRegistryConfigSchema = z.object({
127
- enabled: z.boolean().default(true),
128
- maxAttempts: z.number().int().positive().default(5),
129
- initialDelay: z.number().int().positive().default(15e3),
130
- backoffMultiplier: z.number().positive().default(2)
131
- });
132
- var NpmConfigSchema = z.object({
133
- enabled: z.boolean().default(true),
134
- auth: z.enum(["auto", "oidc", "token"]).default("auto"),
135
- provenance: z.boolean().default(true),
136
- access: z.enum(["public", "restricted"]).default("public"),
137
- registry: z.string().default("https://registry.npmjs.org"),
138
- copyFiles: z.array(z.string()).default(["LICENSE"]),
139
- tag: z.string().default("latest")
140
- });
141
- var CargoConfigSchema = z.object({
142
- enabled: z.boolean().default(false),
143
- noVerify: z.boolean().default(false),
144
- publishOrder: z.array(z.string()).default([]),
145
- clean: z.boolean().default(false)
146
- });
147
- var GitConfigSchema = z.object({
148
- push: z.boolean().default(true),
149
- pushMethod: z.enum(["auto", "ssh", "https"]).default("auto"),
150
- remote: z.string().default("origin"),
151
- branch: z.string().default("main")
152
- });
153
- var GitHubReleaseConfigSchema = z.object({
154
- enabled: z.boolean().default(true),
155
- draft: z.boolean().default(true),
156
- generateNotes: z.boolean().default(true),
157
- perPackage: z.boolean().default(false),
158
- prerelease: z.union([z.literal("auto"), z.boolean()]).default("auto"),
159
- notesFile: z.string().optional()
160
- });
161
- var VerifyConfigSchema = z.object({
162
- npm: VerifyRegistryConfigSchema.default({}),
163
- cargo: VerifyRegistryConfigSchema.default({
164
- enabled: true,
165
- maxAttempts: 10,
166
- initialDelay: 3e4,
167
- backoffMultiplier: 2
168
- })
169
- });
170
- var PublishConfigSchema = z.object({
171
- npm: NpmConfigSchema.default({}),
172
- cargo: CargoConfigSchema.default({}),
173
- git: GitConfigSchema.default({}),
174
- githubRelease: GitHubReleaseConfigSchema.default({}),
175
- verify: VerifyConfigSchema.default({})
176
- });
177
- function loadConfig(projectDir, configFile) {
178
- const configPath = configFile ?? path.join(projectDir, "publish.config.json");
179
- if (!fs.existsSync(configPath)) {
180
- return getDefaultConfig();
181
- }
182
- try {
183
- const content = fs.readFileSync(configPath, "utf-8");
184
- const raw = JSON.parse(content);
185
- return PublishConfigSchema.parse(raw);
186
- } catch (error) {
187
- if (error instanceof z.ZodError) {
188
- const issues = error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
189
- throw createPublishError("CONFIG_ERROR" /* CONFIG_ERROR */, `Validation errors:
190
- ${issues}`);
191
- }
192
- if (error instanceof SyntaxError) {
193
- throw createPublishError("CONFIG_ERROR" /* CONFIG_ERROR */, `Invalid JSON: ${error.message}`);
194
- }
195
- throw error;
196
- }
197
- }
198
- function getDefaultConfig() {
199
- return PublishConfigSchema.parse({});
200
- }
201
-
202
- // src/utils/exec.ts
203
- import { exec } from "child_process";
204
- import { debug, info } from "@releasekit/core";
205
- async function execCommand(command, options = {}) {
206
- if (options.dryRun) {
207
- info(`[DRY RUN] Would execute: ${options.label ?? command}`);
208
- return { stdout: "", stderr: "", exitCode: 0 };
209
- }
210
- debug(`Executing: ${command}`);
211
- return new Promise((resolve5, reject) => {
212
- exec(
213
- command,
214
- {
215
- maxBuffer: 1024 * 1024 * 10,
216
- cwd: options.cwd,
217
- env: options.env ? { ...process.env, ...options.env } : void 0
218
- },
219
- (error, stdout, stderr) => {
220
- if (error) {
221
- reject(
222
- Object.assign(new Error(error.message), {
223
- stdout: stdout.toString(),
224
- stderr: stderr.toString(),
225
- exitCode: error.code ?? 1
226
- })
227
- );
228
- } else {
229
- resolve5({
230
- stdout: stdout.toString(),
231
- stderr: stderr.toString(),
232
- exitCode: 0
233
- });
234
- }
235
- }
236
- );
237
- });
238
- }
239
- async function execCommandSafe(command, options = {}) {
240
- try {
241
- return await execCommand(command, options);
242
- } catch (error) {
243
- if (error && typeof error === "object" && "stdout" in error) {
244
- const execError = error;
245
- return {
246
- stdout: execError.stdout ?? "",
247
- stderr: execError.stderr ?? "",
248
- exitCode: execError.exitCode ?? 1
249
- };
250
- }
251
- return { stdout: "", stderr: error instanceof Error ? error.message : String(error), exitCode: 1 };
252
- }
253
- }
254
-
255
- // src/utils/auth.ts
256
- function detectNpmAuth() {
257
- if (process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
258
- return "oidc";
259
- }
260
- if (process.env.NPM_TOKEN) {
261
- return "token";
262
- }
263
- return null;
264
- }
265
- function hasCargoAuth() {
266
- return !!process.env.CARGO_REGISTRY_TOKEN;
267
- }
268
- async function detectGitPushMethod(remote, cwd) {
269
- const result = await execCommand(`git remote get-url ${remote}`, { cwd });
270
- const url = result.stdout.trim();
271
- if (url.startsWith("git@") || url.startsWith("ssh://")) {
272
- return "ssh";
273
- }
274
- return "https";
275
- }
276
-
277
- // src/utils/semver.ts
278
- import semver from "semver";
279
- function isPrerelease(version) {
280
- return semver.prerelease(version) !== null;
281
- }
282
- function getDistTag(version, defaultTag = "latest") {
283
- const pre = semver.prerelease(version);
284
- if (pre && pre.length > 0) {
285
- const identifier = pre[0];
286
- return typeof identifier === "string" ? identifier : "next";
287
- }
288
- return defaultTag;
289
- }
290
-
291
- // src/stages/cargo-publish.ts
292
- import * as fs2 from "fs";
293
- import * as path2 from "path";
294
- import * as TOML from "smol-toml";
295
- import { success, warn, debug as debug2 } from "@releasekit/core";
296
- async function runCargoPublishStage(ctx) {
297
- const { input, config, cliOptions, cwd } = ctx;
298
- const dryRun = cliOptions.dryRun;
299
- if (!config.cargo.enabled) {
300
- debug2("Cargo publishing disabled in config");
301
- return;
302
- }
303
- if (!hasCargoAuth() && !dryRun) {
304
- throw createPublishError("CARGO_AUTH_ERROR" /* CARGO_AUTH_ERROR */, "CARGO_REGISTRY_TOKEN not set");
305
- }
306
- const crates = findCrates(input.updates.map((u) => ({ dir: path2.dirname(path2.resolve(cwd, u.filePath)), ...u })), cwd);
307
- if (crates.length === 0) {
308
- debug2("No Cargo crates found to publish");
309
- return;
310
- }
311
- const ordered = orderCrates(crates, config.cargo.publishOrder);
312
- for (const crate of ordered) {
313
- const result = {
314
- packageName: crate.name,
315
- version: crate.version,
316
- registry: "cargo",
317
- success: false,
318
- skipped: false
319
- };
320
- const searchResult = await execCommandSafe(`cargo search ${crate.name} --limit 1`, { cwd, dryRun: false });
321
- if (searchResult.exitCode === 0 && searchResult.stdout.includes(`"${crate.version}"`)) {
322
- result.alreadyPublished = true;
323
- result.skipped = true;
324
- result.success = true;
325
- result.reason = "Already published on crates.io";
326
- ctx.output.cargo.push(result);
327
- warn(`${crate.name}@${crate.version} is already published on crates.io, skipping`);
328
- continue;
329
- }
330
- if (config.cargo.clean) {
331
- await execCommand("cargo clean", { cwd: crate.dir, dryRun, label: `cargo clean (${crate.name})` });
332
- }
333
- const parts = ["cargo publish", `--manifest-path "${crate.manifestPath}"`];
334
- if (config.cargo.noVerify) {
335
- parts.push("--no-verify");
336
- }
337
- const command = parts.join(" ");
338
- try {
339
- await execCommand(command, {
340
- cwd,
341
- dryRun,
342
- label: `cargo publish ${crate.name}@${crate.version}`
343
- });
344
- result.success = true;
345
- if (!dryRun) {
346
- success(`Published ${crate.name}@${crate.version} to crates.io`);
347
- }
348
- } catch (error) {
349
- result.reason = error instanceof Error ? error.message : String(error);
350
- warn(`Failed to publish ${crate.name}: ${result.reason}`);
351
- }
352
- ctx.output.cargo.push(result);
353
- }
354
- }
355
- function findCrates(updates, cwd) {
356
- const crates = [];
357
- for (const update of updates) {
358
- const cargoPath = path2.join(update.dir, "Cargo.toml");
359
- if (!fs2.existsSync(cargoPath)) {
360
- continue;
361
- }
362
- try {
363
- const content = fs2.readFileSync(cargoPath, "utf-8");
364
- const cargo = TOML.parse(content);
365
- const pkg = cargo["package"];
366
- if (!pkg?.["name"]) {
367
- continue;
368
- }
369
- const pathDeps = [];
370
- const deps = cargo["dependencies"];
371
- if (deps) {
372
- for (const dep of Object.values(deps)) {
373
- if (dep && typeof dep === "object" && "path" in dep) {
374
- pathDeps.push(dep.path);
375
- }
376
- }
377
- }
378
- crates.push({
379
- name: pkg["name"],
380
- version: update.newVersion,
381
- dir: update.dir,
382
- manifestPath: cargoPath,
383
- pathDeps
384
- });
385
- } catch {
386
- }
387
- }
388
- return crates;
389
- }
390
- function orderCrates(crates, explicitOrder) {
391
- if (explicitOrder.length > 0) {
392
- const ordered = [];
393
- const byName = new Map(crates.map((c) => [c.name, c]));
394
- for (const name of explicitOrder) {
395
- const crate = byName.get(name);
396
- if (crate) {
397
- ordered.push(crate);
398
- byName.delete(name);
399
- }
400
- }
401
- for (const crate of byName.values()) {
402
- ordered.push(crate);
403
- }
404
- return ordered;
405
- }
406
- return topologicalSort(crates);
407
- }
408
- function topologicalSort(crates) {
409
- const nameSet = new Set(crates.map((c) => c.name));
410
- const graph = /* @__PURE__ */ new Map();
411
- const crateMap = new Map(crates.map((c) => [c.name, c]));
412
- for (const crate of crates) {
413
- graph.set(crate.name, []);
414
- }
415
- for (const crate of crates) {
416
- for (const depPath of crate.pathDeps) {
417
- const resolvedDir = path2.resolve(crate.dir, depPath);
418
- for (const other of crates) {
419
- if (path2.resolve(other.dir) === resolvedDir && nameSet.has(other.name)) {
420
- graph.get(crate.name)?.push(other.name);
421
- }
422
- }
423
- }
424
- }
425
- const inDegree = /* @__PURE__ */ new Map();
426
- for (const name of nameSet) {
427
- inDegree.set(name, 0);
428
- }
429
- for (const deps of graph.values()) {
430
- for (const dep of deps) {
431
- inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
432
- }
433
- }
434
- const queue = [];
435
- for (const [name, degree] of inDegree) {
436
- if (degree === 0) {
437
- queue.push(name);
438
- }
439
- }
440
- const result = [];
441
- while (queue.length > 0) {
442
- const name = queue.shift();
443
- const crate = crateMap.get(name);
444
- if (crate) {
445
- result.push(crate);
446
- }
447
- for (const dep of graph.get(name) ?? []) {
448
- const newDegree = (inDegree.get(dep) ?? 1) - 1;
449
- inDegree.set(dep, newDegree);
450
- if (newDegree === 0) {
451
- queue.push(dep);
452
- }
453
- }
454
- }
455
- result.reverse();
456
- return result;
457
- }
458
-
459
- // src/stages/git-commit.ts
460
- import * as path3 from "path";
461
- import { info as info3, success as success2 } from "@releasekit/core";
462
- async function runGitCommitStage(ctx) {
463
- const { input, cliOptions, cwd } = ctx;
464
- const dryRun = cliOptions.dryRun;
465
- if (!input.commitMessage) {
466
- info3("No commit message provided, skipping git commit");
467
- return;
468
- }
469
- const filePaths = input.updates.map((u) => path3.resolve(cwd, u.filePath));
470
- if (filePaths.length === 0) {
471
- info3("No files to commit");
472
- return;
473
- }
474
- try {
475
- await execCommand(`git add ${filePaths.map((f) => `"${f}"`).join(" ")}`, {
476
- cwd,
477
- dryRun,
478
- label: `git add ${filePaths.length} file(s)`
479
- });
480
- } catch (error) {
481
- throw createPublishError(
482
- "GIT_COMMIT_ERROR" /* GIT_COMMIT_ERROR */,
483
- `git add failed: ${error instanceof Error ? error.message : String(error)}`
484
- );
485
- }
486
- const escapedMessage = input.commitMessage.replace(/"/g, '\\"');
487
- try {
488
- await execCommand(`git commit -m "${escapedMessage}"`, {
489
- cwd,
490
- dryRun,
491
- label: `git commit -m "${input.commitMessage}"`
492
- });
493
- ctx.output.git.committed = true;
494
- if (!dryRun) {
495
- success2("Created git commit");
496
- }
497
- } catch (error) {
498
- throw createPublishError(
499
- "GIT_COMMIT_ERROR" /* GIT_COMMIT_ERROR */,
500
- `git commit failed: ${error instanceof Error ? error.message : String(error)}`
501
- );
502
- }
503
- for (const tag of input.tags) {
504
- try {
505
- const tagMessage = `Release ${tag}`;
506
- const escapedTagMessage = tagMessage.replace(/"/g, '\\"');
507
- await execCommand(`git tag -a "${tag}" -m "${escapedTagMessage}"`, {
508
- cwd,
509
- dryRun,
510
- label: `git tag ${tag}`
511
- });
512
- ctx.output.git.tags.push(tag);
513
- if (!dryRun) {
514
- success2(`Created tag: ${tag}`);
515
- }
516
- } catch (error) {
517
- throw createPublishError(
518
- "GIT_TAG_ERROR" /* GIT_TAG_ERROR */,
519
- `Failed to create tag ${tag}: ${error instanceof Error ? error.message : String(error)}`
520
- );
521
- }
522
- }
523
- }
524
-
525
- // src/stages/git-push.ts
526
- import { info as info4, success as success3 } from "@releasekit/core";
527
- async function runGitPushStage(ctx) {
528
- const { config, cliOptions, cwd, output } = ctx;
529
- const dryRun = cliOptions.dryRun;
530
- if (!config.git.push) {
531
- info4("Git push disabled in config, skipping");
532
- return;
533
- }
534
- if (!output.git.committed && output.git.tags.length === 0) {
535
- info4("Nothing to push (no commits or tags created)");
536
- return;
537
- }
538
- const { remote, branch } = config.git;
539
- let pushMethod = config.git.pushMethod;
540
- if (pushMethod === "auto") {
541
- try {
542
- pushMethod = await detectGitPushMethod(remote, cwd);
543
- } catch {
544
- pushMethod = "https";
545
- }
546
- }
547
- try {
548
- if (output.git.committed) {
549
- await execCommand(`git push ${remote} ${branch}`, {
550
- cwd,
551
- dryRun,
552
- label: `git push ${remote} ${branch}`
553
- });
554
- }
555
- if (output.git.tags.length > 0) {
556
- await execCommand(`git push ${remote} --tags`, {
557
- cwd,
558
- dryRun,
559
- label: `git push ${remote} --tags`
560
- });
561
- }
562
- ctx.output.git.pushed = true;
563
- if (!dryRun) {
564
- success3(`Pushed to ${remote}/${branch}`);
565
- }
566
- } catch (error) {
567
- throw createPublishError(
568
- "GIT_PUSH_ERROR" /* GIT_PUSH_ERROR */,
569
- `${error instanceof Error ? error.message : String(error)}`
570
- );
571
- }
572
- }
573
-
574
- // src/stages/github-release.ts
575
- import * as fs3 from "fs";
576
- import { info as info5, success as success4, warn as warn2, debug as debug3 } from "@releasekit/core";
577
- async function runGithubReleaseStage(ctx) {
578
- const { config, cliOptions, output } = ctx;
579
- const dryRun = cliOptions.dryRun;
580
- if (!config.githubRelease.enabled) {
581
- debug3("GitHub releases disabled in config");
582
- return;
583
- }
584
- const tags = output.git.tags.length > 0 ? output.git.tags : ctx.input.tags;
585
- if (tags.length === 0) {
586
- info5("No tags available for GitHub release");
587
- return;
588
- }
589
- let notesBody;
590
- if (config.githubRelease.notesFile) {
591
- try {
592
- notesBody = fs3.readFileSync(config.githubRelease.notesFile, "utf-8");
593
- } catch {
594
- debug3(`Could not read notes file: ${config.githubRelease.notesFile}`);
595
- }
596
- }
597
- const tagsToRelease = config.githubRelease.perPackage ? tags : [tags[0]];
598
- for (const tag of tagsToRelease) {
599
- const versionMatch = tag.match(/(\d+\.\d+\.\d+.*)$/);
600
- const version = versionMatch?.[1] ?? "";
601
- const isPreRel = config.githubRelease.prerelease === "auto" ? version ? isPrerelease(version) : false : config.githubRelease.prerelease;
602
- const result = {
603
- tag,
604
- draft: config.githubRelease.draft,
605
- prerelease: isPreRel,
606
- success: false
607
- };
608
- const parts = ["gh release create", `"${tag}"`];
609
- if (config.githubRelease.draft) {
610
- parts.push("--draft");
611
- }
612
- if (isPreRel) {
613
- parts.push("--prerelease");
614
- }
615
- if (notesBody) {
616
- const escapedNotes = notesBody.replace(/"/g, '\\"');
617
- parts.push(`--notes "${escapedNotes}"`);
618
- } else if (config.githubRelease.generateNotes) {
619
- parts.push("--generate-notes");
620
- }
621
- const command = parts.join(" ");
622
- try {
623
- const execResult = await execCommand(command, {
624
- dryRun,
625
- label: `gh release create ${tag}`
626
- });
627
- result.success = true;
628
- if (!dryRun && execResult.stdout.trim()) {
629
- result.url = execResult.stdout.trim();
630
- }
631
- if (!dryRun) {
632
- success4(`Created GitHub release for ${tag}`);
633
- }
634
- } catch (error) {
635
- result.reason = error instanceof Error ? error.message : String(error);
636
- warn2(`Failed to create GitHub release for ${tag}: ${result.reason}`);
637
- }
638
- ctx.output.githubReleases.push(result);
639
- }
640
- }
641
-
642
- // src/stages/npm-publish.ts
643
- import * as fs4 from "fs";
644
- import * as path4 from "path";
645
- import { info as info6, success as success5, warn as warn3, debug as debug4 } from "@releasekit/core";
646
- async function runNpmPublishStage(ctx) {
647
- const { input, config, cliOptions, cwd } = ctx;
648
- const dryRun = cliOptions.dryRun;
649
- if (!config.npm.enabled) {
650
- info6("NPM publishing disabled in config");
651
- return;
652
- }
653
- const authMethod = config.npm.auth === "auto" ? detectNpmAuth() : config.npm.auth;
654
- if (!authMethod && !dryRun) {
655
- throw createPublishError("NPM_AUTH_ERROR" /* NPM_AUTH_ERROR */, "No NPM authentication method detected");
656
- }
657
- const useProvenance = config.npm.provenance && authMethod === "oidc";
658
- for (const update of input.updates) {
659
- const result = {
660
- packageName: update.packageName,
661
- version: update.newVersion,
662
- registry: "npm",
663
- success: false,
664
- skipped: false
665
- };
666
- const pkgJsonPath = path4.resolve(cwd, update.filePath);
667
- try {
668
- const pkgContent = fs4.readFileSync(pkgJsonPath, "utf-8");
669
- const pkgJson = JSON.parse(pkgContent);
670
- if (pkgJson.private) {
671
- result.skipped = true;
672
- result.success = true;
673
- result.reason = "Package is private";
674
- ctx.output.npm.push(result);
675
- debug4(`Skipping private package: ${update.packageName}`);
676
- continue;
677
- }
678
- } catch {
679
- if (update.filePath.endsWith("Cargo.toml")) {
680
- result.skipped = true;
681
- result.success = true;
682
- result.reason = "Not an npm package";
683
- ctx.output.npm.push(result);
684
- continue;
685
- }
686
- }
687
- const viewResult = await execCommandSafe(`npm view ${update.packageName}@${update.newVersion} version --json`, {
688
- cwd,
689
- dryRun: false
690
- // Always check, even in dry-run
691
- });
692
- if (viewResult.exitCode === 0 && viewResult.stdout.trim()) {
693
- result.alreadyPublished = true;
694
- result.skipped = true;
695
- result.success = true;
696
- result.reason = "Already published";
697
- ctx.output.npm.push(result);
698
- warn3(`${update.packageName}@${update.newVersion} is already published, skipping`);
699
- continue;
700
- }
701
- const distTag = getDistTag(update.newVersion, config.npm.tag);
702
- const parts = ["pnpm publish", `--filter ${update.packageName}`, "--no-git-checks", `--access ${config.npm.access}`, `--tag ${distTag}`];
703
- if (useProvenance) {
704
- parts.push("--provenance");
705
- }
706
- const command = parts.join(" ");
707
- try {
708
- await execCommand(command, {
709
- cwd,
710
- dryRun,
711
- label: `npm publish ${update.packageName}@${update.newVersion}`
712
- });
713
- result.success = true;
714
- if (!dryRun) {
715
- success5(`Published ${update.packageName}@${update.newVersion} to npm`);
716
- }
717
- } catch (error) {
718
- result.reason = error instanceof Error ? error.message : String(error);
719
- warn3(`Failed to publish ${update.packageName}: ${result.reason}`);
720
- }
721
- ctx.output.npm.push(result);
722
- }
723
- }
724
-
725
- // src/stages/prepare.ts
726
- import * as fs5 from "fs";
727
- import * as path5 from "path";
728
- import { debug as debug5, info as info7 } from "@releasekit/core";
729
- import * as TOML2 from "smol-toml";
730
- async function runPrepareStage(ctx) {
731
- const { input, config, cliOptions, cwd } = ctx;
732
- if (config.npm.enabled && config.npm.copyFiles.length > 0) {
733
- for (const update of input.updates) {
734
- const pkgDir = path5.dirname(path5.resolve(cwd, update.filePath));
735
- for (const file of config.npm.copyFiles) {
736
- const src = path5.resolve(cwd, file);
737
- const dest = path5.join(pkgDir, file);
738
- if (!fs5.existsSync(src)) {
739
- debug5(`Source file not found, skipping copy: ${src}`);
740
- continue;
741
- }
742
- if (path5.resolve(path5.dirname(src)) === path5.resolve(pkgDir)) {
743
- debug5(`Skipping copy of ${file} - same directory as source`);
744
- continue;
745
- }
746
- if (cliOptions.dryRun) {
747
- info7(`[DRY RUN] Would copy ${src} \u2192 ${dest}`);
748
- continue;
749
- }
750
- try {
751
- fs5.copyFileSync(src, dest);
752
- debug5(`Copied ${file} \u2192 ${pkgDir}`);
753
- } catch (error) {
754
- throw createPublishError(
755
- "FILE_COPY_ERROR" /* FILE_COPY_ERROR */,
756
- `Failed to copy ${src} to ${dest}: ${error instanceof Error ? error.message : String(error)}`
757
- );
758
- }
759
- }
760
- }
761
- }
762
- if (config.cargo.enabled) {
763
- for (const update of input.updates) {
764
- const pkgDir = path5.dirname(path5.resolve(cwd, update.filePath));
765
- const cargoPath = path5.join(pkgDir, "Cargo.toml");
766
- if (!fs5.existsSync(cargoPath)) {
767
- continue;
768
- }
769
- if (cliOptions.dryRun) {
770
- info7(`[DRY RUN] Would update ${cargoPath} to version ${update.newVersion}`);
771
- continue;
772
- }
773
- try {
774
- const content = fs5.readFileSync(cargoPath, "utf-8");
775
- const cargo = TOML2.parse(content);
776
- const pkg = cargo["package"];
777
- if (pkg) {
778
- pkg["version"] = update.newVersion;
779
- fs5.writeFileSync(cargoPath, TOML2.stringify(cargo));
780
- debug5(`Updated ${cargoPath} to version ${update.newVersion}`);
781
- }
782
- } catch (error) {
783
- throw createPublishError(
784
- "CARGO_TOML_ERROR" /* CARGO_TOML_ERROR */,
785
- `${cargoPath}: ${error instanceof Error ? error.message : String(error)}`
786
- );
787
- }
788
- }
789
- }
790
- }
791
-
792
- // src/stages/verify.ts
793
- import { info as info8, success as success6, warn as warn4, debug as debug7 } from "@releasekit/core";
794
-
795
- // src/utils/retry.ts
796
- import { debug as debug6 } from "@releasekit/core";
797
- async function withRetry(fn, options, shouldRetry) {
798
- let lastError;
799
- let delay = options.initialDelay;
800
- for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
801
- try {
802
- return await fn();
803
- } catch (error) {
804
- lastError = error;
805
- if (shouldRetry && !shouldRetry(error)) {
806
- throw error;
807
- }
808
- if (attempt < options.maxAttempts) {
809
- debug6(`Attempt ${attempt}/${options.maxAttempts} failed, retrying in ${delay}ms...`);
810
- await sleep(delay);
811
- delay = Math.floor(delay * options.backoffMultiplier);
812
- }
813
- }
814
- }
815
- throw lastError;
816
- }
817
- function sleep(ms) {
818
- return new Promise((resolve5) => setTimeout(resolve5, ms));
819
- }
820
-
821
- // src/stages/verify.ts
822
- async function runVerifyStage(ctx) {
823
- const { config, cliOptions, output, cwd } = ctx;
824
- if (config.verify.npm.enabled) {
825
- const published = output.npm.filter((r) => r.success && !r.skipped && !r.alreadyPublished);
826
- for (const pkg of published) {
827
- const result = {
828
- packageName: pkg.packageName,
829
- version: pkg.version,
830
- registry: "npm",
831
- verified: false,
832
- attempts: 0
833
- };
834
- if (cliOptions.dryRun) {
835
- info8(`[DRY RUN] Would verify ${pkg.packageName}@${pkg.version} on npm`);
836
- result.verified = true;
837
- ctx.output.verification.push(result);
838
- continue;
839
- }
840
- try {
841
- await withRetry(
842
- async () => {
843
- result.attempts++;
844
- const viewResult = await execCommandSafe(
845
- `pnpm view ${pkg.packageName}@${pkg.version} version --json`,
846
- { cwd, dryRun: false }
847
- );
848
- if (viewResult.exitCode !== 0 || !viewResult.stdout.trim()) {
849
- throw new Error(`${pkg.packageName}@${pkg.version} not yet available on npm`);
850
- }
851
- debug7(`Verified ${pkg.packageName}@${pkg.version} on npm`);
852
- },
853
- config.verify.npm
854
- );
855
- result.verified = true;
856
- success6(`Verified ${pkg.packageName}@${pkg.version} on npm`);
857
- } catch {
858
- warn4(`Failed to verify ${pkg.packageName}@${pkg.version} on npm after ${result.attempts} attempts`);
859
- }
860
- ctx.output.verification.push(result);
861
- }
862
- }
863
- if (config.verify.cargo.enabled) {
864
- const published = output.cargo.filter((r) => r.success && !r.skipped && !r.alreadyPublished);
865
- for (const crate of published) {
866
- const result = {
867
- packageName: crate.packageName,
868
- version: crate.version,
869
- registry: "cargo",
870
- verified: false,
871
- attempts: 0
872
- };
873
- if (cliOptions.dryRun) {
874
- info8(`[DRY RUN] Would verify ${crate.packageName}@${crate.version} on crates.io`);
875
- result.verified = true;
876
- ctx.output.verification.push(result);
877
- continue;
878
- }
879
- try {
880
- await withRetry(
881
- async () => {
882
- result.attempts++;
883
- const response = await fetch(
884
- `https://crates.io/api/v1/crates/${crate.packageName}/${crate.version}`
885
- );
886
- if (!response.ok) {
887
- throw new Error(`${crate.packageName}@${crate.version} not yet available on crates.io`);
888
- }
889
- debug7(`Verified ${crate.packageName}@${crate.version} on crates.io`);
890
- },
891
- config.verify.cargo
892
- );
893
- result.verified = true;
894
- success6(`Verified ${crate.packageName}@${crate.version} on crates.io`);
895
- } catch {
896
- warn4(`Failed to verify ${crate.packageName}@${crate.version} on crates.io after ${result.attempts} attempts`);
897
- }
898
- ctx.output.verification.push(result);
899
- }
900
- }
901
- }
902
-
903
- // src/pipeline/index.ts
904
- async function runPipeline(input, config, options) {
905
- const ctx = {
906
- input,
907
- config,
908
- cliOptions: options,
909
- cwd: process.cwd(),
910
- output: {
911
- dryRun: options.dryRun,
912
- git: { committed: false, tags: [], pushed: false },
913
- npm: [],
914
- cargo: [],
915
- verification: [],
916
- githubReleases: []
917
- }
918
- };
919
- await runPrepareStage(ctx);
920
- if (!options.skipGit) {
921
- await runGitCommitStage(ctx);
922
- }
923
- if (!options.skipPublish) {
924
- if (options.registry === "all" || options.registry === "npm") {
925
- await runNpmPublishStage(ctx);
926
- }
927
- if (options.registry === "all" || options.registry === "cargo") {
928
- await runCargoPublishStage(ctx);
929
- }
930
- }
931
- if (!options.skipVerification && !options.skipPublish) {
932
- await runVerifyStage(ctx);
933
- }
934
- if (!options.skipGit) {
935
- await runGitPushStage(ctx);
936
- }
937
- if (!options.skipGithubRelease) {
938
- await runGithubReleaseStage(ctx);
939
- }
940
- return ctx.output;
941
- }
942
-
943
- // src/stages/input.ts
944
- import * as fs6 from "fs";
945
- import { z as z2 } from "zod";
946
- var VersionChangelogEntrySchema = z2.object({
947
- type: z2.string(),
948
- description: z2.string(),
949
- issueIds: z2.array(z2.string()).optional(),
950
- scope: z2.string().optional(),
951
- originalType: z2.string().optional()
952
- });
953
- var VersionPackageChangelogSchema = z2.object({
954
- packageName: z2.string(),
955
- version: z2.string(),
956
- previousVersion: z2.string().nullable(),
957
- revisionRange: z2.string(),
958
- repoUrl: z2.string().nullable(),
959
- entries: z2.array(VersionChangelogEntrySchema)
960
- });
961
- var VersionPackageUpdateSchema = z2.object({
962
- packageName: z2.string(),
963
- newVersion: z2.string(),
964
- filePath: z2.string()
965
- });
966
- var VersionOutputSchema = z2.object({
967
- dryRun: z2.boolean(),
968
- updates: z2.array(VersionPackageUpdateSchema),
969
- changelogs: z2.array(VersionPackageChangelogSchema),
970
- commitMessage: z2.string().optional(),
971
- tags: z2.array(z2.string())
972
- });
973
- async function parseInput(inputPath) {
974
- let raw;
975
- if (inputPath) {
976
- try {
977
- raw = fs6.readFileSync(inputPath, "utf-8");
978
- } catch {
979
- throw createPublishError("INPUT_PARSE_ERROR" /* INPUT_PARSE_ERROR */, `Could not read file: ${inputPath}`);
980
- }
981
- } else {
982
- raw = await readStdin();
983
- }
984
- let parsed;
985
- try {
986
- parsed = JSON.parse(raw);
987
- } catch {
988
- throw createPublishError("INPUT_PARSE_ERROR" /* INPUT_PARSE_ERROR */, "Input is not valid JSON");
989
- }
990
- const result = VersionOutputSchema.safeParse(parsed);
991
- if (!result.success) {
992
- const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
993
- throw createPublishError("INPUT_VALIDATION_ERROR" /* INPUT_VALIDATION_ERROR */, `Schema validation failed:
994
- ${issues}`);
995
- }
996
- return result.data;
997
- }
998
- async function readStdin() {
999
- const chunks = [];
1000
- for await (const chunk of process.stdin) {
1001
- chunks.push(chunk);
1002
- }
1003
- return chunks.join("");
1004
- }
1005
-
1006
- export {
1007
- BasePublishError,
1008
- PublishError,
1009
- PublishErrorCode,
1010
- createPublishError,
1011
- loadConfig,
1012
- getDefaultConfig,
1013
- detectNpmAuth,
1014
- hasCargoAuth,
1015
- isPrerelease,
1016
- getDistTag,
1017
- runPipeline,
1018
- parseInput
1019
- };