@afoures/auto-release 0.4.1 → 0.5.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
@@ -43,26 +43,26 @@ bunx add @afoures/auto-release
43
43
  Create `auto-release.config.ts`:
44
44
 
45
45
  ```typescript
46
- import { define_config } from 'auto-release'
47
- import { semver } from 'auto-release/versioning'
48
- import { github } from 'auto-release/providers'
49
- import { node } from 'auto-release/components'
46
+ import { define_config } from "@afoures/auto-release";
47
+ import { semver } from "@afoures/auto-release/versioning";
48
+ import { github } from "@afoures/auto-release/providers";
49
+ import { node } from "@afoures/auto-release/components";
50
50
 
51
51
  export default define_config({
52
52
  projects: {
53
- 'my-app': {
54
- components: [node('packages/my-app')],
53
+ "my-app": {
54
+ components: [node("packages/my-app")],
55
55
  versioning: semver(),
56
- changelog: 'CHANGELOG.md',
56
+ changelog: "CHANGELOG.md",
57
57
  },
58
58
  },
59
59
  git: {
60
60
  platform: github({
61
61
  token: process.env.GITHUB_TOKEN!,
62
- owner: 'your-org',
63
- repo: 'your-repo',
62
+ owner: "your-org",
63
+ repo: "your-repo",
64
64
  }),
65
- target_branch: 'main',
65
+ target_branch: "main",
66
66
  },
67
67
  })
68
68
  ```
@@ -3,12 +3,16 @@ declare class ChangeFile<kind extends string> {
3
3
  #private;
4
4
  constructor(props: {
5
5
  kind: kind;
6
+ index?: number;
6
7
  slug?: string;
7
8
  summary: string;
9
+ birthtime?: Date;
8
10
  });
9
11
  get kind(): kind;
12
+ get index(): number;
10
13
  get summary(): string;
11
14
  get filename(): string;
15
+ get birthtime(): Date;
12
16
  }
13
17
  //#endregion
14
18
  export { ChangeFile };
@@ -1,30 +1,40 @@
1
- import { list_files, read_file, write_file } from "./utils/fs.mjs";
1
+ import { get_file_stats, list_files, read_file, write_file } from "./utils/fs.mjs";
2
2
  import { basename, join } from "node:path";
3
3
  import { regex } from "arkregex";
4
4
  import { humanId } from "human-id";
5
5
 
6
6
  //#region src/lib/change-file.ts
7
- const CHANGE_FILENAME_REGEX = regex("^(?<kind>[a-z0-9-]+)\\.(?<slug>[a-z0-9-]+)\\.md$");
7
+ const CHANGE_FILENAME_REGEX = regex("^(?<kind>[a-z0-9-]+)\\.(?:(?<index>\\d+)-)?(?<slug>[a-z0-9-]+)\\.md$");
8
8
  var ChangeFile = class {
9
9
  #kind;
10
+ #index;
10
11
  #slug;
11
12
  #summary;
13
+ #birthtime;
12
14
  constructor(props) {
13
15
  this.#kind = props.kind;
16
+ this.#index = props.index ?? 1;
14
17
  this.#slug = props.slug ?? humanId({
15
18
  separator: "-",
16
19
  capitalize: false
17
20
  });
18
21
  this.#summary = props.summary;
22
+ this.#birthtime = props.birthtime ?? /* @__PURE__ */ new Date(0);
19
23
  }
20
24
  get kind() {
21
25
  return this.#kind;
22
26
  }
27
+ get index() {
28
+ return this.#index;
29
+ }
23
30
  get summary() {
24
31
  return this.#summary;
25
32
  }
26
33
  get filename() {
27
- return `${this.#kind}.${this.#slug}.md`;
34
+ return `${this.#kind}.${this.#index}-${this.#slug}.md`;
35
+ }
36
+ get birthtime() {
37
+ return this.#birthtime;
28
38
  }
29
39
  };
