@afoures/auto-release 0.2.12 → 0.3.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 +10 -10
- package/dist/lib/commands/generate-release-pr.mjs +21 -21
- package/dist/lib/commands/init.mjs +40 -199
- package/dist/lib/commands/list.mjs +10 -10
- 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 +13 -13
- package/dist/lib/formatter.mjs +15 -14
- package/dist/lib/types.d.mts +11 -5
- package/dist/lib/utils/branch-protection.mjs +0 -3
- 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 +80 -0
- package/docs/configuration.md +116 -0
- package/docs/lexicon.md +23 -0
- package/docs/recommended-usage.md +155 -0
- package/package.json +15 -14
|
@@ -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,11 +33,12 @@ 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,
|
|
@@ -130,16 +131,15 @@ async function find_nearest_config(options) {
|
|
|
130
131
|
*/
|
|
131
132
|
function validate_config(config) {
|
|
132
133
|
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)`);
|
|
134
|
+
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)");
|
|
135
|
+
const project_entries = Object.entries(config.projects);
|
|
136
|
+
for (const [name, project] of project_entries) {
|
|
137
|
+
if (!project.components || !Array.isArray(project.components)) throw new Error(`Project "${name}" must have a "components" array`);
|
|
138
|
+
if (project.components.length === 0) throw new Error(`Project "${name}" must have at least one component`);
|
|
139
|
+
if (!project.versioning) throw new Error(`Project "${name}" must have a "versioning" config`);
|
|
140
|
+
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?`);
|
|
141
|
+
if (!project.versioning.allowed_changes || !Array.isArray(project.versioning.allowed_changes)) throw new Error(`Project "${name}" versioning must have an "allowed_changes" array`);
|
|
142
|
+
if (!project.changelog || typeof project.changelog !== "string") throw new Error(`Project "${name}" must have a changelog path (string)`);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
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,20 @@ 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(
|
|
117
|
+
lines.push("This PR is managed by `[auto-release](https://github.com/afoures/auto-release)`. Do not edit it manually.");
|
|
118
|
+
lines.push(`## Automated release for \`${project.name}\``);
|
|
119
|
+
lines.push(`Version: \`${current_version}\` → \`${next_version}\``);
|
|
120
|
+
lines.push("");
|
|
121
|
+
lines.push("## Changelog");
|
|
121
122
|
const grouped = /* @__PURE__ */ new Map();
|
|
122
123
|
for (const change of changes) {
|
|
123
124
|
const group = grouped.get(change.kind) ?? [];
|
|
@@ -130,7 +131,7 @@ function default_formatter({ allowed_changes, display_map }) {
|
|
|
130
131
|
const labels = resolved_display_map[kind];
|
|
131
132
|
const heading = labels?.plural ?? labels?.singular ?? kind;
|
|
132
133
|
lines.push("");
|
|
133
|
-
lines.push(
|
|
134
|
+
lines.push(`### ${heading}`);
|
|
134
135
|
for (const change of items) {
|
|
135
136
|
lines.push(change.summary);
|
|
136
137
|
lines.push("");
|
package/dist/lib/types.d.mts
CHANGED
|
@@ -13,22 +13,28 @@ 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
|
+
projects: Record<string, ProjectDefinition>;
|
|
18
24
|
}
|
|
19
25
|
/**
|
|
20
|
-
*
|
|
26
|
+
* Project definition
|
|
21
27
|
*/
|
|
22
|
-
interface
|
|
28
|
+
interface ProjectDefinition {
|
|
23
29
|
components: Array<Component>;
|
|
24
30
|
versioning: VersionManager;
|
|
25
31
|
changelog: string;
|
|
26
32
|
}
|
|
27
|
-
type
|
|
33
|
+
type ManagedProject = {
|
|
28
34
|
name: string;
|
|
29
35
|
components: Array<ResolvedComponent>;
|
|
30
36
|
versioning: VersionManager;
|
|
31
37
|
changelog: string;
|
|
32
38
|
};
|
|
33
39
|
//#endregion
|
|
34
|
-
export { AutoReleaseConfig,
|
|
40
|
+
export { AutoReleaseConfig, ManagedProject };
|
|
@@ -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,80 @@
|
|
|
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 and change files:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
auto-release check
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Use in CI to ensure everything is valid before merging.
|
|
22
|
+
|
|
23
|
+
## `record-change`
|
|
24
|
+
|
|
25
|
+
Create a new change file:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Interactive
|
|
29
|
+
auto-release record-change
|
|
30
|
+
|
|
31
|
+
# Non-interactive
|
|
32
|
+
auto-release record-change --project my-app --type minor
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## `list`
|
|
36
|
+
|
|
37
|
+
List all projects managed by `auto-release` with their current versions:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
auto-release list
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## `generate-release-pr`
|
|
44
|
+
|
|
45
|
+
Create or update release PRs:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Preview changes
|
|
49
|
+
auto-release generate-release-pr --dry-run
|
|
50
|
+
|
|
51
|
+
# Create/update PRs
|
|
52
|
+
auto-release generate-release-pr
|
|
53
|
+
|
|
54
|
+
# Specific projects only
|
|
55
|
+
auto-release generate-release-pr --filter my-app --filter another-app
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## `tag-release-commit`
|
|
59
|
+
|
|
60
|
+
Create git tags and releases for version changes:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Preview what would be tagged
|
|
64
|
+
auto-release tag-release-commit --dry-run
|
|
65
|
+
|
|
66
|
+
# Create tags and releases
|
|
67
|
+
auto-release tag-release-commit
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Compares HEAD with HEAD^1 to detect version changes. Creates tags in format `project-name@version`.
|
|
71
|
+
|
|
72
|
+
## `manual-release`
|
|
73
|
+
|
|
74
|
+
Create a manual release using existing change files:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
auto-release manual-release
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Useful for local testing or emergency releases.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
## Projects
|
|
4
|
+
|
|
5
|
+
The `projects` object defines each releasable unit:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
projects: {
|
|
9
|
+
'my-app': {
|
|
10
|
+
// Components: where versions are read/written
|
|
11
|
+
components: [
|
|
12
|
+
node('packages/my-app'),
|
|
13
|
+
node('packages/shared'),
|
|
14
|
+
],
|
|
15
|
+
|
|
16
|
+
// Versioning strategy
|
|
17
|
+
versioning: semver(),
|
|
18
|
+
|
|
19
|
+
// Changelog file path
|
|
20
|
+
changelog: 'apps/my-app/CHANGELOG.md',
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Versioning Strategies
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { semver, calver, markver } from 'auto-release/versioning'
|
|
29
|
+
|
|
30
|
+
// Semantic versioning: 1.2.3
|
|
31
|
+
versioning: semver() // Change types: major, minor, patch
|
|
32
|
+
|
|
33
|
+
// Calendar versioning: 2025.1.2
|
|
34
|
+
versioning: calver() // Change types: feature, fix
|
|
35
|
+
|
|
36
|
+
// Marketing versioning: 1.0.0
|
|
37
|
+
versioning: markver() // Change types: marketing, feature, fix
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Git Platforms
|
|
41
|
+
|
|
42
|
+
### GitHub
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { github } from 'auto-release/providers'
|
|
46
|
+
|
|
47
|
+
git: {
|
|
48
|
+
platform: github({
|
|
49
|
+
token: process.env.GITHUB_TOKEN!,
|
|
50
|
+
owner: 'your-org',
|
|
51
|
+
repo: 'your-repo',
|
|
52
|
+
}),
|
|
53
|
+
target_branch: 'main',
|
|
54
|
+
tag_generator: ({ project, version }) => `${project.name}-${version}`,
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### GitLab
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { gitlab } from 'auto-release/providers'
|
|
62
|
+
|
|
63
|
+
git: {
|
|
64
|
+
platform: gitlab({
|
|
65
|
+
token: process.env.GITLAB_TOKEN!,
|
|
66
|
+
project_id: 'your-project-id',
|
|
67
|
+
}),
|
|
68
|
+
target_branch: 'main',
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Tag Generator
|
|
73
|
+
|
|
74
|
+
Customize the format of git tags created during release:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
git: {
|
|
78
|
+
// ... other options
|
|
79
|
+
tag_generator: ({ project, version }) => `${project.name}-${version}`,
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Default**: `project-name@version` (e.g., `my-app@1.2.3`)
|
|
84
|
+
|
|
85
|
+
**Custom examples**:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Version only: 1.2.3
|
|
89
|
+
tag_generator: ({ version }) => `${version}`
|
|
90
|
+
|
|
91
|
+
// Prefixed: v1.2.3
|
|
92
|
+
tag_generator: ({ version }) => `v${version}`
|
|
93
|
+
|
|
94
|
+
// With prefix: release/my-app-1.2.3
|
|
95
|
+
tag_generator: ({ project, version }) => `release/${project.name}-${version}`
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Components
|
|
99
|
+
|
|
100
|
+
Components define version sources:
|
|
101
|
+
|
|
102
|
+
- **`node(path)`**: any node project with package.json
|
|
103
|
+
- **`bun(path)`**: any bun project with package.json
|
|
104
|
+
- **`expo(path)`**: any Expo project with package.json and app.json
|
|
105
|
+
- **`php(path)`**: any PHP project with composer.json
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { node, expo, php } from 'auto-release/components'
|
|
109
|
+
|
|
110
|
+
components: [
|
|
111
|
+
node('packages/web'),
|
|
112
|
+
bun('packages/bff'),
|
|
113
|
+
expo('apps/mobile'),
|
|
114
|
+
php('packages/api'),
|
|
115
|
+
]
|
|
116
|
+
```
|
package/docs/lexicon.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# `auto-release` Lexicon
|
|
2
|
+
|
|
3
|
+
This is a list of words and phrases used in `auto-release` to help contributors and users have a shared understanding of various concepts in the project.
|
|
4
|
+
|
|
5
|
+
- **change file** - A markdown file documenting a single change to a project. Change files are stored in the changes directory with the format `<kind>.<slug>.md` (e.g., `major.add-authentication.md`). Change files are stackable, running `generate-release-pr` will apply any number of change files correctly to calculate the next version and generate changelog entries.
|
|
6
|
+
- **changes directory** - The `.changes/` folder where change files are stored. This directory contains subdirectories for each project (e.g., `.changes/my-app/`), and each subdirectory contains the change files for that project.
|
|
7
|
+
- **change kind** - The type of change represented by a change file (e.g., `major`, `minor`, `patch`, `feature`, `fix`). The valid kinds for a project are defined by its versioning strategy's `allowed_changes`. This determines how the version will be bumped.
|
|
8
|
+
- **slug** - A unique identifier for a change file, used in the filename between the kind and the `.md` extension. Generated automatically or provided by the user (e.g., `add-authentication` in `major.add-authentication.md`).
|
|
9
|
+
- **project** - A releasable unit in your repository or monorepo. Each project has its own independent versioning strategy, one or more components, its own changelog, and a dedicated subdirectory in the changes directory. Projects are defined in the configuration file.
|
|
10
|
+
- **component** - A source of version information within a project. Components define where versions are read from and written to. Built-in component types include `node()/bun()` for package.json files, `expo()` for app.json files, and `php()` for composer.json files. All components within a project must have the same version.
|
|
11
|
+
- **versioning strategy** - Defines how versions are calculated and formatted for a project. Each strategy specifies valid change kinds and a `bump()` function to compute the next version from the current version and collected change kinds.
|
|
12
|
+
- **bump**
|
|
13
|
+
- (1) The command/process to apply all current change files, calculate new versions, update all component files, update changelogs, and remove processed change files.
|
|
14
|
+
- (2) The act of updating a project's version to a new version based on change files.
|
|
15
|
+
- **changelog** - A markdown file where release entries are appended for a project. Each project specifies its changelog path in the configuration. Changelog entries are generated from change file summaries using the formatter.
|
|
16
|
+
- **formatter** - Controls how changelogs and release notes are generated from change files. The formatter can parse markdown from change files and format changelog sections. The default formatter groups changes by kind and outputs markdown lists.
|
|
17
|
+
- **target branch** - The main branch where releases are merged to (typically `main` or `master`). This is the branch that release PRs target and where version tags are created.
|
|
18
|
+
- **release branch** - A branch created by the `generate-release-pr` command containing updated versions in all component files, new changelog entries, and removed change files. Format: `<prefix>/<project-name>` (e.g., `release/my-app`).
|
|
19
|
+
- **release PR** - A pull request from a release branch to the target branch, containing all version updates and changelog changes for a release. Created or updated by the `generate-release-pr` command.
|
|
20
|
+
- **git platform** - The hosting service for your git repository (GitHub or GitLab). The git platform integration is used to create and update release PRs, manage release notes, and interact with the repository API. Configured in the configuration file with authentication tokens.
|
|
21
|
+
- **tag** - A git tag created when a version change is detected on the target branch after a release PR is merged. Format: `<project-name>@<version>` (e.g., `my-app@1.2.3`). Created by the `tag-release-commit` command, typically run in CI.
|
|
22
|
+
- **release** - The combination of versioning, updating changelogs, creating a git tag, and publishing release notes on the git platform. This happens when a release PR is merged and the `tag-release-commit` command detects the version change.
|
|
23
|
+
- **config file** - The `auto-release.config.ts` TypeScript configuration file at the root of your repository. Defines projects, their components, versioning strategies, changelog paths, and git platform settings.
|