@williamthorsen/release-kit 2.1.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +1 -11
  3. package/bin/release-kit.js +2 -0
  4. package/dist/esm/.cache +1 -1
  5. package/dist/esm/bin/release-kit.js +148 -0
  6. package/dist/esm/bumpAllVersions.d.ts +2 -2
  7. package/dist/esm/bumpAllVersions.js +1 -4
  8. package/dist/esm/component.d.ts +1 -1
  9. package/dist/esm/component.js +2 -3
  10. package/dist/esm/createTags.d.ts +5 -0
  11. package/dist/esm/createTags.js +73 -0
  12. package/dist/esm/detectPackageManager.d.ts +2 -0
  13. package/dist/esm/detectPackageManager.js +44 -0
  14. package/dist/esm/findPackageRoot.d.ts +1 -0
  15. package/dist/esm/findPackageRoot.js +17 -0
  16. package/dist/esm/format.d.ts +3 -0
  17. package/dist/esm/format.js +14 -0
  18. package/dist/esm/generateChangelogs.d.ts +2 -2
  19. package/dist/esm/generateChangelogs.js +12 -12
  20. package/dist/esm/hasPrettierConfig.d.ts +1 -0
  21. package/dist/esm/hasPrettierConfig.js +42 -0
  22. package/dist/esm/index.d.ts +12 -2
  23. package/dist/esm/index.js +11 -2
  24. package/dist/esm/init/initCommand.js +9 -2
  25. package/dist/esm/init/scaffold.d.ts +3 -2
  26. package/dist/esm/init/scaffold.js +18 -44
  27. package/dist/esm/init/templates.js +14 -4
  28. package/dist/esm/loadConfig.js +0 -6
  29. package/dist/esm/prepareCommand.d.ts +10 -0
  30. package/dist/esm/prepareCommand.js +96 -20
  31. package/dist/esm/publish.d.ts +7 -0
  32. package/dist/esm/publish.js +49 -0
  33. package/dist/esm/publishCommand.d.ts +1 -0
  34. package/dist/esm/publishCommand.js +54 -0
  35. package/dist/esm/releasePrepare.d.ts +2 -2
  36. package/dist/esm/releasePrepare.js +46 -17
  37. package/dist/esm/releasePrepareMono.d.ts +2 -2
  38. package/dist/esm/releasePrepareMono.js +54 -24
  39. package/dist/esm/reportPrepare.d.ts +2 -0
  40. package/dist/esm/reportPrepare.js +120 -0
  41. package/dist/esm/resolveCliffConfigPath.js +4 -4
  42. package/dist/esm/resolveReleaseTags.d.ts +6 -0
  43. package/dist/esm/resolveReleaseTags.js +42 -0
  44. package/dist/esm/runReleasePrepare.js +3 -2
  45. package/dist/esm/sync-labels/generateCommand.d.ts +4 -0
  46. package/dist/esm/sync-labels/generateCommand.js +51 -0
  47. package/dist/esm/sync-labels/initCommand.d.ts +6 -0
  48. package/dist/esm/sync-labels/initCommand.js +62 -0
  49. package/dist/esm/sync-labels/loadSyncLabelsConfig.d.ts +3 -0
  50. package/dist/esm/sync-labels/loadSyncLabelsConfig.js +59 -0
  51. package/dist/esm/sync-labels/presets.d.ts +2 -0
  52. package/dist/esm/sync-labels/presets.js +40 -0
  53. package/dist/esm/sync-labels/resolveLabels.d.ts +2 -0
  54. package/dist/esm/sync-labels/resolveLabels.js +43 -0
  55. package/dist/esm/sync-labels/scaffold.d.ts +6 -0
  56. package/dist/esm/sync-labels/scaffold.js +25 -0
  57. package/dist/esm/sync-labels/syncCommand.d.ts +1 -0
  58. package/dist/esm/sync-labels/syncCommand.js +33 -0
  59. package/dist/esm/sync-labels/templates.d.ts +4 -0
  60. package/dist/esm/sync-labels/templates.js +50 -0
  61. package/dist/esm/sync-labels/types.d.ts +9 -0
  62. package/dist/esm/sync-labels/types.js +0 -0
  63. package/dist/esm/tagCommand.d.ts +1 -0
  64. package/dist/esm/tagCommand.js +20 -0
  65. package/dist/esm/types.d.ts +29 -1
  66. package/dist/esm/validateConfig.js +11 -6
  67. package/package.json +12 -5
  68. package/presets/labels/common.yaml +52 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,116 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [release-kit-v2.3.0] - 2026-03-28
