@afoures/auto-release 0.2.10 → 0.2.12
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/lib/change-file.mjs +13 -20
- package/dist/lib/commands/check.mjs +2 -2
- package/dist/lib/commands/generate-release-pr.mjs +9 -15
- package/dist/lib/commands/manual-release.mjs +11 -18
- package/dist/lib/config.mjs +1 -1
- package/dist/lib/formatter.mjs +21 -25
- package/dist/lib/utils/fs.mjs +11 -1
- package/dist/lib/utils/git.mjs +1 -1
- package/dist/lib/utils/mdast.mjs +27 -0
- package/dist/lib/versioning/types.d.mts +1 -1
- package/package.json +1 -1
package/dist/lib/change-file.mjs
CHANGED
|
@@ -29,34 +29,24 @@ var ChangeFile = class {
|
|
|
29
29
|
};
|
|
30
30
|
async function save_change_file(change_file, to) {
|
|
31
31
|
const file_path = join(to, change_file.filename);
|
|
32
|
-
|
|
32
|
+
const text = change_file.summary.split("\n").map((line) => line.slice(2)).join("\n");
|
|
33
|
+
await write_file(file_path, text);
|
|
33
34
|
return file_path;
|
|
34
35
|
}
|
|
35
36
|
async function parse_change_file(path) {
|
|
36
37
|
const filename = basename(path);
|
|
37
38
|
const match = CHANGE_FILENAME_REGEX.exec(filename);
|
|
38
39
|
if (!match) return /* @__PURE__ */ new Error(`Invalid change filename format: ${path} (expected: <kind>.<slug>.md)`);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
slug: match.groups.slug,
|
|
46
|
-
kind: match.groups.kind,
|
|
47
|
-
summary: first_line
|
|
48
|
-
});
|
|
49
|
-
const remaining_lines = non_empty_lines.slice(1);
|
|
50
|
-
const invalid_lines = [];
|
|
51
|
-
remaining_lines.forEach((line, index) => {
|
|
52
|
-
if (!line.trim().match(/^[-*+]\s/)) invalid_lines.push(index + 2);
|
|
53
|
-
});
|
|
54
|
-
if (invalid_lines.length > 0) return /* @__PURE__ */ new Error(`Change file ${path} has invalid format: lines ${invalid_lines.join(", ")} should be list items (starting with -, *, or +)`);
|
|
55
|
-
const summary = [first_line, ...remaining_lines].join("\n");
|
|
40
|
+
let file_content = await read_file(path);
|
|
41
|
+
file_content = file_content?.trim() ?? null;
|
|
42
|
+
if (file_content === null) return /* @__PURE__ */ new Error(`Change file is missing: ${path}`);
|
|
43
|
+
if (!file_content) return /* @__PURE__ */ new Error("Change file is empty");
|
|
44
|
+
const [title, ...rest] = file_content.split("\n");
|
|
45
|
+
const text = [`- ${title}`, ...rest.map((line) => ` ${line}`)].join("\n");
|
|
56
46
|
return new ChangeFile({
|
|
57
47
|
slug: match.groups.slug,
|
|
58
48
|
kind: match.groups.kind,
|
|
59
|
-
summary
|
|
49
|
+
summary: text
|
|
60
50
|
});
|
|
61
51
|
}
|
|
62
52
|
async function find_change_files(folder, { allowed_kinds }) {
|
|
@@ -66,7 +56,10 @@ async function find_change_files(folder, { allowed_kinds }) {
|
|
|
66
56
|
for (const filename of files) {
|
|
67
57
|
const file_path = join(folder, filename);
|
|
68
58
|
const change_file_or_error = await parse_change_file(file_path);
|
|
69
|
-
if (change_file_or_error instanceof Error)
|
|
59
|
+
if (change_file_or_error instanceof Error) {
|
|
60
|
+
warnings.push(`Change file ${file_path} is invalid: ${change_file_or_error.message}`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
70
63
|
const change_file = change_file_or_error;
|
|
71
64
|
if (!allowed_kinds.includes(change_file.kind)) {
|
|
72
65
|
warnings.push(`Change file ${file_path} has an invalid kind: ${change_file.kind}`);
|
|
@@ -72,7 +72,7 @@ const check = create_command({
|
|
|
72
72
|
const valid = errors.length === 0;
|
|
73
73
|
if (valid) logger.success("All validations passed!");
|
|
74
74
|
else {
|
|
75
|
-
logger.error("
|
|
75
|
+
logger.error("Detected errors:");
|
|
76
76
|
errors.forEach((err) => logger.error(` ${err}`));
|
|
77
77
|
}
|
|
78
78
|
if (valid) return {
|
|
@@ -81,7 +81,7 @@ const check = create_command({
|
|
|
81
81
|
};
|
|
82
82
|
else return {
|
|
83
83
|
status: "error",
|
|
84
|
-
error:
|
|
84
|
+
error: "Validation failed"
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
});
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { create_command } from "../cli.mjs";
|
|
2
2
|
import { create_logger } from "../utils/logger.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { delete_all_files_from_folder, read_file, write_file } from "../utils/fs.mjs";
|
|
4
4
|
import { find_nearest_config } from "../config.mjs";
|
|
5
5
|
import { find_change_files } from "../change-file.mjs";
|
|
6
6
|
import { diff, reset } from "../utils/git.mjs";
|
|
7
7
|
import { compute_current_version } from "../utils/version.mjs";
|
|
8
|
+
import { parse_markdown } from "../utils/mdast.mjs";
|
|
8
9
|
import { join } from "node:path";
|
|
9
|
-
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
10
|
-
import { gfmFromMarkdown } from "mdast-util-gfm";
|
|
11
|
-
import { gfm } from "micromark-extension-gfm";
|
|
12
10
|
|
|
13
11
|
//#region src/lib/commands/generate-release-pr.ts
|
|
14
12
|
const generate_release_pr = create_command({
|
|
@@ -47,7 +45,8 @@ const generate_release_pr = create_command({
|
|
|
47
45
|
message: "No apps to release"
|
|
48
46
|
};
|
|
49
47
|
for (const app of filtered_applications) {
|
|
50
|
-
const
|
|
48
|
+
const changes_dir = join(config.changes_dir, app.name);
|
|
49
|
+
const changes = await find_change_files(changes_dir, { allowed_kinds: app.versioning.allowed_changes });
|
|
51
50
|
if (changes.warnings.length > 0) for (const warning of changes.warnings) logger.warn(warning);
|
|
52
51
|
const current_version = await compute_current_version(app, { get_file_content: (file_path) => read_file(file_path) }) ?? app.versioning.initial_version;
|
|
53
52
|
const next_version = app.versioning.bump({
|
|
@@ -66,14 +65,11 @@ const generate_release_pr = create_command({
|
|
|
66
65
|
for (const [kind, kind_changes] of changes_by_kind.entries()) {
|
|
67
66
|
const label = display_map[kind]?.plural ?? display_map[kind]?.singular ?? kind;
|
|
68
67
|
message_lines.push(`\n${label}:`);
|
|
69
|
-
for (const change of kind_changes) message_lines.push(`
|
|
68
|
+
for (const change of kind_changes) message_lines.push(` ${change.summary}`);
|
|
70
69
|
}
|
|
71
70
|
logger.note(`Release ${app.name} ${next_version}`, message_lines.join("\n"));
|
|
72
71
|
if (dry_run) continue;
|
|
73
|
-
|
|
74
|
-
const change_file_path = join(config.changes_dir, app.name, change.filename);
|
|
75
|
-
await delete_file(change_file_path);
|
|
76
|
-
}
|
|
72
|
+
await delete_all_files_from_folder(changes_dir);
|
|
77
73
|
for (const component of app.components) for (const part of component.parts) {
|
|
78
74
|
const initial_content = await read_file(part.file);
|
|
79
75
|
if (initial_content === null) continue;
|
|
@@ -81,11 +77,9 @@ const generate_release_pr = create_command({
|
|
|
81
77
|
await write_file(part.file, updated_content);
|
|
82
78
|
}
|
|
83
79
|
const formatter = app.versioning.formatter;
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
const changelog = formatter.transform_markdown(changelog_as_mdast);
|
|
80
|
+
const initial_changelog_content = await read_file(app.changelog);
|
|
81
|
+
const changelog_as_mdast = parse_markdown(initial_changelog_content ?? "");
|
|
82
|
+
const changelog = formatter.transform_markdown(changelog_as_mdast, initial_changelog_content ?? "");
|
|
89
83
|
const updated_changelog_content = formatter.format_changelog({
|
|
90
84
|
...changelog,
|
|
91
85
|
releases: [{
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { create_command } from "../cli.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { delete_all_files_from_folder, read_file, write_file } from "../utils/fs.mjs";
|
|
3
3
|
import { find_nearest_config } from "../config.mjs";
|
|
4
4
|
import { find_change_files } from "../change-file.mjs";
|
|
5
5
|
import { commit, create_tag, get_current_branch, get_head_and_parent_shas, get_staged_diff, has_uncommitted_changes, reset, stage_files, tag_exists } from "../utils/git.mjs";
|
|
6
6
|
import { compute_current_version } from "../utils/version.mjs";
|
|
7
|
+
import { parse_markdown } from "../utils/mdast.mjs";
|
|
7
8
|
import { is_ci } from "../utils/branch-protection.mjs";
|
|
8
9
|
import { cancel, confirm, intro, isCancel, log, note, outro, select, text } from "@clack/prompts";
|
|
9
10
|
import { join } from "node:path";
|
|
10
|
-
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
11
|
-
import { gfmFromMarkdown } from "mdast-util-gfm";
|
|
12
|
-
import { gfm } from "micromark-extension-gfm";
|
|
13
11
|
|
|
14
12
|
//#region src/lib/commands/manual-release.ts
|
|
15
13
|
const manual_release = create_command({
|
|
@@ -128,14 +126,14 @@ const manual_release = create_command({
|
|
|
128
126
|
log.warn(`Could not check remote tag existence: ${error.message}`);
|
|
129
127
|
log.warn("Continuing with local tag check only...");
|
|
130
128
|
}
|
|
131
|
-
const changes_summary = changes.list.map((change) => `
|
|
129
|
+
const changes_summary = changes.list.map((change) => ` ${change.summary.split("\n").join("\n ")}`).join("\n");
|
|
132
130
|
note(`Manual release plan:
|
|
133
131
|
- App: ${app_name}
|
|
134
132
|
- Current version: ${current_version}
|
|
135
133
|
- Next version: ${next_version}
|
|
136
134
|
- Tag: ${tag}
|
|
137
135
|
- Change files:
|
|
138
|
-
${changes_summary}`, "Release details");
|
|
136
|
+
${changes_summary || " No changes in this release."}`, "Release details");
|
|
139
137
|
const proceed_confirmation = await confirm({
|
|
140
138
|
message: "Proceed with generating changelog and version bump?",
|
|
141
139
|
initialValue: false
|
|
@@ -146,12 +144,9 @@ ${changes_summary}`, "Release details");
|
|
|
146
144
|
}
|
|
147
145
|
const files_to_stage = [];
|
|
148
146
|
const changes_dir = join(config.changes_dir, app_name);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
files_to_stage.push(change_file_path);
|
|
153
|
-
}
|
|
154
|
-
log.success("Deleted change files");
|
|
147
|
+
const deleted_change_files = await delete_all_files_from_folder(changes_dir);
|
|
148
|
+
files_to_stage.push(...deleted_change_files);
|
|
149
|
+
log.success(`Deleted ${deleted_change_files.length} change file(s)`);
|
|
155
150
|
for (const component of app.components) for (const part of component.parts) {
|
|
156
151
|
const initial_content = await read_file(part.file);
|
|
157
152
|
if (initial_content === null) continue;
|
|
@@ -161,11 +156,9 @@ ${changes_summary}`, "Release details");
|
|
|
161
156
|
}
|
|
162
157
|
log.success("Updated component versions");
|
|
163
158
|
const formatter = app.versioning.formatter;
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
});
|
|
168
|
-
const changelog = formatter.transform_markdown(changelog_as_mdast);
|
|
159
|
+
const initial_changelog_content = await read_file(app.changelog);
|
|
160
|
+
const changelog_as_mdast = parse_markdown(initial_changelog_content ?? "");
|
|
161
|
+
const changelog = formatter.transform_markdown(changelog_as_mdast, initial_changelog_content ?? "");
|
|
169
162
|
const updated_changelog_content = formatter.format_changelog({
|
|
170
163
|
...changelog,
|
|
171
164
|
releases: [{
|
|
@@ -195,7 +188,7 @@ ${changes_summary}`, "Release details");
|
|
|
195
188
|
if (diff) note(diff, "Changes to be committed");
|
|
196
189
|
const confirm_commit = await confirm({
|
|
197
190
|
message: "Review the changes above. Proceed with commit?",
|
|
198
|
-
initialValue:
|
|
191
|
+
initialValue: false
|
|
199
192
|
});
|
|
200
193
|
if (isCancel(confirm_commit) || !confirm_commit) {
|
|
201
194
|
await reset(root);
|
package/dist/lib/config.mjs
CHANGED
package/dist/lib/formatter.mjs
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { ChangeFile } from "./change-file.mjs";
|
|
2
|
+
import { as_text, to_plain_text } from "./utils/mdast.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/lib/formatter.ts
|
|
4
|
-
function to_plain_text(node) {
|
|
5
|
-
if ("children" in node && Array.isArray(node.children)) return node.children.map((child) => to_plain_text(child)).join("");
|
|
6
|
-
if ("value" in node && typeof node.value === "string") return node.value;
|
|
7
|
-
return "";
|
|
8
|
-
}
|
|
9
5
|
function normalize_label(label) {
|
|
10
6
|
return label.trim().toLowerCase();
|
|
11
7
|
}
|
|
@@ -20,13 +16,6 @@ function resolve_change_kind({ heading_text, allowed_changes, display_map }) {
|
|
|
20
16
|
}
|
|
21
17
|
return null;
|
|
22
18
|
}
|
|
23
|
-
function extract_list_item_text(item) {
|
|
24
|
-
const [title, ...description] = to_plain_text(item).trim().split("\n").map((line) => line.trim()).filter(Boolean);
|
|
25
|
-
return {
|
|
26
|
-
title: title ?? "",
|
|
27
|
-
description
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
19
|
function create_empty_changelog() {
|
|
31
20
|
return {
|
|
32
21
|
root: {
|
|
@@ -46,7 +35,7 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
46
35
|
return map;
|
|
47
36
|
}, {});
|
|
48
37
|
return {
|
|
49
|
-
transform_markdown(tree) {
|
|
38
|
+
transform_markdown(tree, original_text) {
|
|
50
39
|
const changelog = create_empty_changelog();
|
|
51
40
|
let current_release = null;
|
|
52
41
|
let current_kind = null;
|
|
@@ -55,7 +44,7 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
55
44
|
};
|
|
56
45
|
for (const node of tree.children) {
|
|
57
46
|
if (node.type === "heading" && node.depth === 1) {
|
|
58
|
-
changelog.root.title =
|
|
47
|
+
changelog.root.title = as_text(node);
|
|
59
48
|
continue;
|
|
60
49
|
}
|
|
61
50
|
if (node.type === "heading" && node.depth === 2) {
|
|
@@ -78,17 +67,19 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
78
67
|
if (node.type === "list" && current_release) {
|
|
79
68
|
const kind_for_items = current_kind ?? allowed_changes[0] ?? "default";
|
|
80
69
|
for (const item of node.children) {
|
|
81
|
-
const
|
|
82
|
-
|
|
70
|
+
const start = item.position?.start.offset ?? 0;
|
|
71
|
+
const end = item.position?.end.offset ?? 0;
|
|
72
|
+
const summary = original_text.slice(start, end).trim();
|
|
73
|
+
if (!summary) continue;
|
|
83
74
|
current_release.changes.push(new ChangeFile({
|
|
84
75
|
kind: kind_for_items,
|
|
85
|
-
summary
|
|
76
|
+
summary
|
|
86
77
|
}));
|
|
87
78
|
}
|
|
88
79
|
continue;
|
|
89
80
|
}
|
|
90
81
|
if (!current_release && node.type === "paragraph") {
|
|
91
|
-
const text =
|
|
82
|
+
const text = as_text(node);
|
|
92
83
|
if (text) changelog.root.description.push(text);
|
|
93
84
|
}
|
|
94
85
|
}
|
|
@@ -97,10 +88,12 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
97
88
|
},
|
|
98
89
|
format_changelog(changelog, context) {
|
|
99
90
|
const lines = [];
|
|
100
|
-
if (changelog.root.title) lines.push(
|
|
101
|
-
else lines.push(`# \`${context.app.name}\`
|
|
102
|
-
if (changelog.root.description.length > 0)
|
|
103
|
-
|
|
91
|
+
if (changelog.root.title) lines.push(changelog.root.title);
|
|
92
|
+
else lines.push(`# \`${context.app.name}\` changelog`);
|
|
93
|
+
if (changelog.root.description.length > 0) {
|
|
94
|
+
console.log(changelog.root.description);
|
|
95
|
+
lines.push(changelog.root.description.join("\n"));
|
|
96
|
+
} else lines.push(`This is the changelog for \`${context.app.name}\`.`);
|
|
104
97
|
for (const release of changelog.releases) {
|
|
105
98
|
const release_lines = [`## ${release.version}`, ""];
|
|
106
99
|
for (const change_kind of allowed_changes) {
|
|
@@ -109,9 +102,9 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
109
102
|
const labels = resolved_display_map[change_kind];
|
|
110
103
|
const heading = labels?.plural ?? labels?.singular ?? change_kind;
|
|
111
104
|
release_lines.push(`### ${heading}`, "");
|
|
112
|
-
for (const change of kind_changes) release_lines.push(
|
|
105
|
+
for (const change of kind_changes) release_lines.push(change.summary, "");
|
|
113
106
|
}
|
|
114
|
-
if (release.changes.length === 0) release_lines.push("No changes in this release.");
|
|
107
|
+
if (release.changes.length === 0) release_lines.push("No changes in this release.", "");
|
|
115
108
|
lines.push(release_lines.join("\n"));
|
|
116
109
|
}
|
|
117
110
|
return lines.join("\n\n");
|
|
@@ -138,7 +131,10 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
138
131
|
const heading = labels?.plural ?? labels?.singular ?? kind;
|
|
139
132
|
lines.push("");
|
|
140
133
|
lines.push(`## ${heading}`);
|
|
141
|
-
for (const change of items)
|
|
134
|
+
for (const change of items) {
|
|
135
|
+
lines.push(change.summary);
|
|
136
|
+
lines.push("");
|
|
137
|
+
}
|
|
142
138
|
}
|
|
143
139
|
if (grouped.size === 0) {
|
|
144
140
|
lines.push("");
|
package/dist/lib/utils/fs.mjs
CHANGED
|
@@ -30,6 +30,16 @@ async function delete_file(path) {
|
|
|
30
30
|
throw new Error(`Failed to delete file ${path}`, { cause: error });
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
async function delete_all_files_from_folder(folder) {
|
|
34
|
+
const files = await list_files(folder);
|
|
35
|
+
const deleted_files = [];
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const file_path = join(folder, file);
|
|
38
|
+
await delete_file(file_path);
|
|
39
|
+
deleted_files.push(file_path);
|
|
40
|
+
}
|
|
41
|
+
return deleted_files;
|
|
42
|
+
}
|
|
33
43
|
async function list_files(dir, options) {
|
|
34
44
|
try {
|
|
35
45
|
const files = await readdir(dir);
|
|
@@ -61,4 +71,4 @@ async function list_files(dir, options) {
|
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
//#endregion
|
|
64
|
-
export {
|
|
74
|
+
export { delete_all_files_from_folder, exists, list_files, read_file, write_file };
|
package/dist/lib/utils/git.mjs
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { toMarkdown } from "mdast-util-to-markdown";
|
|
2
|
+
import { gfmFromMarkdown, gfmToMarkdown } from "mdast-util-gfm";
|
|
3
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
4
|
+
import { gfm } from "micromark-extension-gfm";
|
|
5
|
+
|
|
6
|
+
//#region src/lib/utils/mdast.ts
|
|
7
|
+
function as_text(node, options) {
|
|
8
|
+
return toMarkdown(node, {
|
|
9
|
+
extensions: [gfmToMarkdown()],
|
|
10
|
+
bullet: "-",
|
|
11
|
+
...options
|
|
12
|
+
}).trim();
|
|
13
|
+
}
|
|
14
|
+
function parse_markdown(text) {
|
|
15
|
+
return fromMarkdown(text, {
|
|
16
|
+
extensions: [gfm()],
|
|
17
|
+
mdastExtensions: [gfmFromMarkdown()]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function to_plain_text(node) {
|
|
21
|
+
if ("children" in node && Array.isArray(node.children)) return node.children.map((child) => to_plain_text(child)).join("");
|
|
22
|
+
if ("value" in node && typeof node.value === "string") return node.value;
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { as_text, parse_markdown, to_plain_text };
|
|
@@ -17,7 +17,7 @@ type Formatter<change_kinds extends string = string, parsed_changelog extends {
|
|
|
17
17
|
* Transform the mdast tree into a custom changelog data structure.
|
|
18
18
|
* @param tree - The markdown tree to transform
|
|
19
19
|
*/
|
|
20
|
-
transform_markdown(tree: Root): parsed_changelog;
|
|
20
|
+
transform_markdown(tree: Root, original_text: string): parsed_changelog;
|
|
21
21
|
/**
|
|
22
22
|
* Format the changelog data into markdown.
|
|
23
23
|
* The changelog releases will be sorted by version in ascending order automatically.
|