@versionsmith/cli 0.0.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.
- package/dist/index.js +647 -0
- package/package.json +32 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/init.ts
|
|
4
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
5
|
+
import { join as join2 } from "path";
|
|
6
|
+
import { input, confirm } from "@inquirer/prompts";
|
|
7
|
+
|
|
8
|
+
// src/utils/config.ts
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
var DEFAULT_CONFIG = {
|
|
12
|
+
changelog: "CHANGELOG.md",
|
|
13
|
+
header: `# Changelog
|
|
14
|
+
|
|
15
|
+
All notable changes to this project will be documented in this file.
|
|
16
|
+
|
|
17
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).`
|
|
18
|
+
};
|
|
19
|
+
var VERSIONSMITH_DIR = ".versionsmith";
|
|
20
|
+
var CONFIG_FILE = "config.json";
|
|
21
|
+
function getVersionsmithDir(cwd = process.cwd()) {
|
|
22
|
+
return join(cwd, VERSIONSMITH_DIR);
|
|
23
|
+
}
|
|
24
|
+
function getConfigPath(cwd = process.cwd()) {
|
|
25
|
+
return join(cwd, VERSIONSMITH_DIR, CONFIG_FILE);
|
|
26
|
+
}
|
|
27
|
+
function readConfig(cwd = process.cwd()) {
|
|
28
|
+
const configPath = getConfigPath(cwd);
|
|
29
|
+
if (!existsSync(configPath)) {
|
|
30
|
+
return { ...DEFAULT_CONFIG };
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
34
|
+
const parsed = JSON.parse(raw);
|
|
35
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
36
|
+
} catch {
|
|
37
|
+
return { ...DEFAULT_CONFIG };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function writeConfig(config, cwd = process.cwd()) {
|
|
41
|
+
const configPath = getConfigPath(cwd);
|
|
42
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
43
|
+
}
|
|
44
|
+
function isInitialized(cwd = process.cwd()) {
|
|
45
|
+
return existsSync(getConfigPath(cwd));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/commands/init.ts
|
|
49
|
+
async function init(cwd = process.cwd()) {
|
|
50
|
+
const dir = getVersionsmithDir(cwd);
|
|
51
|
+
const configPath = getConfigPath(cwd);
|
|
52
|
+
if (isInitialized(cwd)) {
|
|
53
|
+
console.log(
|
|
54
|
+
`versionsmith is already initialized (found ${configPath.replace(cwd + "/", "")})`
|
|
55
|
+
);
|
|
56
|
+
const proceed = await confirm({
|
|
57
|
+
message: "Re-initialize and overwrite config?",
|
|
58
|
+
default: false
|
|
59
|
+
});
|
|
60
|
+
if (!proceed) {
|
|
61
|
+
console.log("Aborted.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
console.log("\nWelcome to versionsmith!\n");
|
|
66
|
+
console.log(
|
|
67
|
+
"This tool helps you manage changelogs following the Keep a Changelog format."
|
|
68
|
+
);
|
|
69
|
+
console.log("https://keepachangelog.com/en/1.1.0/\n");
|
|
70
|
+
const changelogFile = await input({
|
|
71
|
+
message: "Changelog file path:",
|
|
72
|
+
default: DEFAULT_CONFIG.changelog,
|
|
73
|
+
validate: (v) => v.trim().length > 0 ? true : "Cannot be empty"
|
|
74
|
+
});
|
|
75
|
+
console.log(
|
|
76
|
+
"\nCustomize your changelog header (the text before any release sections)."
|
|
77
|
+
);
|
|
78
|
+
console.log(`Default:
|
|
79
|
+
${DEFAULT_CONFIG.header}
|
|
80
|
+
`);
|
|
81
|
+
const useDefault = await confirm({
|
|
82
|
+
message: "Use the default header?",
|
|
83
|
+
default: true
|
|
84
|
+
});
|
|
85
|
+
let header = DEFAULT_CONFIG.header;
|
|
86
|
+
if (!useDefault) {
|
|
87
|
+
header = await input({
|
|
88
|
+
message: "Enter your changelog header (use \\n for newlines):",
|
|
89
|
+
default: DEFAULT_CONFIG.header.replace(/\n/g, "\\n"),
|
|
90
|
+
validate: (v) => v.trim().length > 0 ? true : "Cannot be empty"
|
|
91
|
+
});
|
|
92
|
+
header = header.replace(/\\n/g, "\n");
|
|
93
|
+
}
|
|
94
|
+
const config = {
|
|
95
|
+
changelog: changelogFile.trim(),
|
|
96
|
+
header
|
|
97
|
+
};
|
|
98
|
+
if (!existsSync2(dir)) {
|
|
99
|
+
mkdirSync(dir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
writeConfig(config, cwd);
|
|
102
|
+
const gitkeepPath = join2(dir, ".gitkeep");
|
|
103
|
+
if (!existsSync2(gitkeepPath)) {
|
|
104
|
+
writeFileSync2(gitkeepPath, "", "utf-8");
|
|
105
|
+
}
|
|
106
|
+
const changelogPath = join2(cwd, config.changelog);
|
|
107
|
+
if (!existsSync2(changelogPath)) {
|
|
108
|
+
const initialContent = config.header + "\n\n## [Unreleased]\n";
|
|
109
|
+
writeFileSync2(changelogPath, initialContent, "utf-8");
|
|
110
|
+
console.log(`
|
|
111
|
+
Created ${config.changelog}`);
|
|
112
|
+
} else {
|
|
113
|
+
console.log(`
|
|
114
|
+
${config.changelog} already exists, skipping creation.`);
|
|
115
|
+
}
|
|
116
|
+
console.log(`Created .versionsmith/config.json`);
|
|
117
|
+
console.log(`
|
|
118
|
+
versionsmith initialized successfully!
|
|
119
|
+
`);
|
|
120
|
+
console.log("Next steps:");
|
|
121
|
+
console.log(" npx @versionsmith/cli - Create a new changelog entry");
|
|
122
|
+
console.log(
|
|
123
|
+
" npx @versionsmith/cli release - Merge entries into your changelog"
|
|
124
|
+
);
|
|
125
|
+
console.log(
|
|
126
|
+
" npx @versionsmith/cli release --version 1.0.0 - Release a specific version"
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/commands/add.ts
|
|
131
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
132
|
+
import { checkbox, input as input2, confirm as confirm2 } from "@inquirer/prompts";
|
|
133
|
+
|
|
134
|
+
// src/utils/changelog.ts
|
|
135
|
+
var CHANGE_TYPES = [
|
|
136
|
+
"Added",
|
|
137
|
+
"Changed",
|
|
138
|
+
"Deprecated",
|
|
139
|
+
"Removed",
|
|
140
|
+
"Fixed",
|
|
141
|
+
"Security"
|
|
142
|
+
];
|
|
143
|
+
function parseChangeset(content) {
|
|
144
|
+
const entries = {};
|
|
145
|
+
let currentType = null;
|
|
146
|
+
for (const line of content.split("\n")) {
|
|
147
|
+
const typeMatch = line.match(/^###\s+(Added|Changed|Deprecated|Removed|Fixed|Security)\s*$/);
|
|
148
|
+
if (typeMatch) {
|
|
149
|
+
currentType = typeMatch[1];
|
|
150
|
+
if (!entries[currentType]) entries[currentType] = [];
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (currentType && line.startsWith("- ")) {
|
|
154
|
+
entries[currentType].push(line.slice(2).trim());
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { entries };
|
|
158
|
+
}
|
|
159
|
+
function renderEntries(entries) {
|
|
160
|
+
const parts = [];
|
|
161
|
+
for (const type of CHANGE_TYPES) {
|
|
162
|
+
const items = entries[type];
|
|
163
|
+
if (!items || items.length === 0) continue;
|
|
164
|
+
parts.push(`### ${type}
|
|
165
|
+
`);
|
|
166
|
+
for (const item of items) {
|
|
167
|
+
parts.push(`- ${item}`);
|
|
168
|
+
}
|
|
169
|
+
parts.push("");
|
|
170
|
+
}
|
|
171
|
+
return parts.join("\n");
|
|
172
|
+
}
|
|
173
|
+
function mergeChangesets(changesets) {
|
|
174
|
+
const merged = {};
|
|
175
|
+
for (const cs of changesets) {
|
|
176
|
+
for (const type of CHANGE_TYPES) {
|
|
177
|
+
const items = cs.entries[type];
|
|
178
|
+
if (!items || items.length === 0) continue;
|
|
179
|
+
if (!merged[type]) merged[type] = [];
|
|
180
|
+
merged[type].push(...items);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return merged;
|
|
184
|
+
}
|
|
185
|
+
function parseChangelog(content) {
|
|
186
|
+
const lines = content.split("\n");
|
|
187
|
+
let headerLines = [];
|
|
188
|
+
let releases = [];
|
|
189
|
+
let currentRelease = null;
|
|
190
|
+
let currentType = null;
|
|
191
|
+
let foundFirstRelease = false;
|
|
192
|
+
for (const line of lines) {
|
|
193
|
+
const releaseMatch = line.match(
|
|
194
|
+
/^##\s+\[([^\]]+)\](?:\s+-\s+(\d{4}-\d{2}-\d{2}))?\s*$/
|
|
195
|
+
);
|
|
196
|
+
if (releaseMatch) {
|
|
197
|
+
if (currentRelease) {
|
|
198
|
+
releases.push(currentRelease);
|
|
199
|
+
}
|
|
200
|
+
foundFirstRelease = true;
|
|
201
|
+
currentType = null;
|
|
202
|
+
currentRelease = {
|
|
203
|
+
version: releaseMatch[1],
|
|
204
|
+
date: releaseMatch[2],
|
|
205
|
+
entries: {}
|
|
206
|
+
};
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (!foundFirstRelease) {
|
|
210
|
+
headerLines.push(line);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (currentRelease) {
|
|
214
|
+
const typeMatch = line.match(
|
|
215
|
+
/^###\s+(Added|Changed|Deprecated|Removed|Fixed|Security)\s*$/
|
|
216
|
+
);
|
|
217
|
+
if (typeMatch) {
|
|
218
|
+
currentType = typeMatch[1];
|
|
219
|
+
if (!currentRelease.entries[currentType]) {
|
|
220
|
+
currentRelease.entries[currentType] = [];
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (currentType && line.startsWith("- ")) {
|
|
225
|
+
currentRelease.entries[currentType].push(line.slice(2).trim());
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (currentType && line.match(/^\s{2,}/) && currentRelease.entries[currentType].length > 0) {
|
|
229
|
+
const arr = currentRelease.entries[currentType];
|
|
230
|
+
arr[arr.length - 1] += "\n" + line;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (currentRelease) {
|
|
236
|
+
releases.push(currentRelease);
|
|
237
|
+
}
|
|
238
|
+
while (headerLines.length > 0 && headerLines[headerLines.length - 1].trim() === "") {
|
|
239
|
+
headerLines.pop();
|
|
240
|
+
}
|
|
241
|
+
return { header: headerLines.join("\n"), releases };
|
|
242
|
+
}
|
|
243
|
+
function renderChangelog(header, releases) {
|
|
244
|
+
const parts = [header, ""];
|
|
245
|
+
for (const release2 of releases) {
|
|
246
|
+
const heading = release2.version === "Unreleased" ? "## [Unreleased]" : `## [${release2.version}] - ${release2.date}`;
|
|
247
|
+
parts.push(heading);
|
|
248
|
+
parts.push("");
|
|
249
|
+
const body = renderEntries(release2.entries);
|
|
250
|
+
if (body) {
|
|
251
|
+
parts.push(body);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const result = parts.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
255
|
+
return result + "\n";
|
|
256
|
+
}
|
|
257
|
+
function insertUnreleased(parsed, newEntries) {
|
|
258
|
+
const releases = [...parsed.releases];
|
|
259
|
+
const unreleasedIdx = releases.findIndex((r) => r.version === "Unreleased");
|
|
260
|
+
if (unreleasedIdx === -1) {
|
|
261
|
+
releases.unshift({ version: "Unreleased", entries: newEntries });
|
|
262
|
+
} else {
|
|
263
|
+
const existing = releases[unreleasedIdx];
|
|
264
|
+
const merged = {};
|
|
265
|
+
for (const type of CHANGE_TYPES) {
|
|
266
|
+
const existingItems = existing.entries[type] ?? [];
|
|
267
|
+
const newItems = newEntries[type] ?? [];
|
|
268
|
+
if (existingItems.length > 0 || newItems.length > 0) {
|
|
269
|
+
merged[type] = [...existingItems, ...newItems];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
releases[unreleasedIdx] = { ...existing, entries: merged };
|
|
273
|
+
}
|
|
274
|
+
return { ...parsed, releases };
|
|
275
|
+
}
|
|
276
|
+
function promoteToRelease(parsed, version, date, newEntries) {
|
|
277
|
+
const releases = [...parsed.releases];
|
|
278
|
+
const unreleasedIdx = releases.findIndex((r) => r.version === "Unreleased");
|
|
279
|
+
const existingUnreleased = unreleasedIdx !== -1 ? releases[unreleasedIdx].entries : {};
|
|
280
|
+
const mergedEntries = {};
|
|
281
|
+
for (const type of CHANGE_TYPES) {
|
|
282
|
+
const a = existingUnreleased[type] ?? [];
|
|
283
|
+
const b = newEntries[type] ?? [];
|
|
284
|
+
if (a.length > 0 || b.length > 0) {
|
|
285
|
+
mergedEntries[type] = [...a, ...b];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const newRelease = { version, date, entries: mergedEntries };
|
|
289
|
+
if (unreleasedIdx !== -1) {
|
|
290
|
+
releases.splice(unreleasedIdx, 1);
|
|
291
|
+
}
|
|
292
|
+
releases.unshift(newRelease);
|
|
293
|
+
releases.unshift({ version: "Unreleased", entries: {} });
|
|
294
|
+
return { ...parsed, releases };
|
|
295
|
+
}
|
|
296
|
+
function today() {
|
|
297
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/utils/names.ts
|
|
301
|
+
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
302
|
+
import { join as join3 } from "path";
|
|
303
|
+
var ADJECTIVES = [
|
|
304
|
+
"ancient",
|
|
305
|
+
"brave",
|
|
306
|
+
"calm",
|
|
307
|
+
"dark",
|
|
308
|
+
"eager",
|
|
309
|
+
"fair",
|
|
310
|
+
"gentle",
|
|
311
|
+
"happy",
|
|
312
|
+
"idle",
|
|
313
|
+
"jolly",
|
|
314
|
+
"keen",
|
|
315
|
+
"lively",
|
|
316
|
+
"merry",
|
|
317
|
+
"noble",
|
|
318
|
+
"odd",
|
|
319
|
+
"plain",
|
|
320
|
+
"quiet",
|
|
321
|
+
"rapid",
|
|
322
|
+
"swift",
|
|
323
|
+
"tidy",
|
|
324
|
+
"ultra",
|
|
325
|
+
"vivid",
|
|
326
|
+
"warm",
|
|
327
|
+
"exact",
|
|
328
|
+
"young",
|
|
329
|
+
"amber",
|
|
330
|
+
"blunt",
|
|
331
|
+
"crisp",
|
|
332
|
+
"dull",
|
|
333
|
+
"early",
|
|
334
|
+
"faint",
|
|
335
|
+
"grand",
|
|
336
|
+
"harsh",
|
|
337
|
+
"icy",
|
|
338
|
+
"jazzy",
|
|
339
|
+
"kind",
|
|
340
|
+
"large",
|
|
341
|
+
"mild",
|
|
342
|
+
"neat",
|
|
343
|
+
"open",
|
|
344
|
+
"pale",
|
|
345
|
+
"quick",
|
|
346
|
+
"rare",
|
|
347
|
+
"slim",
|
|
348
|
+
"tall",
|
|
349
|
+
"urban",
|
|
350
|
+
"vast",
|
|
351
|
+
"wild",
|
|
352
|
+
"xeric",
|
|
353
|
+
"zesty"
|
|
354
|
+
];
|
|
355
|
+
var NOUNS = [
|
|
356
|
+
"anchor",
|
|
357
|
+
"bridge",
|
|
358
|
+
"castle",
|
|
359
|
+
"delta",
|
|
360
|
+
"ember",
|
|
361
|
+
"fjord",
|
|
362
|
+
"glacier",
|
|
363
|
+
"harbor",
|
|
364
|
+
"island",
|
|
365
|
+
"jungle",
|
|
366
|
+
"knoll",
|
|
367
|
+
"lagoon",
|
|
368
|
+
"meadow",
|
|
369
|
+
"nexus",
|
|
370
|
+
"ocean",
|
|
371
|
+
"peak",
|
|
372
|
+
"quarry",
|
|
373
|
+
"river",
|
|
374
|
+
"summit",
|
|
375
|
+
"tundra",
|
|
376
|
+
"uplift",
|
|
377
|
+
"valley",
|
|
378
|
+
"wetland",
|
|
379
|
+
"xenon",
|
|
380
|
+
"zenith",
|
|
381
|
+
"arrow",
|
|
382
|
+
"basin",
|
|
383
|
+
"canyon",
|
|
384
|
+
"dune",
|
|
385
|
+
"estuary",
|
|
386
|
+
"forest",
|
|
387
|
+
"gorge",
|
|
388
|
+
"heath",
|
|
389
|
+
"inlet",
|
|
390
|
+
"jetty",
|
|
391
|
+
"kelp",
|
|
392
|
+
"ledge",
|
|
393
|
+
"marsh",
|
|
394
|
+
"nova",
|
|
395
|
+
"orbit",
|
|
396
|
+
"pine",
|
|
397
|
+
"quartz",
|
|
398
|
+
"ridge",
|
|
399
|
+
"shore",
|
|
400
|
+
"trail",
|
|
401
|
+
"umbra",
|
|
402
|
+
"vortex",
|
|
403
|
+
"wave",
|
|
404
|
+
"xylem",
|
|
405
|
+
"yard"
|
|
406
|
+
];
|
|
407
|
+
function generateName(dir) {
|
|
408
|
+
const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
409
|
+
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
|
|
410
|
+
const base = `${adjective}-${noun}`;
|
|
411
|
+
if (!existsSync3(dir)) {
|
|
412
|
+
return base;
|
|
413
|
+
}
|
|
414
|
+
const existing = new Set(
|
|
415
|
+
readdirSync(dir).map((f) => f.replace(/\.md$/, ""))
|
|
416
|
+
);
|
|
417
|
+
if (!existing.has(base)) {
|
|
418
|
+
return base;
|
|
419
|
+
}
|
|
420
|
+
let counter = 2;
|
|
421
|
+
while (existing.has(`${base}-${counter}`)) {
|
|
422
|
+
counter++;
|
|
423
|
+
}
|
|
424
|
+
return `${base}-${counter}`;
|
|
425
|
+
}
|
|
426
|
+
function changesetPath(dir, name) {
|
|
427
|
+
return join3(dir, `${name}.md`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/commands/add.ts
|
|
431
|
+
async function add(cwd = process.cwd()) {
|
|
432
|
+
if (!isInitialized(cwd)) {
|
|
433
|
+
console.error(
|
|
434
|
+
"versionsmith is not initialized. Run `npx @versionsmith/cli init` first."
|
|
435
|
+
);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
const dir = getVersionsmithDir(cwd);
|
|
439
|
+
console.log("\nCreate a new changelog entry\n");
|
|
440
|
+
const selectedTypes = await checkbox({
|
|
441
|
+
message: "Select the type(s) of change (space to select, enter to confirm):",
|
|
442
|
+
choices: CHANGE_TYPES.map((t) => ({
|
|
443
|
+
name: t,
|
|
444
|
+
value: t,
|
|
445
|
+
description: typeDescription(t)
|
|
446
|
+
})),
|
|
447
|
+
validate: (v) => v.length > 0 ? true : "Select at least one change type."
|
|
448
|
+
});
|
|
449
|
+
const entries = {};
|
|
450
|
+
for (const type of selectedTypes) {
|
|
451
|
+
console.log(`
|
|
452
|
+
${type} \u2014 ${typeDescription(type)}`);
|
|
453
|
+
const items = [];
|
|
454
|
+
let addAnother = true;
|
|
455
|
+
while (addAnother) {
|
|
456
|
+
const description = await input2({
|
|
457
|
+
message: ` Describe the ${type.toLowerCase()} change:`,
|
|
458
|
+
validate: (v) => v.trim().length > 0 ? true : "Description cannot be empty."
|
|
459
|
+
});
|
|
460
|
+
items.push(description.trim());
|
|
461
|
+
addAnother = await confirm2({
|
|
462
|
+
message: ` Add another ${type.toLowerCase()} entry?`,
|
|
463
|
+
default: false
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
entries[type] = items;
|
|
467
|
+
}
|
|
468
|
+
console.log("\n--- Preview ---");
|
|
469
|
+
console.log(renderEntries(entries));
|
|
470
|
+
console.log("---------------\n");
|
|
471
|
+
const confirmed = await confirm2({
|
|
472
|
+
message: "Save this changelog entry?",
|
|
473
|
+
default: true
|
|
474
|
+
});
|
|
475
|
+
if (!confirmed) {
|
|
476
|
+
console.log("Aborted. No file written.");
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const name = generateName(dir);
|
|
480
|
+
const filePath = changesetPath(dir, name);
|
|
481
|
+
const content = renderEntries(entries);
|
|
482
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
483
|
+
console.log(`
|
|
484
|
+
Saved: .versionsmith/${name}.md`);
|
|
485
|
+
console.log(
|
|
486
|
+
"\nCommit this file alongside your changes. When ready to release, run:"
|
|
487
|
+
);
|
|
488
|
+
console.log(" npx @versionsmith/cli release");
|
|
489
|
+
}
|
|
490
|
+
function typeDescription(type) {
|
|
491
|
+
switch (type) {
|
|
492
|
+
case "Added":
|
|
493
|
+
return "New features";
|
|
494
|
+
case "Changed":
|
|
495
|
+
return "Changes in existing functionality";
|
|
496
|
+
case "Deprecated":
|
|
497
|
+
return "Soon-to-be removed features";
|
|
498
|
+
case "Removed":
|
|
499
|
+
return "Now removed features";
|
|
500
|
+
case "Fixed":
|
|
501
|
+
return "Bug fixes";
|
|
502
|
+
case "Security":
|
|
503
|
+
return "Vulnerability fixes";
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/commands/release.ts
|
|
508
|
+
import {
|
|
509
|
+
existsSync as existsSync4,
|
|
510
|
+
readdirSync as readdirSync2,
|
|
511
|
+
readFileSync as readFileSync2,
|
|
512
|
+
writeFileSync as writeFileSync4,
|
|
513
|
+
unlinkSync
|
|
514
|
+
} from "fs";
|
|
515
|
+
import { join as join4 } from "path";
|
|
516
|
+
async function release(options = {}) {
|
|
517
|
+
const cwd = options.cwd ?? process.cwd();
|
|
518
|
+
if (!isInitialized(cwd)) {
|
|
519
|
+
console.error(
|
|
520
|
+
"versionsmith is not initialized. Run `npx @versionsmith/cli init` first."
|
|
521
|
+
);
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
const config = readConfig(cwd);
|
|
525
|
+
const dir = getVersionsmithDir(cwd);
|
|
526
|
+
const changelogPath = join4(cwd, config.changelog);
|
|
527
|
+
const files = existsSync4(dir) ? readdirSync2(dir).filter(
|
|
528
|
+
(f) => f.endsWith(".md") && f !== ".gitkeep"
|
|
529
|
+
) : [];
|
|
530
|
+
if (files.length === 0 && !options.version) {
|
|
531
|
+
console.log("No changelog entries found in .versionsmith/");
|
|
532
|
+
console.log("Run `npx @versionsmith/cli` to create one.");
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const changesets = files.map((f) => {
|
|
536
|
+
const content = readFileSync2(join4(dir, f), "utf-8");
|
|
537
|
+
return parseChangeset(content);
|
|
538
|
+
});
|
|
539
|
+
const merged = mergeChangesets(changesets);
|
|
540
|
+
let parsed = { header: config.header, releases: [] };
|
|
541
|
+
if (existsSync4(changelogPath)) {
|
|
542
|
+
const existing = readFileSync2(changelogPath, "utf-8");
|
|
543
|
+
parsed = parseChangelog(existing);
|
|
544
|
+
if (!parsed.header.trim()) {
|
|
545
|
+
parsed = { ...parsed, header: config.header };
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const dateStr = options.date ?? today();
|
|
549
|
+
let updated;
|
|
550
|
+
if (options.version) {
|
|
551
|
+
updated = promoteToRelease(parsed, options.version, dateStr, merged);
|
|
552
|
+
console.log(`
|
|
553
|
+
Releasing version ${options.version} (${dateStr})
|
|
554
|
+
`);
|
|
555
|
+
} else {
|
|
556
|
+
updated = insertUnreleased(parsed, merged);
|
|
557
|
+
console.log(`
|
|
558
|
+
Merging entries into [Unreleased]
|
|
559
|
+
`);
|
|
560
|
+
}
|
|
561
|
+
const rendered = renderChangelog(updated.header, updated.releases);
|
|
562
|
+
writeFileSync4(changelogPath, rendered, "utf-8");
|
|
563
|
+
for (const f of files) {
|
|
564
|
+
unlinkSync(join4(dir, f));
|
|
565
|
+
}
|
|
566
|
+
console.log(`Updated ${config.changelog}`);
|
|
567
|
+
if (files.length > 0) {
|
|
568
|
+
console.log(`Removed ${files.length} changeset file(s):
|
|
569
|
+
`);
|
|
570
|
+
for (const f of files) {
|
|
571
|
+
console.log(` - .versionsmith/${f}`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
console.log("\nDone! Review your changelog and commit the changes.");
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/index.ts
|
|
578
|
+
var args = process.argv.slice(2);
|
|
579
|
+
var command = args[0];
|
|
580
|
+
function parseVersion(args2) {
|
|
581
|
+
const idx = args2.indexOf("--version");
|
|
582
|
+
if (idx !== -1 && args2[idx + 1]) {
|
|
583
|
+
return args2[idx + 1];
|
|
584
|
+
}
|
|
585
|
+
const flag = args2.find((a) => a.startsWith("--version="));
|
|
586
|
+
if (flag) {
|
|
587
|
+
return flag.split("=")[1];
|
|
588
|
+
}
|
|
589
|
+
return void 0;
|
|
590
|
+
}
|
|
591
|
+
function showHelp() {
|
|
592
|
+
console.log(`
|
|
593
|
+
versionsmith \u2014 conflict-free changelog management
|
|
594
|
+
|
|
595
|
+
USAGE
|
|
596
|
+
npx @versionsmith/cli [command] [options]
|
|
597
|
+
|
|
598
|
+
COMMANDS
|
|
599
|
+
(no command) Create a new changelog entry interactively
|
|
600
|
+
add Alias for the default command
|
|
601
|
+
init Initialize versionsmith in the current directory
|
|
602
|
+
release Merge changeset entries into CHANGELOG.md
|
|
603
|
+
|
|
604
|
+
OPTIONS
|
|
605
|
+
--version <version> (release only) Create a versioned release entry
|
|
606
|
+
--help, -h Show this help message
|
|
607
|
+
|
|
608
|
+
EXAMPLES
|
|
609
|
+
npx @versionsmith/cli init
|
|
610
|
+
npx @versionsmith/cli
|
|
611
|
+
npx @versionsmith/cli add
|
|
612
|
+
npx @versionsmith/cli release
|
|
613
|
+
npx @versionsmith/cli release --version 1.2.0
|
|
614
|
+
`);
|
|
615
|
+
}
|
|
616
|
+
async function main() {
|
|
617
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
618
|
+
showHelp();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
switch (command) {
|
|
622
|
+
case "init":
|
|
623
|
+
await init();
|
|
624
|
+
break;
|
|
625
|
+
case "release": {
|
|
626
|
+
const version = parseVersion(args.slice(1));
|
|
627
|
+
await release({ version });
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
case "add":
|
|
631
|
+
case void 0:
|
|
632
|
+
await add();
|
|
633
|
+
break;
|
|
634
|
+
default:
|
|
635
|
+
console.error(`Unknown command: ${command}`);
|
|
636
|
+
showHelp();
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
main().catch((err) => {
|
|
641
|
+
if (err instanceof Error && (err.message.includes("force closed") || err.name === "ExitPromptError")) {
|
|
642
|
+
console.log("\nAborted.");
|
|
643
|
+
process.exit(0);
|
|
644
|
+
}
|
|
645
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
646
|
+
process.exit(1);
|
|
647
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@versionsmith/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A simple, conflict-free changelog management tool following Keep a Changelog",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"versionsmith": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"test": "node --test --experimental-strip-types src/__tests__/*.test.ts",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@inquirer/prompts": "^7.5.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0",
|
|
23
|
+
"tsup": "^8.0.0",
|
|
24
|
+
"typescript": "^5.5.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.0.0"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
}
|
|
32
|
+
}
|