6
+
7
+ ### Features
8
+
9
+ - #8 feat: Add shared writeFileWithCheck utility and overwrite reporting (#66)
10
+
11
+ Extracts three duplicated `writeIfAbsent` implementations and two duplicated terminal helper sets into shared utilities in `@williamthorsen/node-monorepo-core`, then migrates all consumers (`release-kit init`, `preflight init`, `sync-labels`) to use them. All init commands now report which files were created, overwritten, skipped, or failed — including when `--force` replaces existing files.
12
+
13
+ - #11 release-kit|feat: Separate tag-write errors from release preparation errors (#67)
14
+
15
+ When tag-file writing fails, the error message now reads "Error writing release tags:" instead of the misleading "Error preparing release:", which only appeared because both operations shared a single try/catch.
16
+
17
+ Refactors `writeReleaseTags` to use the shared `writeFileWithCheck` utility from `@node-monorepo-tools/core` instead of raw `mkdirSync`/`writeFileSync`. The function now returns a structured `WriteResult` instead of throwing, and contains no `console` calls — all presentation moves to `runAndReport`.
18
+
19
+ ### Tests
20
+
21
+ - #14 release-kit|tests: Add eligibility check failure and short-circuit tests (#63)
22
+
23
+ Adds 4 unit tests to `initCommand.unit.test.ts` covering the remaining `checkEligibility` orchestration gaps: individual failure exit codes for `hasPackageJson` and `usesPnpm`, and short-circuit verification ensuring downstream checks are skipped when an earlier check fails.
24
+
25
+ - #13 release-kit|tests: Add cliff.toml.template alignment test (#64)
26
+
27
+ Adds a unit test that enforces bidirectional alignment between `DEFAULT_WORK_TYPES` and the bundled `cliff.toml.template` commit parsers. The test parses the TOML template using `smol-toml`, then verifies that every canonical type name and alias is matched by a parser with the correct group heading, and that every parser group maps to a known work type header.
28
+
29
+ - #12 release-kit|tests: Add releasePrepare coverage for bumpOverride, tagPrefix, and dry-run tags (#65)
30
+
31
+ Adds three unit tests to `releasePrepare.unit.test.ts` covering previously untested code paths: the `bumpOverride` bypass of commit-based bump detection, custom `tagPrefix` propagation into tags, and tag computation in dry-run mode.
32
+
33
+ ## [release-kit-v2.2.0] - 2026-03-27
34
+
35
+ ### Features
36
+
37
+ - #27 release-kit|feat: Auto-detect Prettier for CHANGELOG formatting (#36)
38
+
39
+ When `formatCommand` is not configured, release-kit now auto-detects whether the repo uses Prettier by checking for config files (`.prettierrc*`, `prettier.config.*`) or a `"prettier"` key in root `package.json`. If found, it defaults to `npx prettier --write` on generated files. If not found, formatting is skipped.
40
+
41
+ - #28 release-kit|feat: Add tag-creation command (#40)
42
+
43
+ Adds a `release-kit tag` CLI command that reads computed tag names from the `tmp/.release-tags` file produced by `prepare` and creates annotated git tags. The command supports `--dry-run` (preview without creating tags) and `--no-git-checks` (skip dirty working tree validation). The `createTags` function and its options type are exported for programmatic use.
44
+
45
+ - #29 release-kit|feat: Add publish command (#42)
46
+
47
+ Adds a `release-kit publish` subcommand that derives packages to publish from git tags on HEAD and delegates to the repo's detected package manager. Also cleans up the `.release-tags` file after tag creation.
48
+
49
+ - #41 release-kit|feat: Remove tagPrefix customization from component config (#49)
50
+
51
+ Removes the ability to customize `tagPrefix` per component, enforcing the deterministic `{dir}-v` convention universally. The internal `tagPrefix` property on `ComponentConfig` and `ReleaseConfig` is preserved — only the override/customization entry points are removed. Existing configs that still include `tagPrefix` now receive a clear deprecation error.
52
+
53
+ - #54 release-kit|feat: Add styled terminal output to prepare command (#55)
54
+
55
+ Adds ANSI formatting and emoji markers to the `release-kit prepare` command output. Progress chatter is dimmed, key results (version bumps, release tags, completion status) are highlighted with bold text and emoji, and monorepo components are separated by box-drawing section headers.
56
+
57
+ - #59 feat: Extract nmr CLI from core package (#61)
58
+
59
+ Extracts all nmr CLI code from `packages/core` into a new `packages/nmr` package (`@williamthorsen/nmr`). Core is reduced to an empty shared-library shell ready for cross-cutting utilities. All internal references are rewired and the full build/test pipeline passes.
60
+
61
+ Scopes: core, nmr
62
+
63
+ ### Refactoring
64
+
65
+ - #43 refactor: Replace dist bin targets with thin wrapper scripts (#48)
66
+
67
+ The `bin` entries in `packages/core` and `packages/release-kit` pointed directly into `dist/esm/`, causing `pnpm install` to emit "Failed to create bin" warnings in fresh worktrees where `dist/` does not yet exist. Each bin entry now points to a committed wrapper script in `bin/` that dynamically imports the real entry point. The `files` field in both packages includes `bin` so the wrappers are published.
68
+
69
+ - #53 release-kit|refactor: Separate presentation from logic in prepare workflow (#57)
70
+
71
+ Extracts all `console.info` calls from the prepare workflow's logic functions (`bumpAllVersions`, `generateChangelogs`, `releasePrepare`, `releasePrepareMono`) into a dedicated `reportPrepare` formatter. Logic functions now return structured result types (`BumpResult`, `ComponentPrepareResult`, `PrepareResult`). The legacy `runReleasePrepare` entry point is retired, with its utilities absorbed into `prepareCommand`.
72
+
73
+ ### Tests
74
+
75
+ - #17 release-kit|tests: Cover multi-changelogPaths and error paths (#44)
76
+
77
+ Add three tests for previously untested code paths:
78
+
79
+ - `releasePrepareMono`: component with two `changelogPaths` entries, asserting `git-cliff` is invoked once per path with the correct `--output` target.
80
+ - `getCommitsSinceTarget`: `git describe` failure with a non-128 exit status propagates as a wrapped error instead of being swallowed.
81
+ - `getCommitsSinceTarget`: `git log` failure is wrapped and re-thrown with the commit range in the message.
82
+
83
+ Also adds a `findAllCliffOutputPaths()` test helper that collects the `--output` arg from every `git-cliff` mock call.
84
+
85
+ ### Tooling
86
+
87
+ - #37 root|tooling: Adopt nmr to run monorepo and workspace scripts (#38)
88
+
89
+ Replaces the legacy workspace script runner and ~25 root `package.json` scripts with `nmr`, the monorepo's own context-aware script runner. Root scripts are reduced to 4 (`prepare`, `postinstall`, `ci`, `bootstrap`), packages use direct build commands for bootstrap, and release-kit declares tier-3 test overrides for its integration test configs.
90
+
91
+ ## [release-workflow-v1] - 2026-03-19
92
+
93
+ ### Dependencies
94
+
95
+ - Root|deps: Add release-kit as root devDependency
96
+
97
+ Make `npx release-kit` and `pnpm exec release-kit` resolve within
98
+ this repo by adding a `workspace:*` dependency that symlinks the bin.
99
+
100
+ ### Features
101
+
102
+ - #23 release-kit|feat: Add sync-labels command (#33)
103
+
104
+ Add a `release-kit sync-labels` command group with three subcommands (`init`, `generate`, `sync`) for declarative GitHub label management in monorepos. Bundle a reusable GitHub Actions workflow and composable label presets with the release-kit package. Introduce a `findPackageRoot` utility to replace fragile hardcoded path resolutions across the codebase.
105
+
106
+ - #34 release-kit|feat: Report up-to-date status for unchanged init files (#35)
107
+
108
+ `release-kit init` now compares existing file content against the default before reporting status. When an existing file is identical to the default (after normalizing trailing whitespace), it reports `✅ (up to date)` instead of the misleading `⚠️ (already exists)`.
109
+
110
+ - Release-workflow|feat: Accept force input
111
+
112
+ Pass `--force` to the prepare command so callers can force a version
113
+ bump even when there are no release-worthy changes.
114
+
5
115
  ## [release-kit-v2.1.0] - 2026-03-17
