@naturalcycles/dev-lib 20.52.2 → 20.52.3

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.
@@ -8,14 +8,18 @@ export declare function getCurrentBranch(): string;
8
8
  /**
9
9
  * Make sure the full git history and all tags are available:
10
10
  * CI checkouts are often shallow and without tags, while release calculation is based on tags.
11
- * Tolerates fetch failures (e.g. offline local dry-run) with a warning.
11
+ *
12
+ * On a complete (non-shallow) clone a failed fetch is tolerated with a warning (e.g. offline
13
+ * local dry-run) - the local tags are complete enough. On a shallow clone a failed unshallow
14
+ * is fatal: a truncated history hides release tags and would make a released package look like
15
+ * a first release, publishing a wrong version.
12
16
  */
13
- export declare function ensureFullGitHistory(): void;
17
+ export declare function ensureFullGitHistory(repo: RepoInfo): void;
14
18
  /**
15
19
  * Abort if someone pushed to the branch since this checkout - releasing from a stale HEAD
16
20
  * would compute wrong versions/notes. No-op if the branch has no remote counterpart.
17
21
  */
18
- export declare function checkHeadIsUpToDateWithRemote(branch: string): void;
22
+ export declare function checkHeadIsUpToDateWithRemote(branch: string, repo: RepoInfo): void;
19
23
  /**
20
24
  * All git tags reachable from HEAD.
21
25
  * Reachability matters: on a prerelease branch this naturally excludes tags of other
@@ -29,7 +33,5 @@ export declare function getReachableTags(): string[];
29
33
  export declare function getCommitsSince(tag: string | undefined, relativeDir?: string): RawCommit[];
30
34
  /**
31
35
  * Create the release tag on HEAD and push it to origin.
32
- * Uses a token-authenticated url when GITHUB_TOKEN is available,
33
- * since CI checkouts don't persist git credentials.
34
36
  */
35
37
  export declare function createAndPushTag(tag: string, repo: RepoInfo): void;
