@programinglive/commiter 1.1.1 β 1.1.4
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/CHANGELOG.md +16 -0
- package/docs/release-notes/RELEASE_NOTES.md +28 -1
- package/package.json +1 -1
- package/scripts/release.js +63 -4
- package/scripts/update-release-notes.js +182 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [1.1.4](https://github.com/programinglive/commiter/compare/v1.1.3...v1.1.4) (2025-11-05)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### π Bug Fixes
|
|
9
|
+
|
|
10
|
+
* simplify release notes staging to avoid git ref conflicts ([d4077aa](https://github.com/programinglive/commiter/commit/d4077aa1544e07cb64d089a440cf9461da42c413))
|
|
11
|
+
|
|
12
|
+
### [1.1.3](https://github.com/programinglive/commiter/compare/v1.1.2...v1.1.3) (2025-11-05)
|
|
13
|
+
|
|
14
|
+
### [1.1.2](https://github.com/programinglive/commiter/compare/v1.1.1...v1.1.2) (2025-11-05)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### π§Ή Chores
|
|
18
|
+
|
|
19
|
+
* auto-update release notes during release ([99d1043](https://github.com/programinglive/commiter/commit/99d104374794a6a46dc34e8a91fa8b30ae9b297b))
|
|
20
|
+
|
|
5
21
|
### [1.1.1](https://github.com/programinglive/commiter/compare/v1.1.0...v1.1.1) (2025-11-05)
|
|
6
22
|
|
|
7
23
|
|
|
@@ -4,6 +4,10 @@ This document summarizes every published version of `@programinglive/commiter`.
|
|
|
4
4
|
|
|
5
5
|
| Version | Date | Highlights |
|
|
6
6
|
|---------|------|------------|
|
|
7
|
+
| 1.1.4 | 2025-11-05 | simplify release notes staging to avoid git ref conflicts (d4077aa) |
|
|
8
|
+
| 1.1.3 | 2025-11-05 | See CHANGELOG for details. |
|
|
9
|
+
| 1.1.2 | 2025-11-05 | auto-update release notes during release (99d1043) |
|
|
10
|
+
| 1.1.1 | 2025-11-05 | π Bug Fix β removed the fs.F_OK deprecation warning from release runs. |
|
|
7
11
|
| 1.1.0 | 2025-10-29 | π Documentation β clarified release automation flow. |
|
|
8
12
|
| 1.0.12 | 2025-10-29 | β¨ Feature β autodetects project test command before releasing. |
|
|
9
13
|
| 1.0.11 | 2025-10-29 | π Documentation β added project status and download badges. |
|
|
@@ -19,7 +23,30 @@ This document summarizes every published version of `@programinglive/commiter`.
|
|
|
19
23
|
| 1.0.1 | 2025-10-18 | π Bug Fix β aligned commitlint config with project module type. |
|
|
20
24
|
| 1.0.0 | 2025-10-17 | β¨ Initial release β bootstrapped conventional release tooling; added community docs and metadata. |
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## 1.1.4 β π Bug Fixes
|
|
30
|
+
|
|
31
|
+
Released on **2025-11-05**.
|
|
32
|
+
|
|
33
|
+
- simplify release notes staging to avoid git ref conflicts (d4077aa)
|
|
34
|
+
|
|
35
|
+
## 1.1.3
|
|
36
|
+
|
|
37
|
+
Released on **2025-11-05**.
|
|
38
|
+
|
|
39
|
+
- See CHANGELOG for details.
|
|
40
|
+
|
|
41
|
+
## 1.1.2 β π§Ή Chores
|
|
42
|
+
|
|
43
|
+
Released on **2025-11-05**.
|
|
44
|
+
|
|
45
|
+
- auto-update release notes during release (99d1043)
|
|
46
|
+
|
|
47
|
+
## 1.1.1 β fs.F_OK Deprecation Warning Fix
|
|
48
|
+
|
|
49
|
+
Released on **2025-11-05**.
|
|
23
50
|
- Eliminated the `[DEP0176] fs.F_OK` warning emitted during release commands by injecting a preload script that transparently rewrites `fs.F_OK` usage inside `standard-version`.
|
|
24
51
|
- Ensured the preload script runs for both in-process unit tests and the child process that executes `standard-version` by appending a `--require` flag to `NODE_OPTIONS`.
|
|
25
52
|
- Updated the test suite to cover the preload behaviour and the adjusted release workflow, switching the project to Nodeβs built-in test runner (`node --test`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/commiter",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Commiter keeps repositories release-ready by enforcing conventional commits, generating icon-rich changelog entries, and orchestrating semantic version bumps without manual toil. It bootstraps Husky hooks, commitlint rules, and release scripts that inspect history, detect framework-specific test commands, run them automatically, tag git releases, coordinate npm publishing, surface release metrics, enforce project-specific checks, and give maintainers observability across distributed teams. Plus!",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/scripts/release.js
CHANGED
|
@@ -4,6 +4,8 @@ const { spawnSync } = require('child_process');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
|
+
const { updateReleaseNotes } = require('./update-release-notes');
|
|
8
|
+
|
|
7
9
|
const fsFokPreloadPath = path.join(__dirname, 'preload', 'fs-f-ok.cjs');
|
|
8
10
|
const FS_FOK_PRELOAD_FLAG = buildPreloadFlag(fsFokPreloadPath);
|
|
9
11
|
|
|
@@ -132,7 +134,13 @@ function runProjectTests({ spawn = spawnSync, env = process.env, cwd = process.c
|
|
|
132
134
|
});
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
function runRelease({
|
|
137
|
+
function runRelease({
|
|
138
|
+
argv = process.argv,
|
|
139
|
+
env = process.env,
|
|
140
|
+
spawn = spawnSync,
|
|
141
|
+
cwd = process.cwd(),
|
|
142
|
+
dependencies = {}
|
|
143
|
+
} = {}) {
|
|
136
144
|
const { releaseType: cliReleaseType, extraArgs } = getCliArguments(argv);
|
|
137
145
|
const inferredReleaseType = cliReleaseType || getNpmRunArgument(env);
|
|
138
146
|
const standardVersionArgs = buildStandardVersionArgs({
|
|
@@ -140,6 +148,12 @@ function runRelease({ argv = process.argv, env = process.env, spawn = spawnSync
|
|
|
140
148
|
extraArgs
|
|
141
149
|
});
|
|
142
150
|
|
|
151
|
+
const checkWorkingTreeClean = dependencies.isWorkingTreeClean || (() => isWorkingTreeClean({ cwd }));
|
|
152
|
+
const workingTreeClean = checkWorkingTreeClean();
|
|
153
|
+
if (!workingTreeClean) {
|
|
154
|
+
throw new Error('Working tree has uncommitted changes. Commit or stash before running release.');
|
|
155
|
+
}
|
|
156
|
+
|
|
143
157
|
const testResult = runProjectTests({ spawn, env });
|
|
144
158
|
if (testResult && typeof testResult.status === 'number' && testResult.status !== 0) {
|
|
145
159
|
return testResult;
|
|
@@ -147,10 +161,34 @@ function runRelease({ argv = process.argv, env = process.env, spawn = spawnSync
|
|
|
147
161
|
|
|
148
162
|
const standardVersionBin = require.resolve('standard-version/bin/cli.js');
|
|
149
163
|
const childEnv = appendPreloadToNodeOptions(env, FS_FOK_PRELOAD_FLAG);
|
|
150
|
-
|
|
164
|
+
const releaseResult = spawn(process.execPath, [standardVersionBin, ...standardVersionArgs], {
|
|
151
165
|
stdio: 'inherit',
|
|
152
|
-
env: childEnv
|
|
166
|
+
env: childEnv,
|
|
167
|
+
cwd
|
|
153
168
|
});
|
|
169
|
+
|
|
170
|
+
if (releaseResult && typeof releaseResult.status === 'number' && releaseResult.status !== 0) {
|
|
171
|
+
return releaseResult;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const releaseNotesPath = dependencies.releaseNotesPath || path.join('docs', 'release-notes', 'RELEASE_NOTES.md');
|
|
175
|
+
const updateNotes = dependencies.updateReleaseNotes || ((options = {}) => updateReleaseNotes({ rootDir: cwd, ...options }));
|
|
176
|
+
|
|
177
|
+
let notesUpdated = false;
|
|
178
|
+
try {
|
|
179
|
+
notesUpdated = updateNotes();
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.warn(`β οΈ Skipping release notes update: ${error.message}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (notesUpdated) {
|
|
185
|
+
const gitAddResult = spawnSync('git', ['add', releaseNotesPath], { stdio: 'inherit', cwd });
|
|
186
|
+
if (!gitAddResult || typeof gitAddResult.status !== 'number' || gitAddResult.status !== 0) {
|
|
187
|
+
console.warn('β οΈ Release notes updated but failed to stage. Please add manually: git add ' + releaseNotesPath);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return releaseResult;
|
|
154
192
|
}
|
|
155
193
|
|
|
156
194
|
if (require.main === module) {
|
|
@@ -174,7 +212,8 @@ module.exports = {
|
|
|
174
212
|
loadPackageJson,
|
|
175
213
|
detectPackageManager,
|
|
176
214
|
runProjectTests,
|
|
177
|
-
runRelease
|
|
215
|
+
runRelease,
|
|
216
|
+
isWorkingTreeClean
|
|
178
217
|
};
|
|
179
218
|
|
|
180
219
|
function buildPreloadFlag(filePath) {
|
|
@@ -191,3 +230,23 @@ function appendPreloadToNodeOptions(env, preloadFlag) {
|
|
|
191
230
|
nextEnv.NODE_OPTIONS = existing ? `${existing} ${preloadFlag}` : preloadFlag;
|
|
192
231
|
return nextEnv;
|
|
193
232
|
}
|
|
233
|
+
|
|
234
|
+
function isWorkingTreeClean({ cwd = process.cwd() } = {}) {
|
|
235
|
+
const result = spawnSync('git', ['status', '--porcelain'], {
|
|
236
|
+
cwd,
|
|
237
|
+
encoding: 'utf8'
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (result.error) {
|
|
241
|
+
throw result.error;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (typeof result.status === 'number' && result.status !== 0) {
|
|
245
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : '';
|
|
246
|
+
const message = stderr || 'Failed to determine working tree status.';
|
|
247
|
+
throw new Error(message);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const output = typeof result.stdout === 'string' ? result.stdout : '';
|
|
251
|
+
return output.trim().length === 0;
|
|
252
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function updateReleaseNotes({
|
|
7
|
+
rootDir = process.cwd(),
|
|
8
|
+
releaseNotesRelativePath = path.join('docs', 'release-notes', 'RELEASE_NOTES.md'),
|
|
9
|
+
changelogRelativePath = 'CHANGELOG.md',
|
|
10
|
+
packageJsonRelativePath = 'package.json',
|
|
11
|
+
now = () => new Date()
|
|
12
|
+
} = {}) {
|
|
13
|
+
const releaseNotesPath = path.join(rootDir, releaseNotesRelativePath);
|
|
14
|
+
if (!fs.existsSync(releaseNotesPath)) {
|
|
15
|
+
console.log(`βΉοΈ Release notes file not found at ${releaseNotesPath}; skipping update.`);
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const packageJsonPath = path.join(rootDir, packageJsonRelativePath);
|
|
20
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
21
|
+
console.log(`βΉοΈ package.json not found at ${packageJsonPath}; skipping release notes update.`);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const changelogPath = path.join(rootDir, changelogRelativePath);
|
|
26
|
+
if (!fs.existsSync(changelogPath)) {
|
|
27
|
+
console.log(`βΉοΈ CHANGELOG not found at ${changelogPath}; skipping release notes update.`);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
32
|
+
const version = packageJson.version;
|
|
33
|
+
if (!version) {
|
|
34
|
+
console.log('βΉοΈ package.json version is missing; skipping release notes update.');
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const releaseNotesContent = fs.readFileSync(releaseNotesPath, 'utf8');
|
|
39
|
+
if (releaseNotesContent.includes(`| ${version} `) || releaseNotesContent.includes(`## ${version}`)) {
|
|
40
|
+
console.log(`βΉοΈ Release notes already contain version ${version}; skipping.`);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const changelogContent = fs.readFileSync(changelogPath, 'utf8');
|
|
45
|
+
const changelogInfo = extractChangelogInfo({ changelogContent, version });
|
|
46
|
+
|
|
47
|
+
const releaseDate = changelogInfo.date || formatDate(now());
|
|
48
|
+
const highlight = changelogInfo.highlight || changelogInfo.heading || 'See CHANGELOG for details.';
|
|
49
|
+
const sectionHeading = changelogInfo.heading || '';
|
|
50
|
+
const detailBullets = Array.isArray(changelogInfo.bullets) && changelogInfo.bullets.length > 0
|
|
51
|
+
? changelogInfo.bullets
|
|
52
|
+
: [highlight];
|
|
53
|
+
|
|
54
|
+
const updatedContent = insertReleaseNotesEntry({
|
|
55
|
+
content: releaseNotesContent,
|
|
56
|
+
version,
|
|
57
|
+
releaseDate,
|
|
58
|
+
highlight,
|
|
59
|
+
sectionHeading,
|
|
60
|
+
detailBullets
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
fs.writeFileSync(releaseNotesPath, updatedContent);
|
|
64
|
+
console.log(`β
Added release notes entry for version ${version}.`);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function insertReleaseNotesEntry({ content, version, releaseDate, highlight, sectionHeading, detailBullets }) {
|
|
69
|
+
const lines = content.split('\n');
|
|
70
|
+
const headerSeparatorIndex = lines.findIndex((line) => line.trim().startsWith('|---------'));
|
|
71
|
+
if (headerSeparatorIndex === -1) {
|
|
72
|
+
throw new Error('Unable to locate release notes table header.');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const newRow = `| ${version} | ${releaseDate} | ${escapePipes(highlight)} |`;
|
|
76
|
+
lines.splice(headerSeparatorIndex + 1, 0, newRow);
|
|
77
|
+
|
|
78
|
+
const sectionLines = [];
|
|
79
|
+
sectionLines.push('');
|
|
80
|
+
const trimmedHeading = typeof sectionHeading === 'string' ? sectionHeading.trim() : '';
|
|
81
|
+
const headingSuffix = trimmedHeading ? ` β ${trimmedHeading}` : '';
|
|
82
|
+
sectionLines.push(`## ${version}${headingSuffix}`);
|
|
83
|
+
sectionLines.push('');
|
|
84
|
+
sectionLines.push(`Released on **${releaseDate}**.`);
|
|
85
|
+
sectionLines.push('');
|
|
86
|
+
for (const bullet of detailBullets) {
|
|
87
|
+
sectionLines.push(`- ${bullet}`);
|
|
88
|
+
}
|
|
89
|
+
sectionLines.push('');
|
|
90
|
+
|
|
91
|
+
const firstSectionIndex = lines.findIndex((line, idx) => idx > headerSeparatorIndex && line.startsWith('## '));
|
|
92
|
+
if (firstSectionIndex === -1) {
|
|
93
|
+
lines.push(...sectionLines);
|
|
94
|
+
} else {
|
|
95
|
+
lines.splice(firstSectionIndex, 0, ...sectionLines);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function extractChangelogInfo({ changelogContent, version }) {
|
|
102
|
+
const versionHeadingRegex = new RegExp(`^### \\[${escapeRegExp(version)}\\][^\n]*`, 'm');
|
|
103
|
+
const match = changelogContent.match(versionHeadingRegex);
|
|
104
|
+
if (!match) {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const headingIndex = match.index;
|
|
109
|
+
const rest = changelogContent.slice(headingIndex + match[0].length);
|
|
110
|
+
const nextVersionRegex = /^### \[[^\n]*\]/m;
|
|
111
|
+
const nextVersionMatch = nextVersionRegex.exec(rest);
|
|
112
|
+
const section = nextVersionMatch ? rest.slice(0, nextVersionMatch.index) : rest;
|
|
113
|
+
|
|
114
|
+
const dateMatch = match[0].match(/\((\d{4}-\d{2}-\d{2})\)/);
|
|
115
|
+
const date = dateMatch ? dateMatch[1] : undefined;
|
|
116
|
+
|
|
117
|
+
const categoryRegex = /^###\s+([^\n]+)$/m;
|
|
118
|
+
const categoryMatch = section.match(categoryRegex);
|
|
119
|
+
let heading;
|
|
120
|
+
if (categoryMatch) {
|
|
121
|
+
heading = sanitizeText(categoryMatch[1]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const bullets = [];
|
|
125
|
+
const bulletRegex = /^\*\s+([^\n]+)$/gm;
|
|
126
|
+
let bulletMatch;
|
|
127
|
+
while ((bulletMatch = bulletRegex.exec(section)) !== null) {
|
|
128
|
+
const sanitized = sanitizeText(bulletMatch[1]);
|
|
129
|
+
if (!bullets.includes(sanitized)) {
|
|
130
|
+
bullets.push(sanitized);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const highlight = bullets.length > 0 ? bullets[0] : undefined;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
date,
|
|
138
|
+
highlight,
|
|
139
|
+
heading,
|
|
140
|
+
bullets
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function sanitizeText(text) {
|
|
145
|
+
return text
|
|
146
|
+
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
|
|
147
|
+
.replace(/\s+/g, ' ')
|
|
148
|
+
.trim();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function escapeRegExp(string) {
|
|
152
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function escapePipes(text) {
|
|
156
|
+
return text.replace(/\|/g, '\\|');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function formatDate(date) {
|
|
160
|
+
const year = date.getFullYear();
|
|
161
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
162
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
163
|
+
return `${year}-${month}-${day}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
updateReleaseNotes,
|
|
168
|
+
insertReleaseNotesEntry,
|
|
169
|
+
extractChangelogInfo,
|
|
170
|
+
sanitizeText,
|
|
171
|
+
escapePipes,
|
|
172
|
+
formatDate
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (require.main === module) {
|
|
176
|
+
try {
|
|
177
|
+
updateReleaseNotes();
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error(`β Failed to update release notes: ${error.message}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
}
|