6
116
 
7
117
  ### Features
package/README.md CHANGED
@@ -48,12 +48,6 @@ Options:
48
48
 
49
49
  Component names for `--only` match the package directory name (e.g., `arrays`, `release-kit`).
50
50
 
51
- **Self-hosting note**: in a repo where `release-kit` is a workspace package (e.g., this monorepo), `npx` and `pnpm exec` may not resolve the binary. Run the built entry point directly:
52
-
53
- ```bash
54
- node packages/release-kit/dist/esm/bin/release-kit.js prepare --dry-run
55
- ```
56
-
57
51
  ### `release-kit init`
58
52
 
59
53
  Initialize release-kit in the current repository. By default, scaffolds only the GitHub Actions workflow file. Use `--with-config` to also scaffold configuration files.
@@ -120,7 +114,6 @@ All fields are optional.
120
114
  ```typescript
121
115
  interface ComponentOverride {
122
116
  dir: string; // Package directory name (e.g., 'arrays')
123
- tagPrefix?: string; // Custom git tag prefix (defaults to '${dir}-v')
124
117
  shouldExclude?: boolean; // If true, exclude from release processing
125
118
  }
126
119
  ```
@@ -184,12 +177,9 @@ component('packages/arrays');
184
177
  // changelogPaths: ['packages/arrays'],
