@naturalcycles/dev-lib 20.63.0 → 20.64.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.
|
@@ -16,10 +16,18 @@ export declare function getCurrentBranch(): string;
|
|
|
16
16
|
*/
|
|
17
17
|
export declare function ensureFullGitHistory(repo: RepoInfo): void;
|
|
18
18
|
/**
|
|
19
|
-
* Abort if
|
|
20
|
-
* would
|
|
19
|
+
* Abort if the branch was rewritten since this checkout (e.g. force-pushed) and no longer
|
|
20
|
+
* contains HEAD: the release tag would land on an orphaned commit, invisible to the next release,
|
|
21
|
+
* which would then try to publish the same version again.
|
|
22
|
+
*
|
|
23
|
+
* A remote that merely moved forward is fine: the plan (version, notes) comes from `lastTag..HEAD`
|
|
24
|
+
* and the tag goes on HEAD, so commits ahead of HEAD can't affect it - they are released by their
|
|
25
|
+
* own run. Aborting on them would silently skip releases in repos whose CI runs the release only
|
|
26
|
+
* when package files change (the newer commits may not trigger a run at all).
|
|
27
|
+
*
|
|
28
|
+
* No-op if the branch has no remote counterpart.
|
|
21
29
|
*/
|
|
22
|
-
export declare function
|
|
30
|
+
export declare function checkHeadIsOnRemoteBranch(branch: string, repo: RepoInfo): void;
|
|
23
31
|
/**
|
|
24
32
|
* All git tags reachable from HEAD.
|
|
25
33
|
* Reachability matters: on a prerelease branch this naturally excludes tags of other
|
package/dist/release/git.util.js
CHANGED
|
@@ -56,10 +56,18 @@ export function ensureFullGitHistory(repo) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
* Abort if
|
|
60
|
-
* would
|
|
59
|
+
* Abort if the branch was rewritten since this checkout (e.g. force-pushed) and no longer
|
|
60
|
+
* contains HEAD: the release tag would land on an orphaned commit, invisible to the next release,
|
|
61
|
+
* which would then try to publish the same version again.
|
|
62
|
+
*
|
|
63
|
+
* A remote that merely moved forward is fine: the plan (version, notes) comes from `lastTag..HEAD`
|
|
64
|
+
* and the tag goes on HEAD, so commits ahead of HEAD can't affect it - they are released by their
|
|
65
|
+
* own run. Aborting on them would silently skip releases in repos whose CI runs the release only
|
|
66
|
+
* when package files change (the newer commits may not trigger a run at all).
|
|
67
|
+
*
|
|
68
|
+
* No-op if the branch has no remote counterpart.
|
|
61
69
|
*/
|
|
62
|
-
export function
|
|
70
|
+
export function checkHeadIsOnRemoteBranch(branch, repo) {
|
|
63
71
|
const remote = getAuthenticatedRemote(repo);
|
|
64
72
|
let remoteSha;
|
|
65
73
|
try {
|
|
@@ -69,13 +77,26 @@ export function checkHeadIsUpToDateWithRemote(branch, repo) {
|
|
|
69
77
|
?.trim();
|
|
70
78
|
}
|
|
71
79
|
catch {
|
|
72
|
-
console.log(dimGrey('git ls-remote failed, skipping
|
|
80
|
+
console.log(dimGrey('git ls-remote failed, skipping remote branch check'));
|
|
73
81
|
return;
|
|
74
82
|
}
|
|
75
83
|
if (!remoteSha)
|
|
76
84
|
return;
|
|
77
85
|
const headSha = exec2.exec('git rev-parse HEAD').trim();
|
|
78
|
-
|
|
86
|
+
if (remoteSha === headSha)
|
|
87
|
+
return;
|
|
88
|
+
// The branch moved since checkout: fetch its tip to tell a fast-forward from a rewrite
|
|
89
|
+
try {
|
|
90
|
+
exec2.exec(`git fetch --quiet "${remote}" "refs/heads/${branch}"`);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Sanitized: the original error contains the failed command incl. the token
|
|
94
|
+
throw new Error(`git fetch of remote ${branch} failed - cannot verify that it still contains HEAD, aborting release`);
|
|
95
|
+
}
|
|
96
|
+
const localOnlyCommits = Number(exec2.exec('git rev-list --count FETCH_HEAD..HEAD').trim());
|
|
97
|
+
_assert(localOnlyCommits === 0, `Remote ${branch} (${remoteSha.slice(0, 7)}) was rewritten and no longer contains HEAD (${headSha.slice(0, 7)}) - aborting release`);
|
|
98
|
+
const ahead = exec2.exec('git rev-list --count HEAD..FETCH_HEAD').trim();
|
|
99
|
+
console.log(dimGrey(`Remote ${branch} is ${ahead} commit(s) ahead of HEAD (${headSha.slice(0, 7)}) - releasing from HEAD, the newer commits get their own run`));
|
|
79
100
|
}
|
|
80
101
|
/**
|
|
81
102
|
* All git tags reachable from HEAD.
|
|
@@ -7,7 +7,7 @@ import { fs2 } from '@naturalcycles/nodejs-lib/fs2';
|
|
|
7
7
|
import picomatch from 'picomatch';
|
|
8
8
|
import { readDevLibConfigIfPresent } from '../config.js';
|
|
9
9
|
import { analyzeCommits, parseCommit } from './commit.util.js';
|
|
10
|
-
import {
|
|
10
|
+
import { checkHeadIsOnRemoteBranch, createAndPushTag, ensureFullGitHistory, getCommitsSince, getCurrentBranch, getReachableTags, getRepoInfo, } from './git.util.js';
|
|
11
11
|
import { GithubApi } from './github.util.js';
|
|
12
12
|
import { generateReleaseNotes } from './notes.util.js';
|
|
13
13
|
import { getLastRelease, getNextVersion, getVersionsFromTags } from './version.util.js';
|
|
@@ -51,7 +51,7 @@ export async function releaseCommand() {
|
|
|
51
51
|
}
|
|
52
52
|
ensureFullGitHistory(repo);
|
|
53
53
|
if (!dryRun) {
|
|
54
|
-
|
|
54
|
+
checkHeadIsOnRemoteBranch(branch, repo);
|
|
55
55
|
}
|
|
56
56
|
const mode = resolveReleaseMode(cfg.mode);
|
|
57
57
|
const packages = discoverReleasePackages(mode, argv.filter);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { commonLoggerCreate } from '@naturalcycles/js-lib/log';
|
|
2
|
-
import {
|
|
2
|
+
import { logEntryToString } from '@naturalcycles/nodejs-lib';
|
|
3
3
|
export function silentConsole() {
|
|
4
4
|
console.log = () => { };
|
|
5
5
|
console.debug = () => { };
|
|
@@ -9,13 +9,9 @@ export function silentConsole() {
|
|
|
9
9
|
console.time = () => { };
|
|
10
10
|
console.table = () => { };
|
|
11
11
|
}
|
|
12
|
-
export const testLogger = commonLoggerCreate(
|
|
12
|
+
export const testLogger = commonLoggerCreate(entry => {
|
|
13
13
|
if (process.env['TEST_SILENT'])
|
|
14
14
|
return; // no-op
|
|
15
|
-
process.stdout.write(
|
|
16
|
-
.map(a => _inspect(a, {
|
|
17
|
-
includeErrorStack: true,
|
|
18
|
-
}))
|
|
19
|
-
.join(' ') + '\n');
|
|
15
|
+
process.stdout.write(logEntryToString(entry) + '\n');
|
|
20
16
|
});
|
|
21
17
|
export const testLog = testLogger.log.bind(testLogger);
|