@@ -24,33 +24,52 @@ export function getCurrentBranch() {
24
24
  /**
25
25
  * Make sure the full git history and all tags are available:
26
26
  * CI checkouts are often shallow and without tags, while release calculation is based on tags.
27
- * Tolerates fetch failures (e.g. offline local dry-run) with a warning.
27
+ *
28
+ * On a complete (non-shallow) clone a failed fetch is tolerated with a warning (e.g. offline
29
+ * local dry-run) - the local tags are complete enough. On a shallow clone a failed unshallow
30
+ * is fatal: a truncated history hides release tags and would make a released package look like
31
+ * a first release, publishing a wrong version.
28
32
  */
29
- export function ensureFullGitHistory() {
30
- try {
31
- const shallow = exec2.exec('git rev-parse --is-shallow-repository').trim() === 'true';
32
- if (shallow) {
33
- exec2.exec('git fetch --unshallow --tags --quiet', { log: true });
33
+ export function ensureFullGitHistory(repo) {
34
+ // Fetch from an authenticated url, not `origin`: private repos reject unauthenticated fetches,
35
+ // and CI checkouts don't persist git credentials (persist-credentials: false).
36
+ const remote = getAuthenticatedRemote(repo);
37
+ const shallow = exec2.exec('git rev-parse --is-shallow-repository').trim() === 'true';
38
+ if (shallow) {
39
+ console.log(dimGrey('git fetch --unshallow --tags'));
40
+ try {
41
+ exec2.exec(`git fetch --unshallow --tags --quiet "${remote}"`);
34
42
  }
35
- else {
36
- exec2.exec('git fetch --tags --quiet', { log: true });
43
+ catch {
44
+ // Sanitized: the original error contains the failed command incl. the token
45
+ throw new Error('git fetch --unshallow failed - refusing to release with a shallow git history, as version calculation depends on it');
37
46
  }
38
47
  }
39
- catch (err) {
40
- console.log(dimGrey(`git fetch failed, continuing with local tags/history: ${err}`));
48
+ else {
49
+ console.log(dimGrey('git fetch --tags'));
50
+ try {
51
+ exec2.exec(`git fetch --tags --quiet "${remote}"`);
52
+ }
53
+ catch {
54
+ console.log(dimGrey('git fetch --tags failed, continuing with local tags/history'));
55
+ }
41
56
  }
42
57
  }
43
58
  /**
44
59
  * Abort if someone pushed to the branch since this checkout - releasing from a stale HEAD
45
60
  * would compute wrong versions/notes. No-op if the branch has no remote counterpart.
46
61
  */
47
- export function checkHeadIsUpToDateWithRemote(branch) {
62
+ export function checkHeadIsUpToDateWithRemote(branch, repo) {
63
+ const remote = getAuthenticatedRemote(repo);
48
64
  let remoteSha;
49
65
  try {
50
- remoteSha = exec2.exec(`git ls-remote origin "refs/heads/${branch}"`).split('\t')[0]?.trim();
66
+ remoteSha = exec2
67
+ .exec(`git ls-remote "${remote}" "refs/heads/${branch}"`)
68
+ .split('\t')[0]
69
+ ?.trim();
51
70
  }
52
- catch (err) {
53
- console.log(dimGrey(`git ls-remote failed, skipping up-to-date check: ${err}`));
71
+ catch {
72
+ console.log(dimGrey('git ls-remote failed, skipping up-to-date check'));
54
73
  return;
55
74
  }
56
75
  if (!remoteSha)
@@ -91,20 +110,26 @@ export function getCommitsSince(tag, relativeDir) {
91
110
  }
92
111
  /**
93
112
  * Create the release tag on HEAD and push it to origin.
94
- * Uses a token-authenticated url when GITHUB_TOKEN is available,
95
- * since CI checkouts don't persist git credentials.
96
113
  */
97
114
  export function createAndPushTag(tag, repo) {
98
115
  exec2.exec(`git tag "${tag}"`);
99
- const token = process.env['GITHUB_TOKEN'] || process.env['GH_TOKEN'];
100
- const pushUrl = token
101
- ? `https://x-access-token:${token}@github.com/${repo.owner}/${repo.repo}.git`
102
- : 'origin';
103
116
  try {
104
- exec2.exec(`git push "${pushUrl}" "refs/tags/${tag}"`);
117
+ exec2.exec(`git push "${getAuthenticatedRemote(repo)}" "refs/tags/${tag}"`);
105
118
  }
106
119
  catch {
107
120
  // Sanitized rethrow: the original error message contains the failed command incl. the token
108
121
  throw new Error(`Failed to push tag ${tag} to origin`);
109
122
  }
110
123
  }
124
+ /**
125
+ * Token-authenticated remote url when GITHUB_TOKEN is available, `origin` otherwise.
126
+ * CI checkouts don't persist git credentials (persist-credentials: false), so all remote
127
+ * git operations (fetch, ls-remote, push) go through this.
128
+ * NEVER log commands containing this url, and sanitize their errors - they carry the token.
129
+ */
130
+ function getAuthenticatedRemote(repo) {
131
+ const token = process.env['GITHUB_TOKEN'] || process.env['GH_TOKEN'];
132
+ return token
133
+ ? `https://x-access-token:${token}@github.com/${repo.owner}/${repo.repo}.git`
134
+ : 'origin';
135
+ }
@@ -50,9 +50,9 @@ export async function releaseCommand() {
50
50
  if (!dryRun) {
51
51
  _assert(token, 'GITHUB_TOKEN (or GH_TOKEN) is required to release, or use --dry-run');
52
52
  }
53
- ensureFullGitHistory();
53
+ ensureFullGitHistory(repo);
54
54
  if (!dryRun) {
55
- checkHeadIsUpToDateWithRemote(branch);
55
+ checkHeadIsUpToDateWithRemote(branch, repo);
56
56
  }
57
57
  const mode = resolveReleaseMode(cfg.mode);
58
58
  const packages = discoverReleasePackages(mode, argv.filter);
@@ -49,9 +49,12 @@ export function getNextVersion(input) {
49
49
  const { prereleaseId } = channel;
50
50
  if (!lastRelease) {
51
51
  const { firstReleaseVersion } = input;
52
+ // 0.0.0 is the "version is managed by git tags" placeholder, never a real first release
52
53
  const first = (firstReleaseVersion &&
53
54
  !semver.prerelease(firstReleaseVersion) &&
54
- semver.valid(firstReleaseVersion)) ||
55
+ semver.valid(firstReleaseVersion) &&
56
+ semver.gt(firstReleaseVersion, '0.0.0') &&
57
+ firstReleaseVersion) ||
55
58
  '1.0.0';
56
59
  return prereleaseId ? `${first}-${prereleaseId}.1` : first;
57
60
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/dev-lib",
3
3
  "type": "module",
4
- "version": "20.52.2",
4
+ "version": "20.52.3",
5
5
  "dependencies": {
6
6
  "prompts": "^2",
7
7
  "@naturalcycles/js-lib": "^15",