185
178
  // paths: ['packages/arrays/**'],
186
179
  // }
187
-
188
- // Custom tag prefix
189
- component('libs/core', 'core-v');
190
180
  ```
191
181
 
192
- The `dir` field is derived from `path.basename()`, so `packages/arrays` and `libs/arrays` both produce `dir: 'arrays'`.
182
+ The `dir` field is derived from `path.basename()`, so `packages/arrays` and `libs/arrays` both produce `dir: 'arrays'`. The `tagPrefix` is always `${dir}-v` — it cannot be customized.
193
183
 
194
184
  ## GitHub Actions workflow
195
185
 
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/esm/bin/release-kit.js');
package/dist/esm/.cache CHANGED
@@ -1 +1 @@
1
- c3020d624e72bb577d60cb16ff223c951d26227fa82aec1c15894f2b2429603f
1
+ af9641831236630f28f7bf1301d1dc1319b90769e7e70ff6664f80af452c1a72
@@ -1,19 +1,75 @@
1
1
  #!/usr/bin/env node
2
2
  import { initCommand } from "../init/initCommand.js";
3
3
  import { prepareCommand } from "../prepareCommand.js";
4
+ import { publishCommand } from "../publishCommand.js";
5
+ import { generateCommand } from "../sync-labels/generateCommand.js";
6
+ import { syncLabelsInitCommand } from "../sync-labels/initCommand.js";
7
+ import { syncLabelsCommand } from "../sync-labels/syncCommand.js";
8
+ import { tagCommand } from "../tagCommand.js";
4
9
  function showUsage() {
5
10
  console.info(`
6
11
  Usage: release-kit <command> [options]
7
12
 
8
13
  Commands:
9
14
  prepare Run release preparation (auto-discovers workspaces)
15
+ tag Create annotated git tags from the tags file
16
+ publish Publish packages with release tags on HEAD
10
17
  init Initialize release-kit in the current repository
18
+ sync-labels Manage GitHub label synchronization
11
19
 
12
20
  Options:
13
21
  --dry-run Preview changes without writing files
14
22
  --help, -h Show this help message
15
23
  `);
16
24
  }
25
+ function showSyncLabelsHelp() {
26
+ console.info(`
27
+ Usage: release-kit sync-labels <subcommand> [options]
28
+
29
+ Manage GitHub label synchronization via preset and custom label definitions.
30
+
31
+ Subcommands:
32
+ init Scaffold caller workflow and config, then generate labels
33
+ generate Regenerate .github/labels.yaml from config
34
+ sync Trigger the sync-labels workflow via gh CLI
35
+
36
+ Options:
37
+ --help, -h Show this help message
38
+ `);
39
+ }
40
+ function showSyncLabelsInitHelp() {
41
+ console.info(`
42
+ Usage: release-kit sync-labels init [options]
43
+
44
+ Scaffold the sync-labels caller workflow and config file, auto-discover workspaces
45
+ for scope labels, then generate .github/labels.yaml.
46
+
47
+ Options:
48
+ --dry-run Preview changes without writing files
49
+ --force Overwrite existing files instead of skipping them
50
+ --help, -h Show this help message
51
+ `);
52
+ }
53
+ function showSyncLabelsGenerateHelp() {
54
+ console.info(`
55
+ Usage: release-kit sync-labels generate
56
+
57
+ Regenerate .github/labels.yaml from .config/sync-labels.config.ts.
58
+
59
+ Options:
60
+ --help, -h Show this help message
61
+ `);
62
+ }
63
+ function showSyncLabelsSyncHelp() {
64
+ console.info(`
65
+ Usage: release-kit sync-labels sync
66
+
67
+ Trigger the sync-labels GitHub Actions workflow via the gh CLI.
68
+
69
+ Options:
70
+ --help, -h Show this help message
71
+ `);
72
+ }
17
73
  function showInitHelp() {
18
74
  console.info(`
