@afoures/auto-release 0.2.12 → 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 +30 -269
- package/dist/lib/commands/check.mjs +44 -12
- package/dist/lib/commands/generate-release-pr.mjs +115 -75
- package/dist/lib/commands/init.mjs +40 -199
- package/dist/lib/commands/list.mjs +24 -15
- package/dist/lib/commands/manual-release.mjs +51 -32
- package/dist/lib/commands/record-change.mjs +19 -19
- package/dist/lib/commands/tag-release-commit.mjs +36 -30
- package/dist/lib/config.d.mts +8 -2
- package/dist/lib/config.mjs +16 -14
- package/dist/lib/formatter.mjs +14 -14
- package/dist/lib/types.d.mts +25 -5
- package/dist/lib/utils/branch-protection.mjs +0 -3
- package/dist/lib/utils/group.mjs +19 -0
- package/dist/lib/utils/version.mjs +3 -3
- package/dist/lib/versioning/types.d.mts +4 -4
- package/docs/change-file-anatomy.md +31 -0
- package/docs/commands.md +109 -0
- package/docs/configuration.md +195 -0
- package/docs/lexicon.md +23 -0
- package/docs/recommended-usage.md +155 -0
- package/package.json +18 -15
|
@@ -7,17 +7,17 @@ import { relative } from "node:path";
|
|
|
7
7
|
|
|
8
8
|
//#region src/lib/commands/tag-release-commit.ts
|
|
9
9
|
/**
|
|
10
|
-
* Get the version of
|
|
10
|
+
* Get the version of a project at a specific git revision
|
|
11
11
|
*/
|
|
12
|
-
async function
|
|
12
|
+
async function get_project_version_at_revision(project, root, revision) {
|
|
13
13
|
try {
|
|
14
|
-
const version = await compute_current_version(
|
|
14
|
+
const version = await compute_current_version(project, { get_file_content: (file_path) => {
|
|
15
15
|
const relative_path = relative(root, file_path);
|
|
16
16
|
return read_file_at_revision(root, revision, relative_path);
|
|
17
|
-
} }) ??
|
|
18
|
-
if (!
|
|
17
|
+
} }) ?? project.versioning.initial_version;
|
|
18
|
+
if (!project.versioning.validate({ version })) return {
|
|
19
19
|
ok: false,
|
|
20
|
-
error: `Invalid version format for ${
|
|
20
|
+
error: `Invalid version format for ${project.name} at revision ${revision}: ${version}`
|
|
21
21
|
};
|
|
22
22
|
return {
|
|
23
23
|
ok: true,
|
|
@@ -26,7 +26,7 @@ async function get_app_version_at_revision(app, root, revision) {
|
|
|
26
26
|
} catch (error) {
|
|
27
27
|
return {
|
|
28
28
|
ok: false,
|
|
29
|
-
error: `Failed to get version for ${
|
|
29
|
+
error: `Failed to get version for ${project.name} at revision ${revision}: ${error.message}`
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -60,34 +60,37 @@ const tag_release_commit = create_command({
|
|
|
60
60
|
status: "success",
|
|
61
61
|
message: "HEAD has no parent commit - nothing to tag"
|
|
62
62
|
};
|
|
63
|
-
const
|
|
64
|
-
for (const
|
|
65
|
-
const head_result = await
|
|
63
|
+
const changed_projects = [];
|
|
64
|
+
for (const project of config.managed_projects) {
|
|
65
|
+
const head_result = await get_project_version_at_revision(project, root, head_sha);
|
|
66
66
|
if (!head_result.ok) return {
|
|
67
67
|
status: "error",
|
|
68
|
-
error: `Failed to get HEAD version for ${
|
|
68
|
+
error: `Failed to get HEAD version for ${project.name}: ${head_result.error}`
|
|
69
69
|
};
|
|
70
|
-
const base_result = await
|
|
70
|
+
const base_result = await get_project_version_at_revision(project, root, base_sha);
|
|
71
71
|
if (!base_result.ok) return {
|
|
72
72
|
status: "error",
|
|
73
|
-
error: `Failed to get base version for ${
|
|
73
|
+
error: `Failed to get base version for ${project.name}: ${base_result.error}`
|
|
74
74
|
};
|
|
75
|
-
if (head_result.version !== base_result.version)
|
|
76
|
-
|
|
75
|
+
if (head_result.version !== base_result.version) changed_projects.push({
|
|
76
|
+
project,
|
|
77
77
|
head_version: head_result.version,
|
|
78
78
|
base_version: base_result.version
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
-
if (
|
|
81
|
+
if (changed_projects.length === 0) return {
|
|
82
82
|
status: "success",
|
|
83
83
|
message: "No version changes detected"
|
|
84
84
|
};
|
|
85
|
-
logger.info(`Detected version changes in ${
|
|
86
|
-
for (const {
|
|
85
|
+
logger.info(`Detected version changes in ${changed_projects.length} project(s):`);
|
|
86
|
+
for (const { project, head_version, base_version } of changed_projects) logger.info(` ${project.name}: ${base_version} → ${head_version}`);
|
|
87
87
|
if (dry_run) {
|
|
88
88
|
logger.info("\nDry run - would create tags and releases:");
|
|
89
|
-
for (const {
|
|
90
|
-
const tag =
|
|
89
|
+
for (const { project, head_version } of changed_projects) {
|
|
90
|
+
const tag = config.git.tag_generator({
|
|
91
|
+
project: { name: project.name },
|
|
92
|
+
version: head_version
|
|
93
|
+
});
|
|
91
94
|
logger.info(` - Tag: ${tag}`);
|
|
92
95
|
logger.info(` - Release: ${tag}`);
|
|
93
96
|
}
|
|
@@ -96,15 +99,18 @@ const tag_release_commit = create_command({
|
|
|
96
99
|
message: "Dry run completed - no changes were made"
|
|
97
100
|
};
|
|
98
101
|
}
|
|
99
|
-
const
|
|
102
|
+
const tagged_projects = [];
|
|
100
103
|
const errors = [];
|
|
101
|
-
for (const {
|
|
102
|
-
const tag =
|
|
104
|
+
for (const { project, head_version } of changed_projects) {
|
|
105
|
+
const tag = config.git.tag_generator({
|
|
106
|
+
project: { name: project.name },
|
|
107
|
+
version: head_version
|
|
108
|
+
});
|
|
103
109
|
try {
|
|
104
110
|
const existing_tag = await config.git.platform.get_tag({ tag });
|
|
105
111
|
if (existing_tag !== null) if (existing_tag.commit_sha === head_sha) {
|
|
106
112
|
logger.info(`Tag ${tag} already exists on commit ${head_sha} - skipping`);
|
|
107
|
-
|
|
113
|
+
tagged_projects.push(tag);
|
|
108
114
|
continue;
|
|
109
115
|
} else {
|
|
110
116
|
errors.push(`Tag ${tag} already exists but points to different commit (${existing_tag.commit_sha} vs ${head_sha})`);
|
|
@@ -119,16 +125,16 @@ const tag_release_commit = create_command({
|
|
|
119
125
|
tag,
|
|
120
126
|
release: {
|
|
121
127
|
name: tag,
|
|
122
|
-
body:
|
|
123
|
-
|
|
124
|
-
name:
|
|
125
|
-
changelog:
|
|
128
|
+
body: project.versioning.formatter.generate_release_notes({
|
|
129
|
+
project: {
|
|
130
|
+
name: project.name,
|
|
131
|
+
changelog: project.changelog
|
|
126
132
|
},
|
|
127
133
|
version: head_version
|
|
128
134
|
})
|
|
129
135
|
}
|
|
130
136
|
});
|
|
131
|
-
|
|
137
|
+
tagged_projects.push(tag);
|
|
132
138
|
logger.success(`Tagged and released ${tag}`);
|
|
133
139
|
} catch (error) {
|
|
134
140
|
errors.push(`Failed to tag/release ${tag}: ${error.message}`);
|
|
@@ -140,7 +146,7 @@ const tag_release_commit = create_command({
|
|
|
140
146
|
};
|
|
141
147
|
return {
|
|
142
148
|
status: "success",
|
|
143
|
-
message:
|
|
149
|
+
message: tagged_projects.length === 1 ? `Tagged 1 project: ${tagged_projects[0]}` : `Tagged ${tagged_projects.length} projects: ${tagged_projects.join(", ")}`
|
|
144
150
|
};
|
|
145
151
|
}
|
|
146
152
|
});
|
package/dist/lib/config.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GitPlatformClient } from "./platforms/types.mjs";
|
|
2
|
-
import { AutoReleaseConfig,
|
|
2
|
+
import { AutoReleaseConfig, ManagedProject } from "./types.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/lib/config.d.ts
|
|
5
5
|
declare function define_config<const config extends AutoReleaseConfig>(config: config): InternalConfig;
|
|
@@ -14,8 +14,14 @@ declare class InternalConfig {
|
|
|
14
14
|
platform: GitPlatformClient;
|
|
15
15
|
target_branch: string;
|
|
16
16
|
default_release_branch_prefix: string;
|
|
17
|
+
tag_generator: (args: {
|
|
18
|
+
project: {
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
21
|
+
version: string;
|
|
22
|
+
}) => string;
|
|
17
23
|
};
|
|
18
|
-
get
|
|
24
|
+
get managed_projects(): Array<ManagedProject>;
|
|
19
25
|
}
|
|
20
26
|
//#endregion
|
|
21
27
|
export { define_config };
|
package/dist/lib/config.mjs
CHANGED
|
@@ -33,16 +33,19 @@ var InternalConfig = class {
|
|
|
33
33
|
return {
|
|
34
34
|
platform: this.#config.git.platform,
|
|
35
35
|
target_branch: this.#config.git.target_branch || "main",
|
|
36
|
-
default_release_branch_prefix: this.#config.git.default_release_branch_prefix || "release"
|
|
36
|
+
default_release_branch_prefix: this.#config.git.default_release_branch_prefix || "release",
|
|
37
|
+
tag_generator: this.#config.git.tag_generator || (({ project, version }) => `${project.name}@${version}`)
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
|
-
get
|
|
40
|
-
return Object.entries(this.#config.
|
|
40
|
+
get managed_projects() {
|
|
41
|
+
return Object.entries(this.#config.projects).map(([name, definition]) => {
|
|
41
42
|
const components = definition.components.map((component) => component(this.folder));
|
|
42
43
|
return {
|
|
43
44
|
name,
|
|
44
45
|
...definition,
|
|
45
|
-
components
|
|
46
|
+
components,
|
|
47
|
+
release_group: definition.release_group || this.#config.default_project_config?.release_group || name,
|
|
48
|
+
options: { skip_release_if_no_change_file: definition.options?.skip_release_if_no_change_file || this.#config.default_project_config?.options?.skip_release_if_no_change_file || false }
|
|
46
49
|
};
|
|
47
50
|
});
|
|
48
51
|
}
|
|
@@ -130,16 +133,15 @@ async function find_nearest_config(options) {
|
|
|
130
133
|
*/
|
|
131
134
|
function validate_config(config) {
|
|
132
135
|
if (!config.git) throw new Error("Auto-release config must have a \"git\" platform. Use github() or gitlab() from \"auto-release/providers\"");
|
|
133
|
-
if (!config.
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
if (
|
|
139
|
-
if (
|
|
140
|
-
if (
|
|
141
|
-
if (!
|
|
142
|
-
if (!app.changelog || typeof app.changelog !== "string") throw new Error(`App "${name}" must have a changelog path (string)`);
|
|
136
|
+
if (!config.projects || typeof config.projects !== "object" || Array.isArray(config.projects)) throw new Error("Auto-release config must have a \"projects\" record (object keyed by project name)");
|
|
137
|
+
const project_entries = Object.entries(config.projects);
|
|
138
|
+
for (const [name, project] of project_entries) {
|
|
139
|
+
if (!project.components || !Array.isArray(project.components)) throw new Error(`Project "${name}" must have a "components" array`);
|
|
140
|
+
if (project.components.length === 0) throw new Error(`Project "${name}" must have at least one component`);
|
|
141
|
+
if (!project.versioning) throw new Error(`Project "${name}" must have a "versioning" config`);
|
|
142
|
+
if (typeof project.versioning.bump !== "function") throw new Error(`Project "${name}" versioning must have a "bump" function. Did you forget to call the strategy function?`);
|
|
143
|
+
if (!project.versioning.allowed_changes || !Array.isArray(project.versioning.allowed_changes)) throw new Error(`Project "${name}" versioning must have an "allowed_changes" array`);
|
|
144
|
+
if (!project.changelog || typeof project.changelog !== "string") throw new Error(`Project "${name}" must have a changelog path (string)`);
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
|
package/dist/lib/formatter.mjs
CHANGED
|
@@ -88,12 +88,10 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
88
88
|
},
|
|
89
89
|
format_changelog(changelog, context) {
|
|
90
90
|
const lines = [];
|
|
91
|
-
if (changelog.root.title) lines.push(changelog.root.title);
|
|
92
|
-
else lines.push(`# \`${context.
|
|
93
|
-
if (changelog.root.description.length > 0)
|
|
94
|
-
|
|
95
|
-
lines.push(changelog.root.description.join("\n"));
|
|
96
|
-
} else lines.push(`This is the changelog for \`${context.app.name}\`.`);
|
|
91
|
+
if (changelog.root.title) lines.push(changelog.root.title, "");
|
|
92
|
+
else lines.push(`# \`${context.project.name}\` changelog`, "");
|
|
93
|
+
if (changelog.root.description.length > 0) lines.push(changelog.root.description.join("\n"), "");
|
|
94
|
+
else lines.push(`This is the changelog for \`${context.project.name}\`.`, "");
|
|
97
95
|
for (const release of changelog.releases) {
|
|
98
96
|
const release_lines = [`## ${release.version}`, ""];
|
|
99
97
|
for (const change_kind of allowed_changes) {
|
|
@@ -107,17 +105,19 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
107
105
|
if (release.changes.length === 0) release_lines.push("No changes in this release.", "");
|
|
108
106
|
lines.push(release_lines.join("\n"));
|
|
109
107
|
}
|
|
110
|
-
return lines.join("\n
|
|
108
|
+
return lines.join("\n");
|
|
111
109
|
},
|
|
112
|
-
generate_release_notes({
|
|
110
|
+
generate_release_notes({ project, version }) {
|
|
113
111
|
const hash = version.replaceAll(".", "");
|
|
114
|
-
const file = `${
|
|
115
|
-
return `[See the changelog for ${
|
|
112
|
+
const file = `${project.changelog}#${hash}`;
|
|
113
|
+
return `[See the changelog for ${project.name}@${version} release notes](${file})`;
|
|
116
114
|
},
|
|
117
|
-
generate_pr_body({
|
|
115
|
+
generate_pr_body({ project, current_version, next_version, changes }) {
|
|
118
116
|
const lines = [];
|
|
119
|
-
lines.push(`#
|
|
120
|
-
lines.push(`Version:
|
|
117
|
+
lines.push(`# Automated release for \`${project.name}\``);
|
|
118
|
+
lines.push(`Version: \`${current_version}\` → \`${next_version}\``);
|
|
119
|
+
lines.push("");
|
|
120
|
+
lines.push("## Changelog");
|
|
121
121
|
const grouped = /* @__PURE__ */ new Map();
|
|
122
122
|
for (const change of changes) {
|
|
123
123
|
const group = grouped.get(change.kind) ?? [];
|
|
@@ -130,7 +130,7 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
130
130
|
const labels = resolved_display_map[kind];
|
|
131
131
|
const heading = labels?.plural ?? labels?.singular ?? kind;
|
|
132
132
|
lines.push("");
|
|
133
|
-
lines.push(
|
|
133
|
+
lines.push(`### ${heading}`);
|
|
134
134
|
for (const change of items) {
|
|
135
135
|
lines.push(change.summary);
|
|
136
136
|
lines.push("");
|
package/dist/lib/types.d.mts
CHANGED
|
@@ -13,22 +13,42 @@ interface AutoReleaseConfig {
|
|
|
13
13
|
platform: GitPlatformClient;
|
|
14
14
|
target_branch?: string;
|
|
15
15
|
default_release_branch_prefix?: string;
|
|
16
|
+
tag_generator?: (args: {
|
|
17
|
+
project: {
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
version: string;
|
|
21
|
+
}) => string;
|
|
16
22
|
};
|
|
17
|
-
|
|
23
|
+
default_project_config?: {
|
|
24
|
+
release_group?: string;
|
|
25
|
+
options?: {
|
|
26
|
+
skip_release_if_no_change_file?: boolean;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
projects: Record<string, ProjectDefinition>;
|
|
18
30
|
}
|
|
19
31
|
/**
|
|
20
|
-
*
|
|
32
|
+
* Project definition
|
|
21
33
|
*/
|
|
22
|
-
interface
|
|
34
|
+
interface ProjectDefinition {
|
|
23
35
|
components: Array<Component>;
|
|
24
36
|
versioning: VersionManager;
|
|
25
37
|
changelog: string;
|
|
38
|
+
release_group?: string;
|
|
39
|
+
options?: {
|
|
40
|
+
skip_release_if_no_change_file?: boolean;
|
|
41
|
+
};
|
|
26
42
|
}
|
|
27
|
-
type
|
|
43
|
+
type ManagedProject = {
|
|
28
44
|
name: string;
|
|
29
45
|
components: Array<ResolvedComponent>;
|
|
30
46
|
versioning: VersionManager;
|
|
31
47
|
changelog: string;
|
|
48
|
+
release_group: string;
|
|
49
|
+
options: {
|
|
50
|
+
skip_release_if_no_change_file: boolean;
|
|
51
|
+
};
|
|
32
52
|
};
|
|
33
53
|
//#endregion
|
|
34
|
-
export { AutoReleaseConfig,
|
|
54
|
+
export { AutoReleaseConfig, ManagedProject };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/lib/utils/group.ts
|
|
2
|
+
function group_projects(projects) {
|
|
3
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const project of projects) {
|
|
5
|
+
const existing = groups.get(project.release_group) ?? [];
|
|
6
|
+
existing.push(project);
|
|
7
|
+
groups.set(project.release_group, existing);
|
|
8
|
+
}
|
|
9
|
+
return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([name, group_projects$1]) => ({
|
|
10
|
+
name,
|
|
11
|
+
projects: group_projects$1.sort((a, b) => a.name.localeCompare(b.name))
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
function is_multi_project_group(group) {
|
|
15
|
+
return group.projects.length > 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { group_projects, is_multi_project_group };
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
//#region src/lib/utils/version.ts
|
|
2
|
-
async function compute_current_version(
|
|
2
|
+
async function compute_current_version(project, { get_file_content }) {
|
|
3
3
|
const versions = /* @__PURE__ */ new Set();
|
|
4
|
-
for (const component of
|
|
4
|
+
for (const component of project.components) for (const part of component.parts) {
|
|
5
5
|
const file_content = await get_file_content(part.file);
|
|
6
6
|
if (file_content === null) continue;
|
|
7
7
|
const version = part.get_current_version(file_content);
|
|
8
8
|
versions.add(version);
|
|
9
9
|
}
|
|
10
10
|
if (versions.size === 0) return null;
|
|
11
|
-
return Array.from(versions).sort((a, b) =>
|
|
11
|
+
return Array.from(versions).sort((a, b) => project.versioning.compare(a, b)).at(-1) ?? null;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
//#endregion
|
|
@@ -25,7 +25,7 @@ type Formatter<change_kinds extends string = string, parsed_changelog extends {
|
|
|
25
25
|
* @returns Markdown string that will be written to the changelog file
|
|
26
26
|
*/
|
|
27
27
|
format_changelog(changelog: NoInfer<parsed_changelog>, context: {
|
|
28
|
-
|
|
28
|
+
project: {
|
|
29
29
|
name: string;
|
|
30
30
|
};
|
|
31
31
|
}): string;
|
|
@@ -35,7 +35,7 @@ type Formatter<change_kinds extends string = string, parsed_changelog extends {
|
|
|
35
35
|
* @returns Markdown string for PR body
|
|
36
36
|
*/
|
|
37
37
|
generate_pr_body(options: {
|
|
38
|
-
|
|
38
|
+
project: {
|
|
39
39
|
name: string;
|
|
40
40
|
};
|
|
41
41
|
current_version: string;
|
|
@@ -43,12 +43,12 @@ type Formatter<change_kinds extends string = string, parsed_changelog extends {
|
|
|
43
43
|
changes: ChangeFile<change_kinds>[];
|
|
44
44
|
}): string;
|
|
45
45
|
/**
|
|
46
|
-
* Generate release notes for a given
|
|
46
|
+
* Generate release notes for a given project to use in GitHub/GitLab release bodies.
|
|
47
47
|
* @param options - The options for generating release notes
|
|
48
48
|
* @returns Markdown string for release body
|
|
49
49
|
*/
|
|
50
50
|
generate_release_notes(options: {
|
|
51
|
-
|
|
51
|
+
project: {
|
|
52
52
|
name: string;
|
|
53
53
|
changelog: string;
|
|
54
54
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Change Files
|
|
2
|
+
|
|
3
|
+
Change files are stored in `.changes/<project-name>/` with format:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
<type>.<slug>.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Examples:
|
|
10
|
+
|
|
11
|
+
- `.changes/my-app/major.add-authentication.md`
|
|
12
|
+
- `.changes/my-app/patch.fix-login-bug.md`
|
|
13
|
+
|
|
14
|
+
The change files folder can be customized.
|
|
15
|
+
|
|
16
|
+
## Format
|
|
17
|
+
|
|
18
|
+
**Simple** (title only):
|
|
19
|
+
|
|
20
|
+
```markdown
|
|
21
|
+
Fix authentication bug in login flow
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Detailed** (with description):
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
This adds a comprehensive user profile page with:
|
|
28
|
+
- Avatar upload
|
|
29
|
+
- Bio and social links
|
|
30
|
+
- Privacy settings
|
|
31
|
+
```
|
package/docs/commands.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Commands
|
|
2
|
+
|
|
3
|
+
## `init`
|
|
4
|
+
|
|
5
|
+
Set up auto-release in your repository:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
auto-release init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Interactively configures projects, versioning strategies, and git platform.
|
|
12
|
+
|
|
13
|
+
## `check`
|
|
14
|
+
|
|
15
|
+
Validate configuration, change files, and release groups:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
auto-release check
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Validations:**
|
|
22
|
+
|
|
23
|
+
- Component version consistency
|
|
24
|
+
- Change file content
|
|
25
|
+
- Group name conflicts (group names cannot match project names)
|
|
26
|
+
- Similar group names (case-insensitive)
|
|
27
|
+
- Group name special characters
|
|
28
|
+
|
|
29
|
+
Use in CI to ensure everything is valid before merging.
|
|
30
|
+
|
|
31
|
+
## `record-change`
|
|
32
|
+
|
|
33
|
+
Create a new change file:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Interactive
|
|
37
|
+
auto-release record-change
|
|
38
|
+
|
|
39
|
+
# Non-interactive
|
|
40
|
+
auto-release record-change --project my-app --type minor
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## `list`
|
|
44
|
+
|
|
45
|
+
List all projects managed by `auto-release` with their current versions, grouped by `release_group`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
auto-release list
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Output example:**
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
found 5 projects in 3 groups:
|
|
55
|
+
|
|
56
|
+
Group: frontend (2 projects)
|
|
57
|
+
web-app (1.2.3)
|
|
58
|
+
./apps/web/package.json
|
|
59
|
+
mobile-app (2.0.1)
|
|
60
|
+
./apps/mobile/package.json
|
|
61
|
+
|
|
62
|
+
Group: api-service (1 project)
|
|
63
|
+
api-service (0.5.0)
|
|
64
|
+
./services/api/Cargo.toml
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## `generate-release-pr`
|
|
68
|
+
|
|
69
|
+
Create or update release PRs based on change files:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Preview changes
|
|
73
|
+
auto-release generate-release-pr --dry-run
|
|
74
|
+
|
|
75
|
+
# Create/update PRs
|
|
76
|
+
auto-release generate-release-pr
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Projects are grouped by `release_group` in the configuration. Projects in the same group are released together in a single PR.
|
|
80
|
+
|
|
81
|
+
**PR Structure:**
|
|
82
|
+
|
|
83
|
+
- **Branch**: `release/<group-name>` (e.g., `release/frontend`)
|
|
84
|
+
- **Title**: `release: project-a@1.0.0, project-b@2.0.0` (lists all projects with versions)
|
|
85
|
+
- **Body**: Contains sections for each project's changelog
|
|
86
|
+
|
|
87
|
+
## `tag-release-commit`
|
|
88
|
+
|
|
89
|
+
Create git tags and releases for version changes:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Preview what would be tagged
|
|
93
|
+
auto-release tag-release-commit --dry-run
|
|
94
|
+
|
|
95
|
+
# Create tags and releases
|
|
96
|
+
auto-release tag-release-commit
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Compares HEAD with HEAD^1 to detect version changes. Creates tags in format `project-name@version`.
|
|
100
|
+
|
|
101
|
+
## `manual-release`
|
|
102
|
+
|
|
103
|
+
Create a manual release using existing change files:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
auto-release manual-release
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Useful for local testing or emergency releases.
|