@releasekit/notes 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 +99 -73
- package/dist/aggregator-IUQUAVJC.js +13 -0
- package/dist/chunk-7TJSPQPW.js +243 -0
- package/dist/chunk-F7MUVHZ2.js +165 -0
- package/dist/chunk-QX23CBNW.js +1757 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +225 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +95 -0
- package/docs/configuration.md +276 -0
- package/docs/llm-providers.md +246 -0
- package/docs/monorepo.md +124 -0
- package/docs/templates.md +204 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# @releasekit/notes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@releasekit/notes)
|
|
4
|
+
[](https://www.npmjs.com/package/@releasekit/notes)
|
|
5
|
+
[](https://www.npmjs.com/package/@releasekit/notes)
|
|
6
|
+
|
|
7
|
+
**Changelog and release notes generation from conventional commits**
|
|
8
|
+
|
|
9
|
+
Generates CHANGELOG.md and release notes from `@releasekit/version` output, with optional LLM-powered enhancement and flexible templating.
|
|
4
10
|
|
|
5
11
|
## Features
|
|
6
12
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Monorepo support** — root aggregation, per-package changelogs, or both
|
|
11
|
-
- **
|
|
12
|
-
- **Dry-run mode** — preview without writing files
|
|
13
|
+
- 📝 **Conventional changelog** — Keep a Changelog, Angular, or custom format
|
|
14
|
+
- 🤖 **LLM enhancement** (optional) — enhance descriptions, summarize, categorize, or generate prose release notes
|
|
15
|
+
- 🎨 **Flexible templating** — Liquid, Handlebars, or EJS; single-file or composable layout
|
|
16
|
+
- 📦 **Monorepo support** — root aggregation, per-package changelogs, or both
|
|
17
|
+
- 🔀 **Two outputs** — `CHANGELOG.md` and `RELEASE_NOTES.md` are configured independently
|
|
18
|
+
- 🔍 **Dry-run mode** — preview without writing files
|
|
13
19
|
|
|
14
20
|
## Installation
|
|
15
21
|
|
|
@@ -19,7 +25,7 @@ npm install -g @releasekit/notes
|
|
|
19
25
|
pnpm add -g @releasekit/notes
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
> **Note:**
|
|
28
|
+
> **Note:** ESM only. Requires Node.js 20+.
|
|
23
29
|
|
|
24
30
|
## Quick Start
|
|
25
31
|
|
|
@@ -30,107 +36,122 @@ releasekit-version --json | releasekit-notes
|
|
|
30
36
|
# From a file
|
|
31
37
|
releasekit-notes --input version-data.json
|
|
32
38
|
|
|
33
|
-
# With LLM enhancement
|
|
34
|
-
releasekit-notes --input version-data.json --llm-provider openai --llm-model gpt-4o-mini
|
|
35
|
-
|
|
36
39
|
# Preview without writing
|
|
37
40
|
releasekit-notes --dry-run
|
|
41
|
+
|
|
42
|
+
# With LLM enhancement
|
|
43
|
+
releasekit-notes --input version-data.json \
|
|
44
|
+
--llm-provider openai \
|
|
45
|
+
--llm-model gpt-4o-mini \
|
|
46
|
+
--llm-tasks enhance,summarize
|
|
38
47
|
```
|
|
39
48
|
|
|
40
49
|
## CLI Reference
|
|
41
50
|
|
|
51
|
+
### `releasekit-notes generate` (default)
|
|
52
|
+
|
|
42
53
|
| Flag | Description | Default |
|
|
43
54
|
|------|-------------|---------|
|
|
44
55
|
| `-i, --input <file>` | Input file path | stdin |
|
|
45
|
-
|
|
|
56
|
+
| `--changelog-mode <mode>` | Changelog location: `root`, `packages`, `both` | `root` |
|
|
57
|
+
| `--changelog-file <name>` | Changelog file name override | `CHANGELOG.md` |
|
|
58
|
+
| `--no-changelog` | Disable changelog generation | — |
|
|
59
|
+
| `--release-notes-mode <mode>` | Enable release notes file output: `root`, `packages`, `both` | — |
|
|
60
|
+
| `--release-notes-file <name>` | Release notes file name override | `RELEASE_NOTES.md` |
|
|
61
|
+
| `--no-release-notes` | Disable release notes generation | — |
|
|
46
62
|
| `-t, --template <path>` | Template file or directory | built-in |
|
|
47
63
|
| `-e, --engine <engine>` | Template engine: `handlebars`, `liquid`, `ejs` | `liquid` |
|
|
48
64
|
| `--monorepo <mode>` | Monorepo mode: `root`, `packages`, `both` | — |
|
|
49
65
|
| `--llm-provider <name>` | LLM provider | — |
|
|
50
66
|
| `--llm-model <model>` | LLM model | — |
|
|
51
|
-
| `--llm-
|
|
52
|
-
| `--
|
|
67
|
+
| `--llm-base-url <url>` | Base URL for openai-compatible providers | — |
|
|
68
|
+
| `--llm-tasks <tasks>` | Comma-separated tasks: `enhance`, `summarize`, `categorize`, `release-notes` | — |
|
|
69
|
+
| `--no-llm` | Disable LLM processing | — |
|
|
70
|
+
| `--target <package>` | Filter to a specific package name | — |
|
|
53
71
|
| `--config <path>` | Config file path | `releasekit.config.json` |
|
|
72
|
+
| `--regenerate` | Regenerate entire file instead of prepending | `false` |
|
|
54
73
|
| `--dry-run` | Preview without writing | `false` |
|
|
55
|
-
|
|
|
56
|
-
| `-
|
|
57
|
-
| `-q, --quiet` | Suppress non-error output | `false` |
|
|
58
|
-
|
|
59
|
-
## Subcommands
|
|
60
|
-
|
|
61
|
-
### `releasekit-notes init`
|
|
62
|
-
|
|
63
|
-
Create a default configuration file.
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
releasekit-notes init [--force]
|
|
67
|
-
```
|
|
74
|
+
| `-v, --verbose` | Verbose logging (repeat for more: `-vv`) | — |
|
|
75
|
+
| `-q, --quiet` | Suppress non-error output | — |
|
|
68
76
|
|
|
69
77
|
### `releasekit-notes auth <provider>`
|
|
70
78
|
|
|
71
|
-
|
|
79
|
+
Store an API key for an LLM provider.
|
|
72
80
|
|
|
73
81
|
```bash
|
|
74
|
-
releasekit-notes auth openai
|
|
75
|
-
releasekit-notes auth anthropic
|
|
82
|
+
releasekit-notes auth openai
|
|
83
|
+
releasekit-notes auth anthropic --key sk-ant-...
|
|
76
84
|
```
|
|
77
85
|
|
|
86
|
+
Keys are saved to `~/.config/releasekit/auth.json`.
|
|
87
|
+
|
|
78
88
|
### `releasekit-notes providers`
|
|
79
89
|
|
|
80
90
|
List available LLM providers.
|
|
81
91
|
|
|
82
92
|
## Configuration
|
|
83
93
|
|
|
84
|
-
|
|
94
|
+
All options live under the `notes` key in `releasekit.config.json`:
|
|
85
95
|
|
|
86
96
|
```json
|
|
87
97
|
{
|
|
98
|
+
"$schema": "https://goosewobbler.github.io/releasekit/schema.json",
|
|
88
99
|
"notes": {
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
"changelog": {
|
|
101
|
+
"mode": "root",
|
|
102
|
+
"file": "CHANGELOG.md",
|
|
103
|
+
"templates": {
|
|
104
|
+
"path": "./templates/changelog/",
|
|
105
|
+
"engine": "liquid"
|
|
106
|
+
}
|
|
96
107
|
},
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
"
|
|
102
|
-
"
|
|
108
|
+
"releaseNotes": {
|
|
109
|
+
"mode": "root",
|
|
110
|
+
"llm": {
|
|
111
|
+
"provider": "openai",
|
|
112
|
+
"model": "gpt-4o-mini",
|
|
113
|
+
"tasks": {
|
|
114
|
+
"enhance": true,
|
|
115
|
+
"summarize": true
|
|
116
|
+
}
|
|
103
117
|
}
|
|
104
|
-
}
|
|
118
|
+
},
|
|
119
|
+
"updateStrategy": "prepend"
|
|
105
120
|
}
|
|
106
121
|
}
|
|
107
122
|
```
|
|
108
123
|
|
|
124
|
+
`changelog` and `releaseNotes` are configured independently. Set either to `false` to disable it entirely.
|
|
125
|
+
|
|
109
126
|
## LLM Providers
|
|
110
127
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
|
114
|
-
|
|
115
|
-
|
|
|
116
|
-
|
|
|
128
|
+
LLM configuration lives under `notes.releaseNotes.llm`:
|
|
129
|
+
|
|
130
|
+
| Provider | Key | Auth |
|
|
131
|
+
|----------|-----|------|
|
|
132
|
+
| OpenAI | `openai` | `OPENAI_API_KEY` or `releasekit-notes auth openai` |
|
|
133
|
+
| Anthropic | `anthropic` | `ANTHROPIC_API_KEY` or `releasekit-notes auth anthropic` |
|
|
134
|
+
| Ollama | `ollama` | None (local) |
|
|
135
|
+
| OpenAI-compatible | `openai-compatible` | Varies — set `baseURL` and `apiKey` |
|
|
117
136
|
|
|
118
137
|
### LLM Tasks
|
|
119
138
|
|
|
120
|
-
| Task |
|
|
139
|
+
| Task | What it does |
|
|
121
140
|
|------|-------------|
|
|
122
|
-
| `enhance` |
|
|
123
|
-
| `summarize` |
|
|
124
|
-
| `categorize` |
|
|
125
|
-
| `releaseNotes` |
|
|
141
|
+
| `enhance` | Rewrites each changelog entry description to be clearer |
|
|
142
|
+
| `summarize` | Generates a one-paragraph summary of the release |
|
|
143
|
+
| `categorize` | Groups entries into user-friendly categories (Features, Fixes, …) |
|
|
144
|
+
| `releaseNotes` | Generates full prose release notes (use as GitHub release body) |
|
|
126
145
|
|
|
127
146
|
## Templates
|
|
128
147
|
|
|
129
|
-
### Built-in
|
|
148
|
+
### Built-in Templates
|
|
130
149
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
150
|
+
| Name | Engine | Description |
|
|
151
|
+
|------|--------|-------------|
|
|
152
|
+
| `keep-a-changelog` | Liquid | [Keep a Changelog](https://keepachangelog.com) format (default) |
|
|
153
|
+
| `angular` | Handlebars | Angular-style changelog |
|
|
154
|
+
| `github-release` | EJS | GitHub release notes format |
|
|
134
155
|
|
|
135
156
|
### Custom Templates
|
|
136
157
|
|
|
@@ -138,32 +159,37 @@ Configure via `releasekit.config.json`:
|
|
|
138
159
|
# Single file
|
|
139
160
|
releasekit-notes --template ./my-changelog.liquid
|
|
140
161
|
|
|
141
|
-
# Composable directory
|
|
162
|
+
# Composable directory (document + version + entry)
|
|
142
163
|
releasekit-notes --template ./templates/
|
|
143
164
|
```
|
|
144
165
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
```
|
|
148
|
-
templates/
|
|
149
|
-
├── document.liquid
|
|
150
|
-
├── version.liquid
|
|
151
|
-
└── entry.liquid
|
|
152
|
-
```
|
|
166
|
+
See **[Templates guide](./docs/templates.md)** for the full template context reference and authoring guide.
|
|
153
167
|
|
|
154
168
|
## Monorepo Support
|
|
155
169
|
|
|
156
170
|
```bash
|
|
157
171
|
# Root changelog only (aggregates all packages)
|
|
158
|
-
releasekit-notes --
|
|
172
|
+
releasekit-notes --changelog-mode root
|
|
159
173
|
|
|
160
174
|
# Per-package changelogs
|
|
161
|
-
releasekit-notes --
|
|
175
|
+
releasekit-notes --changelog-mode packages
|
|
162
176
|
|
|
163
|
-
# Both
|
|
164
|
-
releasekit-notes --
|
|
177
|
+
# Both root and per-package
|
|
178
|
+
releasekit-notes --changelog-mode both
|
|
165
179
|
```
|
|
166
180
|
|
|
181
|
+
See **[Monorepo guide](./docs/monorepo.md)** for details on file placement and aggregation behaviour.
|
|
182
|
+
|
|
183
|
+
## Documentation
|
|
184
|
+
|
|
185
|
+
**Getting Started**
|
|
186
|
+
- [Configuration reference](./docs/configuration.md) — all `notes.*` options
|
|
187
|
+
- [LLM providers](./docs/llm-providers.md) — provider setup, auth, tasks, prompt customisation
|
|
188
|
+
|
|
189
|
+
**Guides**
|
|
190
|
+
- [Templates](./docs/templates.md) — custom template authoring and context reference
|
|
191
|
+
- [Monorepo](./docs/monorepo.md) — per-package and root output modes
|
|
192
|
+
|
|
167
193
|
## License
|
|
168
194
|
|
|
169
195
|
MIT
|
|
@@ -0,0 +1,243 @@
|
|
|
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 setQuietMode(quiet) {
|
|
43
|
+
quietMode = quiet;
|
|
44
|
+
}
|
|
45
|
+
function shouldLog(level) {
|
|
46
|
+
if (quietMode && level !== "error") return false;
|
|
47
|
+
return LOG_LEVELS[level] <= LOG_LEVELS[currentLevel];
|
|
48
|
+
}
|
|
49
|
+
function log(message, level = "info") {
|
|
50
|
+
if (!shouldLog(level)) return;
|
|
51
|
+
const formatted = COLORS[level](`${PREFIXES[level]} ${message}`);
|
|
52
|
+
console.error(formatted);
|
|
53
|
+
}
|
|
54
|
+
function error(message) {
|
|
55
|
+
log(message, "error");
|
|
56
|
+
}
|
|
57
|
+
function warn(message) {
|
|
58
|
+
log(message, "warn");
|
|
59
|
+
}
|
|
60
|
+
function info(message) {
|
|
61
|
+
log(message, "info");
|
|
62
|
+
}
|
|
63
|
+
function success(message) {
|
|
64
|
+
if (!shouldLog("info")) return;
|
|
65
|
+
console.error(chalk.green(`[SUCCESS] ${message}`));
|
|
66
|
+
}
|
|
67
|
+
function debug(message) {
|
|
68
|
+
log(message, "debug");
|
|
69
|
+
}
|
|
70
|
+
var ReleaseKitError = class _ReleaseKitError extends Error {
|
|
71
|
+
constructor(message) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = this.constructor.name;
|
|
74
|
+
}
|
|
75
|
+
logError() {
|
|
76
|
+
log(this.message, "error");
|
|
77
|
+
if (this.suggestions.length > 0) {
|
|
78
|
+
log("\nSuggested solutions:", "info");
|
|
79
|
+
for (const [i, suggestion] of this.suggestions.entries()) {
|
|
80
|
+
log(`${i + 1}. ${suggestion}`, "info");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
static isReleaseKitError(error2) {
|
|
85
|
+
return error2 instanceof _ReleaseKitError;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var EXIT_CODES = {
|
|
89
|
+
SUCCESS: 0,
|
|
90
|
+
GENERAL_ERROR: 1,
|
|
91
|
+
CONFIG_ERROR: 2,
|
|
92
|
+
INPUT_ERROR: 3,
|
|
93
|
+
TEMPLATE_ERROR: 4,
|
|
94
|
+
LLM_ERROR: 5,
|
|
95
|
+
GITHUB_ERROR: 6,
|
|
96
|
+
GIT_ERROR: 7,
|
|
97
|
+
VERSION_ERROR: 8,
|
|
98
|
+
PUBLISH_ERROR: 9
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/output/markdown.ts
|
|
102
|
+
import * as fs2 from "fs";
|
|
103
|
+
import * as path2 from "path";
|
|
104
|
+
var TYPE_ORDER = ["added", "changed", "deprecated", "removed", "fixed", "security"];
|
|
105
|
+
var TYPE_LABELS = {
|
|
106
|
+
added: "Added",
|
|
107
|
+
changed: "Changed",
|
|
108
|
+
deprecated: "Deprecated",
|
|
109
|
+
removed: "Removed",
|
|
110
|
+
fixed: "Fixed",
|
|
111
|
+
security: "Security"
|
|
112
|
+
};
|
|
113
|
+
function groupEntriesByType(entries) {
|
|
114
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const type of TYPE_ORDER) {
|
|
116
|
+
grouped.set(type, []);
|
|
117
|
+
}
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
const existing = grouped.get(entry.type) ?? [];
|
|
120
|
+
existing.push(entry);
|
|
121
|
+
grouped.set(entry.type, existing);
|
|
122
|
+
}
|
|
123
|
+
return grouped;
|
|
124
|
+
}
|
|
125
|
+
function formatEntry(entry) {
|
|
126
|
+
let line;
|
|
127
|
+
if (entry.breaking && entry.scope) {
|
|
128
|
+
line = `- **BREAKING** **${entry.scope}**: ${entry.description}`;
|
|
129
|
+
} else if (entry.breaking) {
|
|
130
|
+
line = `- **BREAKING** ${entry.description}`;
|
|
131
|
+
} else if (entry.scope) {
|
|
132
|
+
line = `- **${entry.scope}**: ${entry.description}`;
|
|
133
|
+
} else {
|
|
134
|
+
line = `- ${entry.description}`;
|
|
135
|
+
}
|
|
136
|
+
if (entry.issueIds && entry.issueIds.length > 0) {
|
|
137
|
+
line += ` (${entry.issueIds.join(", ")})`;
|
|
138
|
+
}
|
|
139
|
+
return line;
|
|
140
|
+
}
|
|
141
|
+
function formatVersion(context, options) {
|
|
142
|
+
const lines = [];
|
|
143
|
+
const versionLabel = options?.includePackageName && context.packageName ? `${context.packageName}@${context.version}` : context.version;
|
|
144
|
+
const versionHeader = context.previousVersion ? `## [${versionLabel}]` : `## ${versionLabel}`;
|
|
145
|
+
lines.push(`${versionHeader} - ${context.date}`);
|
|
146
|
+
lines.push("");
|
|
147
|
+
if (context.compareUrl) {
|
|
148
|
+
lines.push(`[Full Changelog](${context.compareUrl})`);
|
|
149
|
+
lines.push("");
|
|
150
|
+
}
|
|
151
|
+
if (context.enhanced?.summary) {
|
|
152
|
+
lines.push(context.enhanced.summary);
|
|
153
|
+
lines.push("");
|
|
154
|
+
}
|
|
155
|
+
const grouped = groupEntriesByType(context.entries);
|
|
156
|
+
for (const [type, entries] of grouped) {
|
|
157
|
+
if (entries.length === 0) continue;
|
|
158
|
+
lines.push(`### ${TYPE_LABELS[type]}`);
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
lines.push(formatEntry(entry));
|
|
161
|
+
}
|
|
162
|
+
lines.push("");
|
|
163
|
+
}
|
|
164
|
+
return lines.join("\n");
|
|
165
|
+
}
|
|
166
|
+
function formatHeader() {
|
|
167
|
+
return `# Changelog
|
|
168
|
+
|
|
169
|
+
All notable changes to this project will be documented in this file.
|
|
170
|
+
|
|
171
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
172
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
173
|
+
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
function renderMarkdown(contexts, options) {
|
|
177
|
+
const sections = [formatHeader()];
|
|
178
|
+
for (const context of contexts) {
|
|
179
|
+
sections.push(formatVersion(context, options));
|
|
180
|
+
}
|
|
181
|
+
return sections.join("\n");
|
|
182
|
+
}
|
|
183
|
+
function prependVersion(existingPath, context, options) {
|
|
184
|
+
let existing = "";
|
|
185
|
+
if (fs2.existsSync(existingPath)) {
|
|
186
|
+
existing = fs2.readFileSync(existingPath, "utf-8");
|
|
187
|
+
const headerEnd = existing.indexOf("\n## ");
|
|
188
|
+
if (headerEnd >= 0) {
|
|
189
|
+
const header = existing.slice(0, headerEnd);
|
|
190
|
+
const body = existing.slice(headerEnd + 1);
|
|
191
|
+
const newVersion = formatVersion(context, options);
|
|
192
|
+
return `${header}
|
|
193
|
+
|
|
194
|
+
${newVersion}
|
|
195
|
+
${body}`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return renderMarkdown([context]);
|
|
199
|
+
}
|
|
200
|
+
function writeMarkdown(outputPath, contexts, config, dryRun, options) {
|
|
201
|
+
const content = renderMarkdown(contexts, options);
|
|
202
|
+
const label = /changelog/i.test(outputPath) ? "Changelog" : "Release notes";
|
|
203
|
+
if (dryRun) {
|
|
204
|
+
info(`[DRY RUN] ${label} preview (would write to ${outputPath}):`);
|
|
205
|
+
info(content);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const dir = path2.dirname(outputPath);
|
|
209
|
+
if (!fs2.existsSync(dir)) {
|
|
210
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
211
|
+
}
|
|
212
|
+
if (outputPath === "-") {
|
|
213
|
+
process.stdout.write(content);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (config.updateStrategy !== "regenerate" && fs2.existsSync(outputPath) && contexts.length === 1) {
|
|
217
|
+
const firstContext = contexts[0];
|
|
218
|
+
if (firstContext) {
|
|
219
|
+
const updated = prependVersion(outputPath, firstContext, options);
|
|
220
|
+
fs2.writeFileSync(outputPath, updated, "utf-8");
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
fs2.writeFileSync(outputPath, content, "utf-8");
|
|
224
|
+
}
|
|
225
|
+
success(`${label} written to ${outputPath}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export {
|
|
229
|
+
readPackageVersion,
|
|
230
|
+
setLogLevel,
|
|
231
|
+
setQuietMode,
|
|
232
|
+
error,
|
|
233
|
+
warn,
|
|
234
|
+
info,
|
|
235
|
+
success,
|
|
236
|
+
debug,
|
|
237
|
+
ReleaseKitError,
|
|
238
|
+
EXIT_CODES,
|
|
239
|
+
formatVersion,
|
|
240
|
+
renderMarkdown,
|
|
241
|
+
prependVersion,
|
|
242
|
+
writeMarkdown
|
|
243
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
debug,
|
|
3
|
+
formatVersion,
|
|
4
|
+
info,
|
|
5
|
+
prependVersion,
|
|
6
|
+
renderMarkdown,
|
|
7
|
+
success
|
|
8
|
+
} from "./chunk-7TJSPQPW.js";
|
|
9
|
+
|
|
10
|
+
// src/monorepo/aggregator.ts
|
|
11
|
+
import * as fs from "fs";
|
|
12
|
+
import * as path from "path";
|
|
13
|
+
|
|
14
|
+
// src/monorepo/splitter.ts
|
|
15
|
+
function splitByPackage(contexts) {
|
|
16
|
+
const byPackage = /* @__PURE__ */ new Map();
|
|
17
|
+
for (const ctx of contexts) {
|
|
18
|
+
byPackage.set(ctx.packageName, ctx);
|
|
19
|
+
}
|
|
20
|
+
return byPackage;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/monorepo/aggregator.ts
|
|
24
|
+
function writeFile(outputPath, content, dryRun) {
|
|
25
|
+
if (dryRun) {
|
|
26
|
+
info(`Would write to ${outputPath}`);
|
|
27
|
+
debug(content);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const dir = path.dirname(outputPath);
|
|
31
|
+
if (!fs.existsSync(dir)) {
|
|
32
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
fs.writeFileSync(outputPath, content, "utf-8");
|
|
35
|
+
success(`Changelog written to ${outputPath}`);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
function aggregateToRoot(contexts) {
|
|
39
|
+
const aggregated = {
|
|
40
|
+
packageName: "monorepo",
|
|
41
|
+
version: contexts[0]?.version ?? "0.0.0",
|
|
42
|
+
previousVersion: contexts[0]?.previousVersion ?? null,
|
|
43
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "",
|
|
44
|
+
repoUrl: contexts[0]?.repoUrl ?? null,
|
|
45
|
+
entries: []
|
|
46
|
+
};
|
|
47
|
+
for (const ctx of contexts) {
|
|
48
|
+
for (const entry of ctx.entries) {
|
|
49
|
+
aggregated.entries.push({
|
|
50
|
+
...entry,
|
|
51
|
+
scope: entry.scope ? `${ctx.packageName}/${entry.scope}` : ctx.packageName
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return aggregated;
|
|
56
|
+
}
|
|
57
|
+
function writeMonorepoChangelogs(contexts, options, config, dryRun) {
|
|
58
|
+
const files = [];
|
|
59
|
+
if (options.mode === "root" || options.mode === "both") {
|
|
60
|
+
const rootPath = path.join(options.rootPath, options.fileName ?? "CHANGELOG.md");
|
|
61
|
+
const fmtOpts = { includePackageName: true };
|
|
62
|
+
info(`Writing root changelog to ${rootPath}`);
|
|
63
|
+
let rootContent;
|
|
64
|
+
if (config.updateStrategy !== "regenerate" && fs.existsSync(rootPath)) {
|
|
65
|
+
const newSections = contexts.map((ctx) => formatVersion(ctx, fmtOpts)).join("\n");
|
|
66
|
+
const existing = fs.readFileSync(rootPath, "utf-8");
|
|
67
|
+
const headerEnd = existing.indexOf("\n## ");
|
|
68
|
+
if (headerEnd >= 0) {
|
|
69
|
+
rootContent = `${existing.slice(0, headerEnd)}
|
|
70
|
+
|
|
71
|
+
${newSections}
|
|
72
|
+
${existing.slice(headerEnd + 1)}`;
|
|
73
|
+
} else {
|
|
74
|
+
rootContent = renderMarkdown(contexts, fmtOpts);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
rootContent = renderMarkdown(contexts, fmtOpts);
|
|
78
|
+
}
|
|
79
|
+
if (writeFile(rootPath, rootContent, dryRun)) {
|
|
80
|
+
files.push(rootPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (options.mode === "packages" || options.mode === "both") {
|
|
84
|
+
const byPackage = splitByPackage(contexts);
|
|
85
|
+
const packageDirMap = buildPackageDirMap(options.rootPath, options.packagesPath);
|
|
86
|
+
for (const [packageName, ctx] of byPackage) {
|
|
87
|
+
const simpleName = packageName.split("/").pop();
|
|
88
|
+
const packageDir = packageDirMap.get(packageName) ?? (simpleName ? packageDirMap.get(simpleName) : void 0) ?? null;
|
|
89
|
+
if (packageDir) {
|
|
90
|
+
const changelogPath = path.join(packageDir, options.fileName ?? "CHANGELOG.md");
|
|
91
|
+
info(`Writing changelog for ${packageName} to ${changelogPath}`);
|
|
92
|
+
const pkgContent = config.updateStrategy !== "regenerate" && fs.existsSync(changelogPath) ? prependVersion(changelogPath, ctx) : renderMarkdown([ctx]);
|
|
93
|
+
if (writeFile(changelogPath, pkgContent, dryRun)) {
|
|
94
|
+
files.push(changelogPath);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
info(`Could not find directory for package ${packageName}, skipping`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return files;
|
|
102
|
+
}
|
|
103
|
+
function buildPackageDirMap(rootPath, packagesPath) {
|
|
104
|
+
const map = /* @__PURE__ */ new Map();
|
|
105
|
+
const packagesDir = path.join(rootPath, packagesPath);
|
|
106
|
+
if (!fs.existsSync(packagesDir)) {
|
|
107
|
+
return map;
|
|
108
|
+
}
|
|
109
|
+
for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {
|
|
110
|
+
if (!entry.isDirectory()) continue;
|
|
111
|
+
const dirPath = path.join(packagesDir, entry.name);
|
|
112
|
+
map.set(entry.name, dirPath);
|
|
113
|
+
const packageJsonPath = path.join(dirPath, "package.json");
|
|
114
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
115
|
+
try {
|
|
116
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
117
|
+
if (pkg.name) {
|
|
118
|
+
map.set(pkg.name, dirPath);
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return map;
|
|
125
|
+
}
|
|
126
|
+
function detectMonorepo(cwd) {
|
|
127
|
+
const pnpmWorkspacesPath = path.join(cwd, "pnpm-workspace.yaml");
|
|
128
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
129
|
+
if (fs.existsSync(pnpmWorkspacesPath)) {
|
|
130
|
+
const content = fs.readFileSync(pnpmWorkspacesPath, "utf-8");
|
|
131
|
+
const packagesMatch = content.match(/packages:\s*\n\s*-\s*['"]([^'"]+)['"]/);
|
|
132
|
+
if (packagesMatch?.[1]) {
|
|
133
|
+
const packagesGlob = packagesMatch[1];
|
|
134
|
+
const packagesPath = packagesGlob.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
|
|
135
|
+
return { isMonorepo: true, packagesPath: packagesPath || "packages" };
|
|
136
|
+
}
|
|
137
|
+
return { isMonorepo: true, packagesPath: "packages" };
|
|
138
|
+
}
|
|
139
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
140
|
+
try {
|
|
141
|
+
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
|
142
|
+
const pkg = JSON.parse(content);
|
|
143
|
+
if (pkg.workspaces) {
|
|
144
|
+
const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
|
|
145
|
+
if (workspaces?.length) {
|
|
146
|
+
const firstWorkspace = workspaces[0];
|
|
147
|
+
if (firstWorkspace) {
|
|
148
|
+
const packagesPath = firstWorkspace.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
|
|
149
|
+
return { isMonorepo: true, packagesPath: packagesPath || "packages" };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
return { isMonorepo: false, packagesPath: "" };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { isMonorepo: false, packagesPath: "" };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export {
|
|
161
|
+
splitByPackage,
|
|
162
|
+
aggregateToRoot,
|
|
163
|
+
writeMonorepoChangelogs,
|
|
164
|
+
detectMonorepo
|
|
165
|
+
};
|