19
75
  Usage: release-kit init [options]
@@ -41,6 +97,31 @@ Options:
41
97
  --help, -h Show this help message
42
98
  `);
43
99
  }
100
+ function showTagHelp() {
101
+ console.info(`
102
+ Usage: release-kit tag [options]
103
+
104
+ Create annotated git tags from the tags file produced by \`prepare\`.
105
+
106
+ Options:
107
+ --dry-run Preview without creating tags
108
+ --no-git-checks Skip dirty working tree check
109
+ --help, -h Show this help message
110
+ `);
111
+ }
112
+ function showPublishHelp() {
113
+ console.info(`
114
+ Usage: release-kit publish [options]
115
+
116
+ Publish packages that have release tags on HEAD.
117
+
118
+ Options:
119
+ --dry-run Preview without publishing
120
+ --no-git-checks Skip git checks (pnpm only)
121
+ --only=name1,name2 Only publish the named packages (comma-separated, monorepo only)
122
+ --help, -h Show this help message
123
+ `);
124
+ }
44
125
  const args = process.argv.slice(2);
45
126
  const command = args[0];
46
127
  const flags = args.slice(1);
@@ -56,6 +137,22 @@ if (command === "prepare") {
56
137
  await prepareCommand(flags);
57
138
  process.exit(0);
58
139
  }
