@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # auto-release
2
2
 
3
- A release management tool inspired by Changesets and Release Please, designed for monorepos with app-centric versioning.
3
+ A release management tool inspired by Changesets and Release Please, designed for monorepos with project-centric versioning.
4
4
 
5
5
  ## Why auto-release?
6
6
 
@@ -8,7 +8,7 @@ Release management should be simple. `auto-release` lets you focus on building f
8
8
 
9
9
  **Language agnostic**: Works with any project type. Built-in components for Node, Bun, Expo, and PHP projects, but you can add custom components for anything.
10
10
 
11
- **Built for monorepos**: Each app can have its own versioning strategy (semver, calver, marketing) and release independently.
11
+ **Built for monorepos**: Each project can have its own versioning strategy (semver, calver, marketing) and release independently.
12
12
 
13
13
  **Developer-friendly**: Record changes with markdown files as you work. No complex conventions or strict commit messages required.
14
14
 
@@ -17,11 +17,13 @@ Release management should be simple. `auto-release` lets you focus on building f
17
17
  ## Quick Start
18
18
 
19
19
  ```bash
20
- npx --package=@afoures/auto-release@latest auto-release init
20
+ npx @afoures/auto-release@latest init
21
21
  # or
22
- pnpx --package=@afoures/auto-release@latest auto-release init
22
+ pnpx @afoures/auto-release@latest init
23
23
  # or
24
- bunx --package=@afoures/auto-release@latest auto-release init
24
+ yarn dlx @afoures/auto-release@latest init
25
+ # or
26
+ bunx @afoures/auto-release@latest init
25
27
  ```
26
28
 
27
29
  This creates `auto-release.config.ts` and sets up the `.changes` directory.
@@ -29,9 +31,13 @@ This creates `auto-release.config.ts` and sets up the `.changes` directory.
29
31
  ### Manual Installation
30
32
 
31
33
  ```bash
32
- npm install auto-release
34
+ npm install @afoures/auto-release
35
+ # or
36
+ pnpm add @afoures/auto-release
37
+ # or
38
+ yarn add @afoures/auto-release
33
39
  # or
34
- pnpm add auto-release
40
+ bunx add @afoures/auto-release
35
41
  ```
36
42
 
37
43
  Create `auto-release.config.ts`:
@@ -43,6 +49,13 @@ import { github } from 'auto-release/providers'
43
49
  import { node } from 'auto-release/components'
44
50
 
