build-strap 5.2.1 → 6.0.0-main.134
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 +25 -3
- package/package.json +1 -1
- package/src/docker.d.ts +5 -6
- package/src/docker.js +17 -17
- package/src/git.d.ts +20 -2
- package/src/git.js +38 -3
- package/src/npm.js +3 -8
- package/src/pkg.d.ts +1 -2
- package/src/version.d.ts +49 -5
- package/src/version.js +137 -26
- package/src/yarn.js +3 -8
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ A suite of functions to help with writing build scripts using TypeScript (or jav
|
|
|
3
3
|
|
|
4
4
|
This project builds itself, so take a look in the `targets/` folder for an example. Run `./bs` to build.
|
|
5
5
|
|
|
6
|
-
[](https://github.com/murrayju/build-strap/actions?query=workflow%3A%22CI+Build%22)
|
|
7
7
|
[](https://github.com/murrayju/build-strap/releases/latest)
|
|
8
8
|
[](https://npmjs.org/package/build-strap)
|
|
9
9
|
[](https://david-dm.org/murrayju/build-strap)
|
|
@@ -36,8 +36,7 @@ By default, the build tools read project-specific configuration from your `packa
|
|
|
36
36
|
"yarnVersion": "1.22.4",
|
|
37
37
|
"repoType": "git",
|
|
38
38
|
"copyright": "your company",
|
|
39
|
-
"
|
|
40
|
-
"devBranch": "dev",
|
|
39
|
+
"mainBranch": "main",
|
|
41
40
|
"npm": {
|
|
42
41
|
"publish": true,
|
|
43
42
|
},
|
|
@@ -50,11 +49,34 @@ By default, the build tools read project-specific configuration from your `packa
|
|
|
50
49
|
}
|
|
51
50
|
```
|
|
52
51
|
|
|
52
|
+
### mainBranch
|
|
53
|
+
The name of the single long-lived branch of the repository (defaults to `main`). All work happens on short-lived branches that are merged into it via pull request.
|
|
54
|
+
|
|
53
55
|
### docker
|
|
54
56
|
* **registry**: base URL for the docker registry, as needed by `docker push`.
|
|
55
57
|
* **repository**: the name of the (organization's) docker repository, in which to put this project
|
|
56
58
|
* **name**: the name of the project, used as the docker image name.
|
|
57
59
|
|
|
60
|
+
## Branching and Release Workflow
|
|
61
|
+
This project (and projects built with it) use a single long-lived branch (`main`) with short-lived work branches merged into it via pull request. There is no separate development or release branch.
|
|
62
|
+
|
|
63
|
+
Releases are triggered by pushing a [semver](https://semver.org/) tag (with an optional leading `v`) to a commit on `main`:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
git tag v1.2.3 && git push origin v1.2.3
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The version being built is determined as follows:
|
|
70
|
+
|
|
71
|
+
| Build | npm version | npm dist-tag | docker tags |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| Tag `v1.2.3` on `main` | `1.2.3` | `latest` | `latest`, `1`, `1.2`, `1.2.3` |
|
|
74
|
+
| Tag `v1.2.3-rc.1` on `main` | `1.2.3-rc.1` | `rc` | `1.2.3-rc.1`, `latest-rc` |
|
|
75
|
+
| Untagged commit on `main` | `1.2.3-main.42` | `next` | `latest-main` |
|
|
76
|
+
| Work branch / pull request | `1.2.3-my-branch.42` | `branch` | (none) |
|
|
77
|
+
|
|
78
|
+
For untagged builds, the base version comes from the `version` field in `package.json` and the build number comes from `--buildNum=` (or the `BUILD_NUMBER` environment variable). For release builds, the version comes from the git tag, and the build refuses to publish if the tagged commit is not contained in `main`.
|
|
79
|
+
|
|
58
80
|
## Write Your Build Script
|
|
59
81
|
See the reference implementations (below) for a complete example of a robust build environment. The library exports many useful functions. Here are some of the most important (see source for more):
|
|
60
82
|
|
package/package.json
CHANGED
package/src/docker.d.ts
CHANGED
|
@@ -9,12 +9,11 @@ export declare function dockerBuild(tags?: string[], buildArgs?: string[], targe
|
|
|
9
9
|
export declare function dockerTag(imageId: string, tags?: string[], repo?: string): Promise<void>;
|
|
10
10
|
/**
|
|
11
11
|
* Apply the standard convention for tagging the docker image for the project,
|
|
12
|
-
* based on the
|
|
13
|
-
* For releases, applies `latest`, `M
|
|
14
|
-
* For
|
|
15
|
-
* For
|
|
16
|
-
* For
|
|
17
|
-
* All other branches, no tag is applies
|
|
12
|
+
* based on the semver git tag (or branch) being built.
|
|
13
|
+
* For releases (a semver tag), applies `latest`, `M`, `M.m`, `M.m.p`
|
|
14
|
+
* For pre-releases (e.g. tag `v1.2.3-rc.1`), applies `1.2.3-rc.1` and `latest-rc`
|
|
15
|
+
* For untagged builds of the main branch, applies `latest-main`
|
|
16
|
+
* For all other builds, no tag is applied
|
|
18
17
|
* @param {string} imageId id of the docker image to which to apply the tags
|
|
19
18
|
* @param {string} repo optional override of the docker repo url to use (derived from package.json by default)
|
|
20
19
|
*/
|
package/src/docker.js
CHANGED
|
@@ -12,7 +12,7 @@ import { downloadFile } from './fetch.js';
|
|
|
12
12
|
import { mountDmg, unmountDmg } from './macos.js';
|
|
13
13
|
import { getCfg, getPkgName, getPkgScope } from './pkg.js';
|
|
14
14
|
import { buildLog } from './run.js';
|
|
15
|
-
import {
|
|
15
|
+
import { getMainBranch, getVersion } from './version.js';
|
|
16
16
|
export function getDockerConfig() {
|
|
17
17
|
return getCfg().docker || {};
|
|
18
18
|
}
|
|
@@ -37,29 +37,29 @@ export async function dockerTag(imageId, tags = ['latest'], repo = getDockerRepo
|
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
39
|
* Apply the standard convention for tagging the docker image for the project,
|
|
40
|
-
* based on the
|
|
41
|
-
* For releases, applies `latest`, `M
|
|
42
|
-
* For
|
|
43
|
-
* For
|
|
44
|
-
* For
|
|
45
|
-
* All other branches, no tag is applies
|
|
40
|
+
* based on the semver git tag (or branch) being built.
|
|
41
|
+
* For releases (a semver tag), applies `latest`, `M`, `M.m`, `M.m.p`
|
|
42
|
+
* For pre-releases (e.g. tag `v1.2.3-rc.1`), applies `1.2.3-rc.1` and `latest-rc`
|
|
43
|
+
* For untagged builds of the main branch, applies `latest-main`
|
|
44
|
+
* For all other builds, no tag is applied
|
|
46
45
|
* @param {string} imageId id of the docker image to which to apply the tags
|
|
47
46
|
* @param {string} repo optional override of the docker repo url to use (derived from package.json by default)
|
|
48
47
|
*/
|
|
49
48
|
export async function dockerTagVersion(imageId, repo = getDockerRepo()) {
|
|
50
|
-
const { branch, isRelease, major, minor, patch } = await getVersion();
|
|
49
|
+
const { branch, isRelease, major, minor, npm, patch, prerelease } = await getVersion();
|
|
51
50
|
// determine what tags to apply
|
|
52
51
|
if (isRelease) {
|
|
53
|
-
await dockerTag(imageId,
|
|
52
|
+
await dockerTag(imageId, prerelease
|
|
53
|
+
? [npm, `latest-${prerelease.split('.')[0]}`]
|
|
54
|
+
: [
|
|
55
|
+
'latest',
|
|
56
|
+
`${major}`,
|
|
57
|
+
`${major}.${minor}`,
|
|
58
|
+
`${major}.${minor}.${patch}`,
|
|
59
|
+
], repo);
|
|
54
60
|
}
|
|
55
|
-
else if (branch === (
|
|
56
|
-
await dockerTag(imageId, [
|
|
57
|
-
}
|
|
58
|
-
else if (branch.match(/^(release|patch)-/)) {
|
|
59
|
-
await dockerTag(imageId, ['latest-rc'], repo);
|
|
60
|
-
}
|
|
61
|
-
else if (branch.match(/^feature-/)) {
|
|
62
|
-
await dockerTag(imageId, ['latest-feature'], repo);
|
|
61
|
+
else if (branch === getMainBranch()) {
|
|
62
|
+
await dockerTag(imageId, [`latest-${branch}`], repo);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
export async function dockerImages(repo = getDockerRepo(), filter = null) {
|
package/src/git.d.ts
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import { SpawnOptions } from './cp.js';
|
|
2
2
|
export declare const git: (args: string[], opts?: SpawnOptions) => Promise<import("./cp.js").SpawnResult>;
|
|
3
3
|
export declare const gitOutput: (args: string[], opts?: SpawnOptions) => Promise<string>;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Convert a git ref name into a string that is safe to embed in a version.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sanitizeGitRefName(refName: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Name of the branch that is currently checked out, or `null` when the working
|
|
10
|
+
* copy is in a detached HEAD state (as is typical for tag and pull request
|
|
11
|
+
* builds on CI) and no branch name can be determined from the environment.
|
|
12
|
+
*/
|
|
13
|
+
export declare function gitBranch(): Promise<null | string>;
|
|
5
14
|
export declare function gitRevId(): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* All tags that point directly at the given commit (HEAD by default).
|
|
17
|
+
*/
|
|
18
|
+
export declare function gitTagsAtCommit(commitish?: string): Promise<string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* True when `ancestorCommitish` is reachable from `descendantCommitish`.
|
|
21
|
+
* Useful for verifying that a release tag lives on the main branch.
|
|
22
|
+
*/
|
|
23
|
+
export declare function gitIsAncestor(ancestorCommitish: string, descendantCommitish: string): Promise<boolean>;
|
|
6
24
|
type GitInfo = {
|
|
7
|
-
branch: string;
|
|
25
|
+
branch: null | string;
|
|
8
26
|
revision: string;
|
|
9
27
|
};
|
|
10
28
|
export declare function gitInfo(noCache?: boolean): Promise<GitInfo>;
|
package/src/git.js
CHANGED
|
@@ -3,16 +3,51 @@ import { userQuestion } from './prompt.js';
|
|
|
3
3
|
import { buildLog } from './run.js';
|
|
4
4
|
export const git = async (args, opts) => spawn('git', args, opts);
|
|
5
5
|
export const gitOutput = async (args, opts) => (await git(args, { captureOutput: true, ...opts })).stdout.trim();
|
|
6
|
+
/**
|
|
7
|
+
* Convert a git ref name into a string that is safe to embed in a version.
|
|
8
|
+
*/
|
|
9
|
+
export function sanitizeGitRefName(refName) {
|
|
10
|
+
return refName.replace(/[_+/]/g, '-').trim();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Name of the branch that is currently checked out, or `null` when the working
|
|
14
|
+
* copy is in a detached HEAD state (as is typical for tag and pull request
|
|
15
|
+
* builds on CI) and no branch name can be determined from the environment.
|
|
16
|
+
*/
|
|
6
17
|
export async function gitBranch() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
const checkedOutBranch = await gitOutput(['symbolic-ref', '--short', 'HEAD'], {
|
|
19
|
+
rejectOnErrorCode: false,
|
|
20
|
+
});
|
|
21
|
+
if (checkedOutBranch) {
|
|
22
|
+
return sanitizeGitRefName(checkedOutBranch);
|
|
23
|
+
}
|
|
24
|
+
// Detached HEAD: fall back to the branch reported by the CI environment.
|
|
25
|
+
// For pull requests, GITHUB_HEAD_REF holds the source branch of the PR.
|
|
26
|
+
const { GITHUB_HEAD_REF, GITHUB_REF } = process.env;
|
|
27
|
+
const envBranch = GITHUB_HEAD_REF || GITHUB_REF?.match(/^refs\/heads\/(.+)$/)?.[1];
|
|
28
|
+
return envBranch ? sanitizeGitRefName(envBranch) : null;
|
|
10
29
|
}
|
|
11
30
|
export async function gitRevId() {
|
|
12
31
|
return (await gitOutput(['rev-parse', '--short=12', 'HEAD']))
|
|
13
32
|
.replace(/[+]/g, '')
|
|
14
33
|
.trim();
|
|
15
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* All tags that point directly at the given commit (HEAD by default).
|
|
37
|
+
*/
|
|
38
|
+
export async function gitTagsAtCommit(commitish = 'HEAD') {
|
|
39
|
+
const output = await gitOutput(['tag', '--points-at', commitish], {
|
|
40
|
+
rejectOnErrorCode: false,
|
|
41
|
+
});
|
|
42
|
+
return output.split('\n').filter(Boolean);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* True when `ancestorCommitish` is reachable from `descendantCommitish`.
|
|
46
|
+
* Useful for verifying that a release tag lives on the main branch.
|
|
47
|
+
*/
|
|
48
|
+
export async function gitIsAncestor(ancestorCommitish, descendantCommitish) {
|
|
49
|
+
return ((await git(['merge-base', '--is-ancestor', ancestorCommitish, descendantCommitish], { rejectOnErrorCode: false })).code === 0);
|
|
50
|
+
}
|
|
16
51
|
let info = null;
|
|
17
52
|
export async function gitInfo(noCache = false) {
|
|
18
53
|
if (!info || noCache) {
|
package/src/npm.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { spawn } from './cp.js';
|
|
5
5
|
import { getCfg, getPkgName } from './pkg.js';
|
|
6
6
|
import { buildLog } from './run.js';
|
|
7
|
-
import {
|
|
7
|
+
import { getDefaultNpmDistTag, getVersion } from './version.js';
|
|
8
8
|
export function getNpmConfig() {
|
|
9
9
|
return getCfg().npm || {};
|
|
10
10
|
}
|
|
@@ -86,7 +86,7 @@ export async function npmPublish({ npmAuthToken, npmConfig, npmCreds, npmPath, p
|
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
const existing = await npmGetVersions(name, npmPath);
|
|
89
|
-
const {
|
|
89
|
+
const { npm: npmVersion } = await getVersion();
|
|
90
90
|
if (existing.includes(npmVersion)) {
|
|
91
91
|
if (skipExisting) {
|
|
92
92
|
buildLog('npm package with this same version already exists. Skipping publish...');
|
|
@@ -102,12 +102,7 @@ export async function npmPublish({ npmAuthToken, npmConfig, npmCreds, npmPath, p
|
|
|
102
102
|
'publish',
|
|
103
103
|
resolvedPath,
|
|
104
104
|
'--tag',
|
|
105
|
-
tag ||
|
|
106
|
-
(isRelease
|
|
107
|
-
? 'latest'
|
|
108
|
-
: branch === (await getDevBranch())
|
|
109
|
-
? 'next'
|
|
110
|
-
: 'branch'),
|
|
105
|
+
tag || (await getDefaultNpmDistTag()),
|
|
111
106
|
...(access ? ['--access', access] : []),
|
|
112
107
|
...(dryRun ? ['--dry-run'] : []),
|
|
113
108
|
...(provenance ? ['--provenance'] : []),
|
package/src/pkg.d.ts
CHANGED
|
@@ -4,10 +4,9 @@ import { NpmConfig } from './npm.js';
|
|
|
4
4
|
export interface BuildStrapConfig {
|
|
5
5
|
cert?: CertConfig;
|
|
6
6
|
copyright?: string;
|
|
7
|
-
devBranch?: string;
|
|
8
7
|
docker?: DockerConfig;
|
|
8
|
+
mainBranch?: string;
|
|
9
9
|
npm?: NpmConfig;
|
|
10
|
-
releaseBranch?: string;
|
|
11
10
|
repoType?: 'git' | 'hg';
|
|
12
11
|
}
|
|
13
12
|
export interface Person {
|
package/src/version.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export declare function getBuild(): string;
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The one long-lived branch of the repository. All work happens on short-lived
|
|
4
|
+
* branches that are merged into it via pull request, and releases are cut by
|
|
5
|
+
* pushing a semver tag to a commit on it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getMainBranch(): string;
|
|
3
8
|
declare enum RepoType {
|
|
4
9
|
git = "git",
|
|
5
10
|
hg = "hg",
|
|
@@ -7,15 +12,44 @@ declare enum RepoType {
|
|
|
7
12
|
}
|
|
8
13
|
export declare function getRepoType(): Promise<RepoType>;
|
|
9
14
|
type RepoInfo = {
|
|
10
|
-
branch: string;
|
|
15
|
+
branch: null | string;
|
|
11
16
|
revision: string;
|
|
12
17
|
};
|
|
13
18
|
export declare function getRepoInfo(): Promise<RepoInfo>;
|
|
14
|
-
export
|
|
15
|
-
|
|
19
|
+
export interface SemverTag {
|
|
20
|
+
major: string;
|
|
21
|
+
minor: string;
|
|
22
|
+
patch: string;
|
|
23
|
+
prerelease: null | string;
|
|
24
|
+
tag: string;
|
|
25
|
+
version: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a git tag as a semver release tag. Returns null if the tag is not a
|
|
29
|
+
* semver version tag (an optional leading `v` is allowed).
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseSemverTag(tag: string): null | SemverTag;
|
|
32
|
+
/**
|
|
33
|
+
* The semver tag pointing at the commit being built, if any. This is what makes
|
|
34
|
+
* a build a release build: releases are triggered by pushing a semver tag.
|
|
35
|
+
* When multiple semver tags point at the same commit, the highest one wins.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getReleaseTag(): Promise<null | SemverTag>;
|
|
38
|
+
/**
|
|
39
|
+
* Verify that the semver release tag agrees with the version in package.json, so
|
|
40
|
+
* that the published artifact always matches its source. Pre-release identifiers
|
|
41
|
+
* on the tag (e.g. `v1.2.3-rc.1` for package version `1.2.3`) are allowed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function assertReleaseTagMatchesPackageVersion(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Verify that the commit being released is contained in the main branch, so that
|
|
46
|
+
* a release can never be cut from an unmerged work branch.
|
|
47
|
+
*/
|
|
48
|
+
export declare function assertReleaseCommitIsOnMainBranch(mainBranch?: string): Promise<void>;
|
|
49
|
+
export declare function getIsRelease(releaseOverride?: null | boolean): Promise<boolean>;
|
|
16
50
|
export declare function useVersionCache(useCache?: boolean): void;
|
|
17
51
|
export interface Version {
|
|
18
|
-
branch: string;
|
|
52
|
+
branch: null | string;
|
|
19
53
|
build: string;
|
|
20
54
|
info: string;
|
|
21
55
|
isRelease: boolean;
|
|
@@ -24,10 +58,20 @@ export interface Version {
|
|
|
24
58
|
name: string;
|
|
25
59
|
npm: string;
|
|
26
60
|
patch: string;
|
|
61
|
+
prerelease: null | string;
|
|
62
|
+
releaseTag: null | string;
|
|
27
63
|
revision: string;
|
|
28
64
|
short: string;
|
|
29
65
|
}
|
|
30
66
|
export declare function getVersion(logIt?: boolean, release?: null | boolean): Promise<Version>;
|
|
67
|
+
/**
|
|
68
|
+
* The npm dist-tag to publish under, following the branch/tag conventions:
|
|
69
|
+
* - release tag without pre-release identifiers => `latest`
|
|
70
|
+
* - release tag with pre-release identifiers => the first identifier (`rc`, `beta`, ...)
|
|
71
|
+
* - untagged build of the main branch => `next`
|
|
72
|
+
* - anything else => `branch`
|
|
73
|
+
*/
|
|
74
|
+
export declare function getDefaultNpmDistTag(): Promise<string>;
|
|
31
75
|
export declare function getBanner(): Promise<string>;
|
|
32
76
|
export declare function getVersionCode(): Promise<string>;
|
|
33
77
|
export declare function getUniqueBuildTag(): Promise<string>;
|
package/src/version.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import { gitInfo } from './git.js';
|
|
2
|
+
import { gitInfo, gitIsAncestor, gitTagsAtCommit } from './git.js';
|
|
3
3
|
import { getCfg, getPkg } from './pkg.js';
|
|
4
4
|
import { buildLog } from './run.js';
|
|
5
5
|
export function getBuild() {
|
|
6
6
|
const arg = process.argv.find((el) => /^--buildNum=\d+$/.test(el));
|
|
7
7
|
return (arg && arg.substring(11)) || process.env.BUILD_NUMBER || '0';
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The one long-lived branch of the repository. All work happens on short-lived
|
|
11
|
+
* branches that are merged into it via pull request, and releases are cut by
|
|
12
|
+
* pushing a semver tag to a commit on it.
|
|
13
|
+
*/
|
|
14
|
+
export function getMainBranch() {
|
|
15
|
+
const { mainBranch } = getCfg();
|
|
16
|
+
return mainBranch || 'main';
|
|
12
17
|
}
|
|
13
18
|
var RepoType;
|
|
14
19
|
(function (RepoType) {
|
|
@@ -31,41 +36,131 @@ export async function getRepoInfo() {
|
|
|
31
36
|
throw new Error(`Unsupported repo type: ${repoType}`);
|
|
32
37
|
}
|
|
33
38
|
const { branch, revision } = await gitInfo();
|
|
34
|
-
if (!
|
|
35
|
-
throw new Error('Failed to parse
|
|
39
|
+
if (!revision) {
|
|
40
|
+
throw new Error('Failed to parse revision from source repo.');
|
|
36
41
|
}
|
|
37
42
|
return { branch, revision };
|
|
38
43
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
const semverTagRegex = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
|
|
45
|
+
/**
|
|
46
|
+
* Parse a git tag as a semver release tag. Returns null if the tag is not a
|
|
47
|
+
* semver version tag (an optional leading `v` is allowed).
|
|
48
|
+
*/
|
|
49
|
+
export function parseSemverTag(tag) {
|
|
50
|
+
const [, major, minor, patch, prerelease] = tag.match(semverTagRegex) || [];
|
|
51
|
+
if (!major || !minor || !patch) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
major,
|
|
56
|
+
minor,
|
|
57
|
+
patch,
|
|
58
|
+
prerelease: prerelease || null,
|
|
59
|
+
tag,
|
|
60
|
+
version: `${major}.${minor}.${patch}${prerelease ? `-${prerelease}` : ''}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The semver tag pointing at the commit being built, if any. This is what makes
|
|
65
|
+
* a build a release build: releases are triggered by pushing a semver tag.
|
|
66
|
+
* When multiple semver tags point at the same commit, the highest one wins.
|
|
67
|
+
*/
|
|
68
|
+
export async function getReleaseTag() {
|
|
69
|
+
const overrideTag = process.argv
|
|
70
|
+
.find((el) => el.startsWith('--releaseTag='))
|
|
71
|
+
?.substring('--releaseTag='.length) ||
|
|
72
|
+
process.env.RELEASE_TAG ||
|
|
73
|
+
// GitHub Actions sets this for tag builds
|
|
74
|
+
process.env.GITHUB_REF?.match(/^refs\/tags\/(.+)$/)?.[1];
|
|
75
|
+
if (overrideTag) {
|
|
76
|
+
const parsed = parseSemverTag(overrideTag);
|
|
77
|
+
if (!parsed) {
|
|
78
|
+
throw new Error(`\`${overrideTag}\` is not a valid semver release tag.`);
|
|
79
|
+
}
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
if ((await getRepoType()) !== RepoType.git) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const semverTags = (await gitTagsAtCommit())
|
|
86
|
+
.map(parseSemverTag)
|
|
87
|
+
.filter((t) => t != null);
|
|
88
|
+
return (semverTags.sort((a, b) => a.version.localeCompare(b.version, undefined, { numeric: true }))[semverTags.length - 1] || null);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Verify that the semver release tag agrees with the version in package.json, so
|
|
92
|
+
* that the published artifact always matches its source. Pre-release identifiers
|
|
93
|
+
* on the tag (e.g. `v1.2.3-rc.1` for package version `1.2.3`) are allowed.
|
|
94
|
+
*/
|
|
95
|
+
export async function assertReleaseTagMatchesPackageVersion() {
|
|
96
|
+
const releaseTag = await getReleaseTag();
|
|
97
|
+
if (!releaseTag) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const { version: pkgVersion } = getPkg();
|
|
101
|
+
const pkgSemver = parseSemverTag(pkgVersion);
|
|
102
|
+
const tagBaseVersion = `${releaseTag.major}.${releaseTag.minor}.${releaseTag.patch}`;
|
|
103
|
+
const pkgBaseVersion = pkgSemver && `${pkgSemver.major}.${pkgSemver.minor}.${pkgSemver.patch}`;
|
|
104
|
+
if (tagBaseVersion !== pkgBaseVersion) {
|
|
105
|
+
throw new Error(`Refusing to release: tag \`${releaseTag.tag}\` does not match the version in package.json (\`${pkgVersion}\`).`);
|
|
106
|
+
}
|
|
42
107
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Verify that the commit being released is contained in the main branch, so that
|
|
110
|
+
* a release can never be cut from an unmerged work branch.
|
|
111
|
+
*/
|
|
112
|
+
export async function assertReleaseCommitIsOnMainBranch(mainBranch = getMainBranch()) {
|
|
113
|
+
const { revision } = await getRepoInfo();
|
|
114
|
+
const candidateRefs = [mainBranch, `origin/${mainBranch}`];
|
|
115
|
+
for (const ref of candidateRefs) {
|
|
116
|
+
// eslint-disable-next-line no-await-in-loop
|
|
117
|
+
if (await gitIsAncestor(revision, ref)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`Refusing to release ${revision}: it is not contained in the \`${mainBranch}\` branch.`);
|
|
122
|
+
}
|
|
123
|
+
export async function getIsRelease(releaseOverride = null) {
|
|
124
|
+
if (releaseOverride != null) {
|
|
125
|
+
return releaseOverride;
|
|
126
|
+
}
|
|
127
|
+
if (process.argv.includes('--force-release-version')) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return (await getReleaseTag()) != null;
|
|
50
131
|
}
|
|
51
132
|
let cacheVersion = true;
|
|
52
133
|
export function useVersionCache(useCache = true) {
|
|
53
134
|
cacheVersion = useCache;
|
|
54
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Label used to identify a non-release build in its version string. Falls back
|
|
138
|
+
* to the revision when no branch name is available.
|
|
139
|
+
*/
|
|
140
|
+
function getBuildSourceLabel({ branch, revision }) {
|
|
141
|
+
return branch || revision;
|
|
142
|
+
}
|
|
55
143
|
let version = null;
|
|
56
144
|
export async function getVersion(logIt = true, release = null) {
|
|
57
145
|
if (!cacheVersion || version == null) {
|
|
58
|
-
const { name, version:
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
146
|
+
const { name, version: pkgVersion } = getPkg();
|
|
147
|
+
const repoInfo = await getRepoInfo();
|
|
148
|
+
const { branch, revision } = repoInfo;
|
|
149
|
+
const releaseTag = await getReleaseTag();
|
|
150
|
+
const isRelease = await getIsRelease(release);
|
|
151
|
+
// A release build takes its version from the git tag that triggered it;
|
|
152
|
+
// everything else is a pre-release of the version in package.json.
|
|
153
|
+
const semver = (isRelease && releaseTag) || parseSemverTag(pkgVersion.split('-')[0]);
|
|
154
|
+
if (!semver) {
|
|
155
|
+
throw new Error(`Invalid version format in package.json: \`${pkgVersion}\``);
|
|
63
156
|
}
|
|
157
|
+
const { major, minor, patch, prerelease } = semver;
|
|
64
158
|
const build = getBuild();
|
|
65
|
-
const isRelease = await getIsRelease(branch, release);
|
|
66
159
|
const short = `${major}.${minor}.${patch}`;
|
|
67
|
-
const npm =
|
|
68
|
-
|
|
160
|
+
const npm = isRelease
|
|
161
|
+
? semver.version
|
|
162
|
+
: `${short}-${getBuildSourceLabel(repoInfo)}.${build}`;
|
|
163
|
+
const info = `${npm}+${build}.${revision}`;
|
|
69
164
|
version = {
|
|
70
165
|
branch,
|
|
71
166
|
build,
|
|
@@ -76,6 +171,8 @@ export async function getVersion(logIt = true, release = null) {
|
|
|
76
171
|
name,
|
|
77
172
|
npm,
|
|
78
173
|
patch,
|
|
174
|
+
prerelease,
|
|
175
|
+
releaseTag: releaseTag?.tag || null,
|
|
79
176
|
revision,
|
|
80
177
|
short,
|
|
81
178
|
};
|
|
@@ -85,6 +182,20 @@ export async function getVersion(logIt = true, release = null) {
|
|
|
85
182
|
}
|
|
86
183
|
return version;
|
|
87
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* The npm dist-tag to publish under, following the branch/tag conventions:
|
|
187
|
+
* - release tag without pre-release identifiers => `latest`
|
|
188
|
+
* - release tag with pre-release identifiers => the first identifier (`rc`, `beta`, ...)
|
|
189
|
+
* - untagged build of the main branch => `next`
|
|
190
|
+
* - anything else => `branch`
|
|
191
|
+
*/
|
|
192
|
+
export async function getDefaultNpmDistTag() {
|
|
193
|
+
const { branch, isRelease, prerelease } = await getVersion();
|
|
194
|
+
if (isRelease) {
|
|
195
|
+
return prerelease ? prerelease.split('.')[0] : 'latest';
|
|
196
|
+
}
|
|
197
|
+
return branch === getMainBranch() ? 'next' : 'branch';
|
|
198
|
+
}
|
|
88
199
|
export async function getBanner() {
|
|
89
200
|
const { copyright = '' } = getCfg();
|
|
90
201
|
const { info, name } = await getVersion();
|
|
@@ -96,6 +207,6 @@ export async function getVersionCode() {
|
|
|
96
207
|
export const version = ${JSON.stringify(await getVersion(), null, 2)}`;
|
|
97
208
|
}
|
|
98
209
|
export async function getUniqueBuildTag() {
|
|
99
|
-
const {
|
|
100
|
-
return `${
|
|
210
|
+
const { build, revision, ...repoInfo } = await getVersion();
|
|
211
|
+
return `${getBuildSourceLabel({ ...repoInfo, revision })}-${revision}-${build}`;
|
|
101
212
|
}
|
package/src/yarn.js
CHANGED
|
@@ -6,7 +6,7 @@ import { envNpmCreds, getNpmConfig, npmGetVersions, npmWriteRc, } from './npm.js
|
|
|
6
6
|
import { getPkg, getPkgName, getPkgSafeName } from './pkg.js';
|
|
7
7
|
import { buildLog } from './run.js';
|
|
8
8
|
import { generateFileHash } from './tgz.js';
|
|
9
|
-
import {
|
|
9
|
+
import { getDefaultNpmDistTag, getVersion } from './version.js';
|
|
10
10
|
export async function yarn({ args = [], spawnOptions, } = {}) {
|
|
11
11
|
const localYarn = path.join(path.dirname(process.execPath), '/node_modules/yarn/bin/yarn.js');
|
|
12
12
|
const spawnOpts = {
|
|
@@ -102,7 +102,7 @@ export async function yarnPublish({ npmAuthToken, npmConfig, npmCreds, npmPath,
|
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
const existing = await npmGetVersions(getPkgName(true), npmPath);
|
|
105
|
-
const {
|
|
105
|
+
const { npm: npmVersion } = await getVersion();
|
|
106
106
|
if (existing.includes(npmVersion)) {
|
|
107
107
|
if (skipExisting) {
|
|
108
108
|
buildLog('npm package with this same version already exists. Skipping publish...');
|
|
@@ -117,12 +117,7 @@ export async function yarnPublish({ npmAuthToken, npmConfig, npmCreds, npmPath,
|
|
|
117
117
|
'--new-version',
|
|
118
118
|
npmVersion,
|
|
119
119
|
'--tag',
|
|
120
|
-
tag ||
|
|
121
|
-
(isRelease
|
|
122
|
-
? 'latest'
|
|
123
|
-
: branch === (await getDevBranch())
|
|
124
|
-
? 'next'
|
|
125
|
-
: 'branch'),
|
|
120
|
+
tag || (await getDefaultNpmDistTag()),
|
|
126
121
|
...(access ? ['--access', access] : []),
|
|
127
122
|
],
|
|
128
123
|
spawnOptions: {
|