140
+ if (command === "tag") {
141
+ if (flags.some((f) => f === "--help" || f === "-h")) {
142
+ showTagHelp();
143
+ process.exit(0);
144
+ }
145
+ tagCommand(flags);
146
+ process.exit(0);
147
+ }
148
+ if (command === "publish") {
149
+ if (flags.some((f) => f === "--help" || f === "-h")) {
150
+ showPublishHelp();
151
+ process.exit(0);
152
+ }
153
+ await publishCommand(flags);
154
+ process.exit(0);
155
+ }
59
156
  if (command === "init") {
60
157
  if (flags.some((f) => f === "--help" || f === "-h")) {
61
158
  showInitHelp();
@@ -73,6 +170,57 @@ if (command === "init") {
73
170
  const exitCode = initCommand({ dryRun, force, withConfig });
74
171
  process.exit(exitCode);
75
172
  }
173
+ if (command === "sync-labels") {
174
+ const subcommand = flags[0];
175
+ const subflags = flags.slice(1);
176
+ if (subcommand === "--help" || subcommand === "-h" || subcommand === void 0) {
177
+ showSyncLabelsHelp();
178
+ process.exit(0);
179
+ }
180
+ if (subcommand === "init") {
181
+ if (subflags.some((f) => f === "--help" || f === "-h")) {
182
+ showSyncLabelsInitHelp();
183
+ process.exit(0);
184
+ }
185
+ const knownFlags = /* @__PURE__ */ new Set(["--dry-run", "--force", "--help", "-h"]);
186
+ const unknownFlags = subflags.filter((f) => !knownFlags.has(f));
187
+ if (unknownFlags.length > 0) {
188
+ console.error(`Error: Unknown option: ${unknownFlags[0]}`);
189
+ process.exit(1);
190
+ }
191
+ const dryRun = subflags.includes("--dry-run");
192
+ const force = subflags.includes("--force");
193
+ const exitCode = await syncLabelsInitCommand({ dryRun, force });
194
+ process.exit(exitCode);
195
+ }
196
+ if (subcommand === "generate") {
197
+ if (subflags.some((f) => f === "--help" || f === "-h")) {
198
+ showSyncLabelsGenerateHelp();
199
+ process.exit(0);
200
+ }
201
+ if (subflags.length > 0) {
202
+ console.error(`Error: Unknown option: ${subflags[0]}`);
203
+ process.exit(1);
204
+ }
205
+ const exitCode = await generateCommand();
206
+ process.exit(exitCode);
207
+ }
208
+ if (subcommand === "sync") {
209
+ if (subflags.some((f) => f === "--help" || f === "-h")) {
210
+ showSyncLabelsSyncHelp();
211
+ process.exit(0);
212
+ }
213
+ if (subflags.length > 0) {
214
+ console.error(`Error: Unknown option: ${subflags[0]}`);
215
+ process.exit(1);
216
+ }
217
+ const exitCode = syncLabelsCommand();
218
+ process.exit(exitCode);
219
+ }
220
+ console.error(`Error: Unknown subcommand: ${subcommand}`);
221
+ showSyncLabelsHelp();
222
+ process.exit(1);
223
+ }
76
224
  console.error(`Error: Unknown command: ${command}`);
77
225
  showUsage();
78
226
  process.exit(1);
@@ -1,2 +1,2 @@
1
- import type { ReleaseType } from './types.ts';
2
- export declare function bumpAllVersions(packageFiles: readonly string[], releaseType: ReleaseType, dryRun: boolean): string;
1
+ import type { BumpResult, ReleaseType } from './types.ts';
2
+ export declare function bumpAllVersions(packageFiles: readonly string[], releaseType: ReleaseType, dryRun: boolean): BumpResult;
@@ -11,10 +11,8 @@ function bumpAllVersions(packageFiles, releaseType, dryRun) {
11
11
  const firstPkg = readPackageJson(firstFile);
12
12
  const currentVersion = firstPkg.version;
13
13
  const newVersion = bumpVersion(currentVersion, releaseType);
14
- console.info(`Bumping version: ${currentVersion} -> ${newVersion} (${releaseType})`);
15
14
  for (const filePath of packageFiles) {
16
15
  if (dryRun) {
17
- console.info(` [dry-run] Would bump ${filePath}`);
18
16
  continue;
19
17
  }
20
18
  const pkg = filePath === firstFile ? firstPkg : readPackageJson(filePath);
@@ -24,9 +22,8 @@ function bumpAllVersions(packageFiles, releaseType, dryRun) {
24
22
  } catch (error) {
25
23
  throw new Error(`Failed to write ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
26
24
  }
27
- console.info(` Bumped ${filePath}`);
28
25
  }
29
- return newVersion;
26
+ return { currentVersion, newVersion, files: [...packageFiles] };
30
27
  }
31
28
  function readPackageJson(filePath) {
32
29
  let content;
@@ -1,2 +1,2 @@
1
1
  import type { ComponentConfig } from './types.ts';
2
- export declare function component(workspacePath: string, tagPrefix?: string): ComponentConfig;
2
+ export declare function component(workspacePath: string): ComponentConfig;
@@ -1,10 +1,9 @@
1
1
  import { basename } from "node:path";
2
- function component(workspacePath, tagPrefix) {
2
+ function component(workspacePath) {
3
3
  const dir = basename(workspacePath);
4
- const prefix = tagPrefix ?? `${dir}-v`;
5
4
  return {
6
5
  dir,
7
- tagPrefix: prefix,
6
+ tagPrefix: `${dir}-v`,
8
7
  packageFiles: [`${workspacePath}/package.json`],
9
8
  changelogPaths: [workspacePath],
10
9
  paths: [`${workspacePath}/**`]
@@ -0,0 +1,5 @@
1
+ export interface CreateTagsOptions {
2
+ dryRun: boolean;
3
+ noGitChecks: boolean;
4
+ }
5
+ export declare function createTags(options: CreateTagsOptions): string[];
@@ -0,0 +1,73 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { readFileSync, unlinkSync } from "node:fs";
3
+ import { RELEASE_TAGS_FILE } from "./prepareCommand.js";
4
+ function createTags(options) {
5
+ const { dryRun, noGitChecks } = options;
6
+ let content;
7
+ try {
8
+ content = readFileSync(RELEASE_TAGS_FILE, "utf8");
9
+ } catch {
10
+ throw new Error("No tags file found. Run `release-kit prepare` first.");
11
+ }
12
+ const tags = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
13
+ if (tags.length === 0) {
14
+ return [];
15
+ }
16
+ if (!dryRun && !noGitChecks) {
17
+ assertCleanWorkingTree();
18
+ }
19
+ if (dryRun) {
20
+ console.info("[dry-run] Would create tags:");
21
+ for (const tag of tags) {
22
+ console.info(`\u{1F3F7}\uFE0F ${tag}`);
23
+ }
24
+ return tags;
25
+ }
26
+ const created = [];
27
+ for (const tag of tags) {
28
+ try {
29
+ execFileSync("git", ["tag", "-a", tag, "-m", tag]);
30
+ created.push(tag);
31
+ } catch (error) {
32
+ if (created.length > 0) {
33
+ console.warn("Tags created before failure:");
34
+ for (const t of created) {
35
+ console.warn(` ${t}`);
36
+ }
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+ console.info("Created tags:");
42
+ for (const tag of tags) {
43
+ console.info(`\u{1F3F7}\uFE0F ${tag}`);
44
+ }
45
+ deleteTagsFile();
46
+ return tags;
47
+ }
48
+ function deleteTagsFile() {
49
+ try {
50
+ unlinkSync(RELEASE_TAGS_FILE);
51
+ } catch (error) {
52
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
53
+ return;
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+ function assertCleanWorkingTree() {
59
+ try {
60
+ execFileSync("git", ["diff", "--quiet"]);
61
+ execFileSync("git", ["diff", "--quiet", "--cached"]);
62
+ } catch (error) {
63
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
64
+ throw error;
65
+ }
66
+ throw new Error(
67
+ "Working tree is dirty. Commit or stash changes before tagging, or use `--no-git-checks` to skip this check."
68
+ );
69
+ }
70
+ }
71
+ export {
72
+ createTags
73
+ };
@@ -0,0 +1,2 @@
1
+ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'yarn-berry';
2
+ export declare function detectPackageManager(): PackageManager;
@@ -0,0 +1,44 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { isRecord } from "./typeGuards.js";
4
+ function detectPackageManager() {
5
+ const packageJsonPath = join(process.cwd(), "package.json");
6
+ try {
7
+ const content = readFileSync(packageJsonPath, "utf8");
8
+ const parsed = JSON.parse(content);
9
+ if (isRecord(parsed) && typeof parsed.packageManager === "string") {
10
+ const [name, version] = parsed.packageManager.split("@");
11
+ if (name === "pnpm" || name === "npm") {
12
+ return name;
13
+ }
14
+ if (name === "yarn") {
15
+ return isYarnBerry(version) ? "yarn-berry" : "yarn";
16
+ }
17
+ }
18
+ } catch {
19
+ }
20
+ return detectFromLockfile();
21
+ }
22
+ function isYarnBerry(version) {
23
+ if (version === void 0) {
24
+ return false;
25
+ }
26
+ const major = Number.parseInt(version, 10);
27
+ return !Number.isNaN(major) && major >= 2;
28
+ }
29
+ function detectFromLockfile() {
30
+ const cwd = process.cwd();
31
+ if (existsSync(join(cwd, "pnpm-lock.yaml"))) {
32
+ return "pnpm";
33
+ }
34
+ if (existsSync(join(cwd, "package-lock.json"))) {
35
+ return "npm";
36
+ }
37
+ if (existsSync(join(cwd, "yarn.lock"))) {
38
+ return "yarn";
39
+ }
40
+ return "npm";
41
+ }
42
+ export {
43
+ detectPackageManager
44
+ };
@@ -0,0 +1 @@
1
+ export declare function findPackageRoot(fromUrl: string): string;
@@ -0,0 +1,17 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ function findPackageRoot(fromUrl) {
5
+ let dir = dirname(fileURLToPath(fromUrl));
6
+ while (!existsSync(resolve(dir, "package.json"))) {
7
+ const parent = dirname(dir);
8
+ if (parent === dir) {
9
+ throw new Error("Could not find package root from " + fromUrl);
10
+ }
11
+ dir = parent;
12
+ }
13
+ return dir;
14
+ }
15
+ export {
16
+ findPackageRoot
17
+ };
@@ -0,0 +1,3 @@
1
+ export declare function bold(text: string): string;
2
+ export declare function dim(text: string): string;
3
+ export declare function sectionHeader(name: string): string;
@@ -0,0 +1,14 @@
1
+ function bold(text) {
2
+ return `\x1B[1m${text}\x1B[0m`;
3
+ }
4
+ function dim(text) {
5
+ return `\x1B[2m${text}\x1B[0m`;
6
+ }
7
+ function sectionHeader(name) {
8
+ return `\u2501\u2501\u2501 ${name} \u2501\u2501\u2501`;
9
+ }
10
+ export {
11
+ bold,
12
+ dim,
13
+ sectionHeader
14
+ };
@@ -2,5 +2,5 @@ import type { ReleaseConfig } from './types.ts';
2
2
  export interface GenerateChangelogOptions {
3
3
  includePaths?: string[];
4
4
  }
5
- export declare function generateChangelog(config: Pick<ReleaseConfig, 'cliffConfigPath'>, changelogPath: string, tag: string, dryRun: boolean, options?: GenerateChangelogOptions): void;
6
- export declare function generateChangelogs(config: ReleaseConfig, tag: string, dryRun: boolean): void;
5
+ export declare function generateChangelog(config: Pick<ReleaseConfig, 'cliffConfigPath'>, changelogPath: string, tag: string, dryRun: boolean, options?: GenerateChangelogOptions): string[];
6
+ export declare function generateChangelogs(config: ReleaseConfig, tag: string, dryRun: boolean): string[];
@@ -7,23 +7,23 @@ function generateChangelog(config, changelogPath, tag, dryRun, options) {
7
7
  for (const includePath of options?.includePaths ?? []) {
8
8
  args.push("--include-path", includePath);
9
9
  }
10
- if (dryRun) {
11
- console.info(` [dry-run] Would run: npx --yes git-cliff ${args.join(" ")}`);
12
- return;
13
- }
14
- console.info(` Generating changelog: ${outputFile}`);
15
- try {
16
- execFileSync("npx", ["--yes", "git-cliff", ...args], { stdio: "inherit" });
17
- } catch (error) {
18
- throw new Error(
19
- `Failed to generate changelog for ${outputFile}: ${error instanceof Error ? error.message : String(error)}`
20
- );
10
+ if (!dryRun) {
11
+ try {
12
+ execFileSync("npx", ["--yes", "git-cliff", ...args], { stdio: "inherit" });
13
+ } catch (error) {
14
+ throw new Error(
15
+ `Failed to generate changelog for ${outputFile}: ${error instanceof Error ? error.message : String(error)}`
16
+ );
17
+ }
21
18
  }
19
+ return [outputFile];
22
20
  }
23
21
  function generateChangelogs(config, tag, dryRun) {
22
+ const results = [];
24
23
  for (const changelogPath of config.changelogPaths) {
25
- generateChangelog(config, changelogPath, tag, dryRun);
24
+ results.push(...generateChangelog(config, changelogPath, tag, dryRun));
26
25
  }
26
+ return results;
27
27
  }
28
28
  export {
29
29
  generateChangelog,
@@ -0,0 +1 @@
1
+ export declare function hasPrettierConfig(): boolean;
@@ -0,0 +1,42 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { isRecord } from "./typeGuards.js";
4
+ function hasPrettierConfig() {
5
+ const cwd = process.cwd();
6
+ for (const file of PRETTIER_CONFIG_FILES) {
7
+ if (existsSync(path.join(cwd, file))) {
8
+ return true;
9
+ }
10
+ }
11
+ return hasPrettierKeyInPackageJson(path.join(cwd, "package.json"));
12
+ }
13
+ function hasPrettierKeyInPackageJson(packageJsonPath) {
14
+ if (!existsSync(packageJsonPath)) {
15
+ return false;
16
+ }
17
+ try {
18
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
19
+ return isRecord(parsed) && "prettier" in parsed;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+ const PRETTIER_CONFIG_FILES = [
25
+ ".prettierrc",
26
+ ".prettierrc.cjs",
27
+ ".prettierrc.js",
28
+ ".prettierrc.json",
29
+ ".prettierrc.json5",
30
+ ".prettierrc.mjs",
31
+ ".prettierrc.toml",
32
+ ".prettierrc.ts",
33
+ ".prettierrc.yaml",
34
+ ".prettierrc.yml",
35
+ "prettier.config.cjs",
36
+ "prettier.config.js",
37
+ "prettier.config.mjs",
38
+ "prettier.config.ts"
39
+ ];
40
+ export {
41
+ hasPrettierConfig
42
+ };