45
51
  export default define_config({
52
+ projects: {
53
+ 'my-app': {
54
+ components: [node('packages/my-app')],
55
+ versioning: semver(),
56
+ changelog: 'CHANGELOG.md',
57
+ },
58
+ },
46
59
  git: {
47
60
  platform: github({
48
61
  token: process.env.GITHUB_TOKEN!,
@@ -51,275 +64,23 @@ export default define_config({
51
64
  }),
52
65
  target_branch: 'main',
53
66
  },
54
- apps: {
55
- 'my-app': {
56
- components: [node('packages/my-app')],
57
- versioning: semver(),
58
- changelog: 'CHANGELOG.md',
59
- },
60
- },
61
67
  })
62
68
  ```
63
69
 
64
- ## Workflow
65
-
66
- ### 1. Development
67
-
68
- Make changes and record them:
69
-
70
- ```bash
71
- auto-release record-change
72
- ```
73
-
74
- Commit everything including the change file:
75
-
76
- ```bash
77
- git add .
78
- git commit -m "feat: add new feature"
79
- git push
80
- ```
81
-
82
- ### 2. Generate Release PR
83
-
84
- On `main` branch, CI should automatically run:
85
-
86
- ```bash
87
- auto-release generate-release-pr
88
- ```
89
-
90
- This creates/updates a release branch with:
91
- - Updated versions in component files
92
- - Generated changelog entries
93
- - Change files removed
94
-
95
- ### 3. Test on Release Branch
96
-
97
- CI on release branch runs:
98
- - Tests and quality checks
99
- - Build and deploy to test/staging environment
100
-
101
- ### 4. Merge Release PR
102
-
103
- When ready, merge the release PR to `main`.
104
-
105
- ### 5. Tag and Deploy
106
-
107
- CI on `main` runs:
108
-
109
- ```bash
110
- auto-release tag-release-commit
111
- ```
112
-
113
- This creates git tags for all releases, which can trigger deployment to pre-production and production.
114
-
115
- ## Commands
116
-
117
- ### `init`
118
-
119
- Set up auto-release in your repository:
120
-
121
- ```bash
122
- auto-release init
123
- ```
124
-
125
- Interactively configures apps, versioning strategies, and git platform.
126
-
127
- ### `check`
128
-
129
- Validate configuration and change files:
130
-
131
- ```bash
132
- auto-release check
133
- ```
134
-
135
- Use in CI to ensure everything is valid before merging.
136
-
137
- ### `record-change`
138
-
139
- Create a new change file:
140
-
141
- ```bash
142
- # Interactive
143
- auto-release record-change
144
-
145
- # Non-interactive
146
- auto-release record-change --app my-app --type minor
147
- ```
148
-
149
- ### `list`
150
-
151
- List all apps managed by `auto-release` with their current versions:
152
-
153
- ```bash
154
- auto-release list
155
- ```
156
-
157
- ### `generate-release-pr`
158
-
159
- Create or update release PRs:
160
-
161
- ```bash
162
- # Preview changes
163
- auto-release generate-release-pr --dry-run
164
-
165
- # Create/update PRs
166
- auto-release generate-release-pr
167
-
168
- # Specific apps only
169
- auto-release generate-release-pr --app my-app --app another-app
170
- ```
171
-
172
- ### `tag-release-commit`
173
-
174
- Create git tags and releases for version changes:
175
-
176
- ```bash
177
- # Preview what would be tagged
178
- auto-release tag-release-commit --dry-run
179
-
180
- # Create tags and releases
181
- auto-release tag-release-commit
182
- ```
183
-
184
- Compares HEAD with HEAD^1 to detect version changes. Creates tags in format `app-name@version`.
185
-
186
- ### `manual-release`
187
-
188
- Create a manual release using existing change files:
189
-
190
- ```bash
191
- auto-release manual-release
192
- ```
193
-
194
- Useful for local testing or emergency releases.
195
-
196
- ## Configuration
197
-
198
- ### Apps
199
-
200
- The `apps` object defines each releasable unit:
201
-
202
- ```typescript
203
- apps: {
204
- 'my-app': {
205
- // Components: where versions are read/written
206
- components: [
207
- node('packages/my-app'),
208
- node('packages/shared'),
209
- ],
210
-
211
- // Versioning strategy
212
- versioning: semver(),
213
-
214
- // Changelog file path
215
- changelog: 'apps/my-app/CHANGELOG.md',
216
- },
217
- }
218
- ```
219
-
220
- ### Versioning Strategies
221
-
222
- ```typescript
223
- import { semver, calver, markver } from 'auto-release/versioning'
224
-
225
- // Semantic versioning: 1.2.3
226
- versioning: semver() // Change types: major, minor, patch
227
-
228
- // Calendar versioning: 2025.1.2
229
- versioning: calver() // Change types: feature, fix
230
-
231
- // Marketing versioning: 1.0.0
232
- versioning: markver() // Change types: marketing, feature, fix
233
- ```
234
-
235
- ### Git Platforms
236
-
237
- #### GitHub
238
-
239
- ```typescript
240
- import { github } from 'auto-release/providers'
241
-
242
- git: {
243
- platform: github({
244
- token: process.env.GITHUB_TOKEN!,
245
- owner: 'your-org',
246
- repo: 'your-repo',
247
- }),
248
- target_branch: 'main',
249
- }
250
- ```
251
-
252
- #### GitLab
70
+ ## Documentation
253
71
 
254
- ```typescript
255
- import { gitlab } from 'auto-release/providers'
256
-
257
- git: {
258
- platform: gitlab({
259
- token: process.env.GITLAB_TOKEN!,
260
- project_id: 'your-project-id',
261
- }),
262
- target_branch: 'main',
263
- }
264
- ```
265
-
266
- ### Components
267
-
268
- Components define version sources:
269
-
270
- - **`node(path)`**: any node project with package.json
271
- - **`bun(path)`**: any bun project with package.json
272
- - **`expo(path)`**: any Expo project with package.json and app.json
273
- - **`php(path)`**: any PHP project with composer.json
274
-
275
- ```typescript
276
- import { node, expo, php } from 'auto-release/components'
277
-
278
- components: [
279
- node('packages/web'),
280
- bun('packages/bff'),
281
- expo('apps/mobile'),
282
- php('packages/api'),
283
- ]
284
- ```
285
-
286
- ## Change Files
287
-
288
- Change files are stored in `.changes/<app-name>/` with format:
289
-
290
- ```
291
- <type>.<slug>.md
292
- ```
293
-
294
- Examples:
295
- - `.changes/my-app/major.add-authentication.md`
296
- - `.changes/my-app/patch.fix-login-bug.md`
297
-
298
- The change files folder can be customized.
299
-
300
- ### Format
301
-
302
- **Simple** (title only):
303
-
304
- ```markdown
305
- Fix authentication bug in login flow
306
- ```
307
-
308
- **Detailed** (with description):
309
-
310
- ```markdown
311
- This adds a comprehensive user profile page with:
312
- - Avatar upload
313
- - Bio and social links
314
- - Privacy settings
315
- ```
72
+ - [Configuration](./docs/configuration.md)
73
+ - [Recommended usage](./docs/recommended-usage.md)
74
+ - [Change File anatomy](./docs/change-file-anatomy.md)
75
+ - [Commands](./docs/commands.md)
76
+ - [Lexicon](./docs/lexicon.md)
316
77
 
317
78
  ## Philosophy
318
79
 
319
- Inspired by Changesets and Release Please, designed for app-centric monorepos where:
80
+ Inspired by Changesets and Release Please, designed for project-centric monorepos where:
320
81
 
321
- - Multiple apps release independently with different versioning strategies
322
- - Change files are organized by app for clarity
82
+ - Multiple projects release independently with different versioning strategies
83
+ - Change files are organized by project for clarity
323
84
  - Release branches allow testing before production
324
85
  - Deployment is integrated with the release process
325
86
 
@@ -327,4 +88,4 @@ Inspired by Changesets and Release Please, designed for app-centric monorepos wh
327
88
 
328
89
  MIT
329
90
 
330
- See [LICENSE](./LICENSE)
91
+ See [LICENSE](./LICENSE)
@@ -6,10 +6,10 @@ import { find_change_files } from "../change-file.mjs";
6
6
  import { join } from "node:path";
7
7
 
8
8
  //#region src/lib/commands/check.ts
9
- async function verify_component_version_consistency(app) {
9
+ async function verify_component_version_consistency(project) {
10
10
  const versions = /* @__PURE__ */ new Set();
11
11
  const errors = [];
12
- for (const component of app.components) for (const part of component.parts) {
12
+ for (const component of project.components) for (const part of component.parts) {
13
13
  const file_content = await read_file(part.file);
14
14
  if (file_content === null) {
15
15
  errors.push(`component ${component.root} has no version`);
@@ -19,14 +19,14 @@ async function verify_component_version_consistency(app) {
19
19
  versions.add(version);
20
20
  }
21
21
  if (versions.size === 0) {
22
- errors.push(`application ${app.name} has no versions`);
22
+ errors.push(`project ${project.name} has no versions`);
23
23
  return {
24
24
  ok: false,
25
25
  errors
26
26
  };
27
27
  }
28
28
  if (versions.size > 1) {
29
- errors.push(`application ${app.name} has multiple versions: ${Array.from(versions).join(", ")}`);
29
+ errors.push(`project ${project.name} has multiple versions: ${Array.from(versions).join(", ")}`);
30
30
  return {
31
31
  ok: false,
32
32
  errors
@@ -34,9 +34,9 @@ async function verify_component_version_consistency(app) {
34
34
  }
35
35
  return { ok: true };
36
36
  }
37
- async function validate_changes_files_content(changes_dir, app) {
37
+ async function validate_changes_files_content(changes_dir, project) {
38
38
  const errors = [];
39
- const change_files = await find_change_files(join(changes_dir, app.name), { allowed_kinds: app.versioning.allowed_changes });
39
+ const change_files = await find_change_files(join(changes_dir, project.name), { allowed_kinds: project.versioning.allowed_changes });
40
40
  if (change_files.warnings.length > 0) for (const warning of change_files.warnings) errors.push(warning);
41
41
  for (const change_file of change_files.list) if (change_file.summary.length === 0) errors.push(`change file ${change_file.filename} has no summary`);
42
42
  if (errors.length > 0) return {
@@ -47,7 +47,7 @@ async function validate_changes_files_content(changes_dir, app) {
47
47
  }
48
48
  const check = create_command({
49
49
  name: "check",
50
- description: "Validate configuration, packages, and change files",
50
+ description: "Validate configuration, versions, and change files",
51
51
  schema: { config: {
52
52
  type: "string",
53
53
  description: "Path to config file"
@@ -63,10 +63,10 @@ const check = create_command({
63
63
  const logger = create_logger();
64
64
  const errors = [];
65
65
  const config = context.config;
66
- for (const app of config.managed_applications) {
67
- const component_validation = await verify_component_version_consistency(app);
66
+ for (const project of config.managed_projects) {
67
+ const component_validation = await verify_component_version_consistency(project);
68
68
  if (!component_validation.ok) errors.push(...component_validation.errors);
69
- const changes_validation = await validate_changes_files_content(config.changes_dir, app);
69
+ const changes_validation = await validate_changes_files_content(config.changes_dir, project);
70
70
  if (!changes_validation.ok) errors.push(...changes_validation.errors);
71
71
  }
72
72
  const valid = errors.length === 0;
@@ -19,7 +19,7 @@ const generate_release_pr = create_command({
19
19
  },
20
20
  filter: {
21
21
  type: "string",
22
- description: "Only generate release PRs for the specified apps",
22
+ description: "Only generate release PRs for the specified projects",
23
23
  multiple: true
24
24
  },
25
25
  "dry-run": {
@@ -39,17 +39,17 @@ const generate_release_pr = create_command({
39
39
  },
40
40
  run: async ({ args: { filter, "dry-run": dry_run = false }, context: { config, root } }) => {
41
41
  const logger = create_logger();
42
- const filtered_applications = filter ? config.managed_applications.filter((app) => filter.includes(app.name)) : config.managed_applications;
43
- if (filtered_applications.length === 0) return {
42
+ const filtered_projects = filter ? config.managed_projects.filter((project) => filter.includes(project.name)) : config.managed_projects;
43
+ if (filtered_projects.length === 0) return {
44
44
  status: "success",
45
- message: "No apps to release"
45
+ message: "No projects to release"
46
46
  };
47
- for (const app of filtered_applications) {
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 });
47
+ for (const project of filtered_projects) {
48
+ const changes_dir = join(config.changes_dir, project.name);
49
+ const changes = await find_change_files(changes_dir, { allowed_kinds: project.versioning.allowed_changes });
50
50
  if (changes.warnings.length > 0) for (const warning of changes.warnings) logger.warn(warning);
51
- const current_version = await compute_current_version(app, { get_file_content: (file_path) => read_file(file_path) }) ?? app.versioning.initial_version;
52
- const next_version = app.versioning.bump({
51
+ const current_version = await compute_current_version(project, { get_file_content: (file_path) => read_file(file_path) }) ?? project.versioning.initial_version;
52
+ const next_version = project.versioning.bump({
53
53
  version: current_version,
54
54
  changes: changes.list,
55
55
  date: /* @__PURE__ */ new Date()
@@ -61,23 +61,23 @@ const generate_release_pr = create_command({
61
61
  changes_by_kind.set(change.kind, existing);
62
62
  }
63
63
  const message_lines = [];
64
- const display_map = app.versioning.display_map;
64
+ const display_map = project.versioning.display_map;
65
65
  for (const [kind, kind_changes] of changes_by_kind.entries()) {
66
66
  const label = display_map[kind]?.plural ?? display_map[kind]?.singular ?? kind;
67
67
  message_lines.push(`\n${label}:`);
68
68
  for (const change of kind_changes) message_lines.push(` ${change.summary}`);
69
69
  }
70
- logger.note(`Release ${app.name} ${next_version}`, message_lines.join("\n"));
70
+ logger.note(`Release ${project.name} ${next_version}`, message_lines.join("\n"));
71
71
  if (dry_run) continue;
72
72
  await delete_all_files_from_folder(changes_dir);
73
- for (const component of app.components) for (const part of component.parts) {
73
+ for (const component of project.components) for (const part of component.parts) {
74
74
  const initial_content = await read_file(part.file);
75
75
  if (initial_content === null) continue;
76
76
  const updated_content = part.update_version(initial_content, next_version);
77
77
  await write_file(part.file, updated_content);
78
78
  }
79
- const formatter = app.versioning.formatter;
80
- const initial_changelog_content = await read_file(app.changelog);
79
+ const formatter = project.versioning.formatter;
80
+ const initial_changelog_content = await read_file(project.changelog);
81
81
  const changelog_as_mdast = parse_markdown(initial_changelog_content ?? "");
82
82
  const changelog = formatter.transform_markdown(changelog_as_mdast, initial_changelog_content ?? "");
83
83
  const updated_changelog_content = formatter.format_changelog({
@@ -85,25 +85,25 @@ const generate_release_pr = create_command({
85
85
  releases: [{
86
86
  version: next_version,
87
87
  changes: changes.list
88
- }, ...changelog.releases.filter((release) => release.version !== next_version)].sort((a, b) => app.versioning.compare(b.version, a.version))
89
- }, { app: { name: app.name } });
90
- await write_file(app.changelog, updated_changelog_content);
88
+ }, ...changelog.releases.filter((release) => release.version !== next_version)].sort((a, b) => project.versioning.compare(b.version, a.version))
89
+ }, { project: { name: project.name } });
90
+ await write_file(project.changelog, updated_changelog_content);
91
91
  const file_operations = await diff(root);
92
92
  await reset(root);
93
93
  const platform = config.git.platform;
94
- const release_branch_name = `${config.git.default_release_branch_prefix}/${app.name}`;
94
+ const release_branch_name = `${config.git.default_release_branch_prefix}/${project.name}`;
95
95
  await platform.create_or_update_branch({
96
96
  branch_name: release_branch_name,
97
97
  base_branch_name: config.git.target_branch,
98
98
  file_operations,
99
- commit_message: `chore: prepare release ${app.name}@${next_version}`
99
+ commit_message: `chore: prepare release ${project.name}@${next_version}`
100
100
  });
101
101
  await platform.create_or_update_pull_request({
102
102
  head_branch_name: release_branch_name,
103
103
  base_branch_name: config.git.target_branch,
104
- title: `release: ${app.name}@${next_version}`,
104
+ title: `release: ${project.name}@${next_version}`,
105
105
  body: formatter.generate_pr_body({
106
- app: { name: app.name },
106
+ project: { name: project.name },
107
107
  current_version,
108
108
  next_version,
109
109
  changes: changes.list