30
40
  async function save_change_file(change_file, to) {
@@ -36,21 +46,25 @@ async function save_change_file(change_file, to) {
36
46
  async function parse_change_file(path) {
37
47
  const filename = basename(path);
38
48
  const match = CHANGE_FILENAME_REGEX.exec(filename);
39
- if (!match) return /* @__PURE__ */ new Error(`Invalid change filename format: ${path} (expected: <kind>.<slug>.md)`);
49
+ if (!match) return /* @__PURE__ */ new Error(`Invalid change filename format: ${path} (expected: <kind>.<index>-<slug>.md)`);
40
50
  let file_content = await read_file(path);
41
51
  file_content = file_content?.trim() ?? null;
42
52
  if (file_content === null) return /* @__PURE__ */ new Error(`Change file is missing: ${path}`);
43
53
  if (!file_content) return /* @__PURE__ */ new Error("Change file is empty");
44
54
  const [title, ...rest] = file_content.split("\n");
45
55
  const text = [`- ${title}`, ...rest.map((line) => ` ${line}`)].join("\n");
56
+ const index = match.groups.index ? parseInt(match.groups.index, 10) : 1;
57
+ const birthtime = (await get_file_stats(path))?.birthtime ?? /* @__PURE__ */ new Date(0);
46
58
  return new ChangeFile({
59
+ index,
47
60
  slug: match.groups.slug,
48
61
  kind: match.groups.kind,
49
- summary: text
62
+ summary: text,
63
+ birthtime
50
64
  });
51
65
  }
52
66
  async function find_change_files(folder, { allowed_kinds }) {
53
- const files = await list_files(folder, { sort: "creation" });
67
+ const files = await list_files(folder, { sort: "name" });
54
68
  const list = [];
55
69
  const warnings = [];
56
70
  for (const filename of files) {
@@ -67,6 +81,11 @@ async function find_change_files(folder, { allowed_kinds }) {
67
81
  }
68
82
  list.push(change_file_or_error);
69
83
  }
84
+ list.sort((a, b) => {
85
+ if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
86
+ if (a.index !== b.index) return a.index - b.index;
87
+ return a.birthtime.getTime() - b.birthtime.getTime();
88
+ });
70
89
  return {
71
90
  list,
72
91
  warnings
@@ -1,7 +1,7 @@
1
1
  import { create_command } from "../cli.mjs";
2
2
  import { exists, read_file } from "../utils/fs.mjs";
3
3
  import { find_nearest_config } from "../config.mjs";
4
- import { ChangeFile, save_change_file } from "../change-file.mjs";
4
+ import { ChangeFile, find_change_files, save_change_file } from "../change-file.mjs";
5
5
  import { exec as exec$1 } from "../utils/exec.mjs";
6
6
  import { cancel, intro, isCancel, log, select, text } from "@clack/prompts";
7
7
  import { join, relative } from "node:path";
@@ -139,6 +139,8 @@ const record_change = create_command({
139
139
  status: "error",
140
140
  error: `Invalid change type "${change_type}". Valid types for ${project_name}: ${valid_types.join(", ")}`
141
141
  };
142
+ const project_change_dir = join(config.changes_dir, project_name);
143
+ const next_index = (await find_change_files(project_change_dir, { allowed_kinds: valid_types })).list.filter((f) => f.kind === change_type).length + 1;
142
144
  const initial_slug = humanId({
143
145
  separator: "-",
144
146
  capitalize: false
@@ -151,11 +153,12 @@ const record_change = create_command({
151
153
  const slug = generate_slug(description_input) || initial_slug;
152
154
  const change_file = new ChangeFile({
153
155
  kind: change_type,
156
+ index: next_index,
154
157
  slug,
155
158
  summary: ""
156
159
  });
157
160
  try {
158
- const file_path = await save_change_file(change_file, join(config.changes_dir, project_name));
161
+ const file_path = await save_change_file(change_file, project_change_dir);
159
162
  const editor = await get_editor(config.changes_dir);
160
163
  if (!editor) return {
161
164
  status: "error",
@@ -110,7 +110,7 @@ function default_formatter({ allowed_changes, display_map }) {
110
110
  generate_release_notes({ project, version }) {
111
111
  const hash = version.replaceAll(".", "");
112
112
  const file = `${project.changelog}#${hash}`;
113
- return `[See the changelog for ${project.name}@${version} release notes](${file})`;
113
+ return `See the changelog for release notes: [${project.name}@${version}](${file})`;
114
114
  },
115
115
  generate_pr_body({ project, current_version, next_version, changes }) {
116
116
  const lines = [];
@@ -69,6 +69,13 @@ async function list_files(dir, options) {
69
69
  return [];
70
70
  }
71
71
  }
72
+ async function get_file_stats(path) {
73
+ try {
74
+ return await stat(path);
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
72
79
 
73
80
  //#endregion
74
- export { delete_all_files_from_folder, exists, list_files, read_file, write_file };
81
+ export { delete_all_files_from_folder, exists, get_file_stats, list_files, read_file, write_file };
@@ -3,13 +3,13 @@
3
3
  Change files are stored in `.changes/<project-name>/` with format:
4
4
 
5
5
  ```
6
- <type>.<slug>.md
6
+ <type>.<index>-<slug>.md
7
7
  ```
8
8
 
9
9
  Examples:
10
10
 
11
- - `.changes/my-app/major.add-authentication.md`
12
- - `.changes/my-app/patch.fix-login-bug.md`
11
+ - `.changes/my-app/major.1-add-authentication.md`
12
+ - `.changes/my-app/patch.1-fix-login-bug.md`
13
13
 
14
14
  The change files folder can be customized.
15
15
 
@@ -6,18 +6,18 @@ The `projects` object defines each releasable unit:
6
6
 
7
7
  ```typescript
8
8
  projects: {
9
- 'my-app': {
9
+ "my-app": {
10
10
  // Components: where versions are read/written
11
11
  components: [
12
- node('packages/my-app'),
13
- node('packages/shared'),
12
+ node("packages/my-app"),
13
+ node("packages/shared"),
14
14
  ],
15
15
 
16
16
  // Versioning strategy
17
17
  versioning: semver(),
18
18
 
19
19
  // Changelog file path
20
- changelog: 'apps/my-app/CHANGELOG.md',
20
+ changelog: "apps/my-app/CHANGELOG.md",
21
21
  },
22
22
  }
23
23
  ```
@@ -104,7 +104,7 @@ Useful for grouped projects where some projects may not have changes in every re
104
104
  ## Versioning Strategies
105
105
 
106
106
  ```typescript
107
- import { semver, calver, markver } from 'auto-release/versioning'
107
+ import { semver, calver, markver } from "@afoures/auto-release/versioning"
108
108
 
109
109
  // Semantic versioning: 1.2.3
110
110
  versioning: semver() // Change types: major, minor, patch
@@ -121,15 +121,15 @@ versioning: markver() // Change types: marketing, feature, fix
121
121
  ### GitHub
122
122
 
123
123
  ```typescript
124
- import { github } from 'auto-release/providers'
124
+ import { github } from "@afoures/auto-release/providers"
125
125
 
126
126
  git: {
127
127
  platform: github({
128
128
  token: process.env.GITHUB_TOKEN!,
129
- owner: 'your-org',
130
- repo: 'your-repo',
129
+ owner: "your-org",
130
+ repo: "your-repo",
131
131
  }),
132
- target_branch: 'main',
132
+ target_branch: "main",
133
133
  tag_generator: ({ project, version }) => `${project.name}-${version}`,
134
134
  }
135
135
  ```
@@ -137,14 +137,14 @@ git: {
137
137
  ### GitLab
138
138
 
139
139
  ```typescript
140
- import { gitlab } from 'auto-release/providers'
140
+ import { gitlab } from "@afoures/auto-release/providers"
141
141
 
142
142
  git: {
143
143
  platform: gitlab({
144
144
  token: process.env.GITLAB_TOKEN!,
145
- project_id: 'your-project-id',
145
+ project_id: "your-project-id",
146
146
  }),
147
- target_branch: 'main',
147
+ target_branch: "main",
148
148
  }
149
149
  ```
150
150
 
@@ -184,12 +184,12 @@ Components define version sources:
184
184
  - **`php(path)`**: any PHP project with composer.json
185
185
 
186
186
  ```typescript
187
- import { node, expo, php } from 'auto-release/components'
187
+ import { node, expo, php } from "@afoures/auto-release/components"
188
188
 
189
189
  components: [
190
- node('packages/web'),
191
- bun('packages/bff'),
192
- expo('apps/mobile'),
193
- php('packages/api'),
190
+ node("packages/web"),
191
+ bun("packages/bff"),
192
+ expo("apps/mobile"),
193
+ php("packages/api"),
194
194
  ]
195
195
  ```
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@afoures/auto-release",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "A file based release management tool for monorepos",
5
+ "keywords": [],
5
6
  "homepage": "https://github.com/afoures/auto-release#readme",
6
7
  "bugs": {
7
8
  "url": "https://github.com/afoures/auto-release/issues"
@@ -21,7 +22,6 @@
21
22
  "package.json",
22
23
  "README.md"
23
24
  ],
24
- "keywords": [],
25
25
  "type": "module",
26
26
  "main": "./dist/index.mjs",
27
27
  "module": "./dist/index.mjs",