calver-bump 0.1.7 → 1.0.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 +74 -7
- package/bin/calver-bump.js +151 -10
- package/package.json +15 -2
- package/src/changelog.js +267 -0
- package/src/files.js +83 -0
- package/src/git.js +82 -0
- package/src/index.js +85 -404
- package/test/calver.test.js +0 -69
- package/test/cli.test.js +0 -109
- package/test/release.test.js +0 -568
package/src/files.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { access, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export async function updatePackageVersion(cwd, version) {
|
|
5
|
+
const packagePath = path.join(cwd, 'package.json');
|
|
6
|
+
const pkg = JSON.parse(await readFile(packagePath, 'utf8'));
|
|
7
|
+
pkg.version = version;
|
|
8
|
+
await writeFile(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function updatePackageLock(cwd, version) {
|
|
12
|
+
for (const fileName of ['package-lock.json', 'npm-shrinkwrap.json']) {
|
|
13
|
+
const filePath = path.join(cwd, fileName);
|
|
14
|
+
let lock;
|
|
15
|
+
try {
|
|
16
|
+
lock = JSON.parse(await readFile(filePath, 'utf8'));
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT') continue;
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof lock.version === 'string') {
|
|
23
|
+
lock.version = version;
|
|
24
|
+
}
|
|
25
|
+
if (lock.packages?.[''] && typeof lock.packages[''].version === 'string') {
|
|
26
|
+
lock.packages[''].version = version;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
await writeFile(filePath, `${JSON.stringify(lock, null, 2)}\n`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function releaseFiles(cwd, options = {}) {
|
|
34
|
+
const candidates = [];
|
|
35
|
+
const files = [];
|
|
36
|
+
if (options.version !== false) {
|
|
37
|
+
candidates.push('package.json', 'package-lock.json', 'npm-shrinkwrap.json');
|
|
38
|
+
}
|
|
39
|
+
for (const candidate of candidates) {
|
|
40
|
+
if (await fileExists(path.join(cwd, candidate))) {
|
|
41
|
+
files.push(candidate);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (options.changelog !== false) {
|
|
45
|
+
files.push('CHANGELOG.md');
|
|
46
|
+
}
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function releaseWarnings(cwd, options = {}) {
|
|
51
|
+
if (!options.updatesVersion) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const warnings = [];
|
|
55
|
+
for (const fileName of ['pnpm-lock.yaml', 'yarn.lock']) {
|
|
56
|
+
if (await fileExists(path.join(cwd, fileName))) {
|
|
57
|
+
warnings.push(`${fileName} detected; calver-bump does not rewrite this lockfile because it does not store the root package version consistently.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return warnings;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function readChangelog(cwd) {
|
|
64
|
+
try {
|
|
65
|
+
return await readFile(path.join(cwd, 'CHANGELOG.md'), 'utf8');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error.code === 'ENOENT') return '';
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function writeChangelog(cwd, body) {
|
|
73
|
+
await writeFile(path.join(cwd, 'CHANGELOG.md'), body);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function fileExists(filePath) {
|
|
77
|
+
try {
|
|
78
|
+
await access(filePath);
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/git.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
|
|
4
|
+
const execFile = promisify(execFileCallback);
|
|
5
|
+
|
|
6
|
+
export async function git(cwd, args) {
|
|
7
|
+
return execFile('git', args, { cwd, encoding: 'utf8' });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function gitLines(cwd, args) {
|
|
11
|
+
const { stdout } = await git(cwd, args);
|
|
12
|
+
return stdout.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function gitCommits(cwd, range) {
|
|
16
|
+
const { stdout } = await git(cwd, ['log', '--pretty=format:%H%x00%s%x00%B%x1e', ...range]);
|
|
17
|
+
return stdout
|
|
18
|
+
.split('\x1e')
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.map((record) => {
|
|
21
|
+
const [hash, subject, body = ''] = record.replace(/^\n+|\n+$/g, '').split('\0');
|
|
22
|
+
return { hash, subject, body };
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function getRemoteUrl(cwd, remote) {
|
|
27
|
+
try {
|
|
28
|
+
const { stdout } = await git(cwd, ['remote', 'get-url', remote]);
|
|
29
|
+
return stdout.trim();
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function fetchTags(cwd, remote) {
|
|
36
|
+
try {
|
|
37
|
+
await git(cwd, ['remote', 'get-url', remote]);
|
|
38
|
+
await git(cwd, ['fetch', '--tags', remote]);
|
|
39
|
+
} catch {
|
|
40
|
+
// Local/offline repos can still release using tags already present.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function currentBranch(cwd) {
|
|
45
|
+
try {
|
|
46
|
+
const { stdout } = await git(cwd, ['branch', '--show-current']);
|
|
47
|
+
return stdout.trim() || 'HEAD';
|
|
48
|
+
} catch {
|
|
49
|
+
return 'HEAD';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function assertCleanWorktree(cwd) {
|
|
54
|
+
const status = await gitLines(cwd, ['status', '--porcelain']);
|
|
55
|
+
if (status.length > 0) {
|
|
56
|
+
throw new Error('Working tree is not clean. Commit or stash changes before releasing.');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function tagExists(cwd, tag) {
|
|
61
|
+
try {
|
|
62
|
+
await git(cwd, ['rev-parse', '--verify', '--quiet', `refs/tags/${tag}^{}`]);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function assertTagAvailable(cwd, tag) {
|
|
70
|
+
if (await tagExists(cwd, tag)) {
|
|
71
|
+
throw new Error(`Git tag ${tag} already exists. Choose another date/format or delete the existing tag before releasing.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function latestReachableTag(cwd) {
|
|
76
|
+
try {
|
|
77
|
+
const { stdout } = await git(cwd, ['describe', '--tags', '--abbrev=0']);
|
|
78
|
+
return stdout.trim() || null;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|