@oneblink/release-cli 2.1.1 → 3.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 +1 -1
- package/dist/bin.js +71 -52
- package/dist/executeCommand.js +15 -0
- package/dist/getPreRelease.js +10 -0
- package/dist/parseChangelogWithLoading.js +15 -0
- package/dist/promptForNextVersion.js +36 -0
- package/dist/promptForReleaseName.js +12 -0
- package/dist/repositories-plugins/CdnHostingPlugin.js +5 -0
- package/dist/repositories-plugins/NodeJsPlugin.js +12 -0
- package/dist/repositories-plugins/NpmPlugin.js +42 -0
- package/dist/repositories-plugins/NugetPlugin.js +28 -0
- package/dist/repositories-plugins/RepositoryPlugin.js +1 -0
- package/dist/repositories-plugins/plugins-factory.js +54 -0
- package/dist/startProductRelease.js +220 -0
- package/dist/{startReleaseProcess.js → startRepositoryRelease.js} +38 -79
- package/dist/wrapWithLoading.js +15 -0
- package/package.json +11 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OneBlink Release CLI [](https://www.npmjs.com/package/@oneblink/release-cli) [](https://github.com/oneblink/release-cli/actions)
|
|
2
2
|
|
|
3
|
-
Used internally by OneBlink to release
|
|
3
|
+
Used internally by OneBlink to release repositories quickly and consistently
|
|
4
4
|
|
|
5
5
|
## CLI Requirements
|
|
6
6
|
|
package/dist/bin.js
CHANGED
|
@@ -2,11 +2,31 @@
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import updateNotifier from 'update-notifier';
|
|
4
4
|
import meow from 'meow';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import startRepositoryRelease from './startRepositoryRelease.js';
|
|
7
7
|
import semver from 'semver';
|
|
8
|
+
import promptForNextVersion from './promptForNextVersion.js';
|
|
9
|
+
import getPreRelease from './getPreRelease.js';
|
|
10
|
+
import startProductRelease from './startProductRelease.js';
|
|
11
|
+
import promptForReleaseName from './promptForReleaseName.js';
|
|
12
|
+
import getRepositoryPlugin from './repositories-plugins/plugins-factory.js';
|
|
8
13
|
const cli = meow(`
|
|
9
|
-
oneblink-release
|
|
14
|
+
${chalk.bold.blue('oneblink-release product [--name]')}
|
|
15
|
+
|
|
16
|
+
${chalk.grey(`Release each repository in the Product. Each repository will offer prompts for
|
|
17
|
+
the information required to perform the release.`)}
|
|
18
|
+
|
|
19
|
+
--name ......... Skip the question to enter a name for the release by passing
|
|
20
|
+
a release name as a flag.
|
|
21
|
+
|
|
22
|
+
${chalk.bold('Examples')}
|
|
23
|
+
|
|
24
|
+
oneblink-release product
|
|
25
|
+
oneblink-release product --name="Inappropriate Release Name"
|
|
26
|
+
|
|
27
|
+
${chalk.bold.blue('oneblink-release repository [next-version] [--no-git] [--name] [--no-name] [--cwd path]')}
|
|
28
|
+
|
|
29
|
+
${chalk.grey('Release a single repository.')}
|
|
10
30
|
|
|
11
31
|
next-version ..... The next version, will prompt for this if not supplied,
|
|
12
32
|
must be a valid semver number.
|
|
@@ -19,18 +39,18 @@ oneblink-release [next-version] [--no-git] [--name] [--no-name] [--cwd path]
|
|
|
19
39
|
--no-name ...... Skip the question to enter a name for the release. Use
|
|
20
40
|
option when running a release for an open source repository.
|
|
21
41
|
|
|
22
|
-
--cwd .......... Directory of the
|
|
42
|
+
--cwd .......... Directory of the repository to release relative to the
|
|
23
43
|
current working directory, defaults to the current
|
|
24
44
|
working directory.
|
|
25
45
|
|
|
26
|
-
Examples
|
|
46
|
+
${chalk.bold('Examples')}
|
|
27
47
|
|
|
28
|
-
oneblink-release
|
|
29
|
-
oneblink-release --no-name
|
|
30
|
-
oneblink-release --name="Inappropriate Release Name"
|
|
31
|
-
oneblink-release 1.1.1
|
|
32
|
-
oneblink-release 1.1.1 --cwd ../path/to/code
|
|
33
|
-
oneblink-release 1.1.1-uat.1 --no-git
|
|
48
|
+
oneblink-release repository
|
|
49
|
+
oneblink-release repository --no-name
|
|
50
|
+
oneblink-release repository --name="Inappropriate Release Name"
|
|
51
|
+
oneblink-release repository 1.1.1
|
|
52
|
+
oneblink-release repository 1.1.1 --cwd ../path/to/code
|
|
53
|
+
oneblink-release repository 1.1.1-uat.1 --no-git
|
|
34
54
|
`, {
|
|
35
55
|
importMeta: import.meta,
|
|
36
56
|
flags: {
|
|
@@ -67,14 +87,7 @@ async function getReleaseName({ name, preRelease, }) {
|
|
|
67
87
|
if (typeof name === 'boolean' && !name) {
|
|
68
88
|
return undefined;
|
|
69
89
|
}
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
type: 'input',
|
|
73
|
-
message: 'Release name? i.e. JIRA release',
|
|
74
|
-
name: 'releaseName',
|
|
75
|
-
},
|
|
76
|
-
]);
|
|
77
|
-
return releaseName;
|
|
90
|
+
return await promptForReleaseName();
|
|
78
91
|
}
|
|
79
92
|
updateNotifier({
|
|
80
93
|
// @ts-expect-error difference in types between packages
|
|
@@ -85,38 +98,44 @@ run().catch((error) => {
|
|
|
85
98
|
console.error(error);
|
|
86
99
|
});
|
|
87
100
|
async function run() {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
const command = cli.input[0];
|
|
102
|
+
switch (command) {
|
|
103
|
+
case 'product': {
|
|
104
|
+
const releaseName = cli.flags.name || (await promptForReleaseName());
|
|
105
|
+
await startProductRelease({ releaseName });
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case 'repository': {
|
|
109
|
+
let input = cli.input[1];
|
|
110
|
+
const repositoryPlugin = await getRepositoryPlugin({
|
|
111
|
+
cwd: path.resolve(process.cwd(), cli.flags.cwd),
|
|
112
|
+
});
|
|
113
|
+
if (!semver.valid(input)) {
|
|
114
|
+
const { nextVersion } = await promptForNextVersion({
|
|
115
|
+
repositoryPlugin,
|
|
116
|
+
noPreRelease: false,
|
|
117
|
+
});
|
|
118
|
+
input = nextVersion;
|
|
119
|
+
}
|
|
120
|
+
const preRelease = getPreRelease(input)?.tag;
|
|
121
|
+
const releaseName = await getReleaseName({
|
|
122
|
+
name: cli.flags.name,
|
|
123
|
+
preRelease,
|
|
124
|
+
});
|
|
125
|
+
await startRepositoryRelease({
|
|
126
|
+
nextVersion: input,
|
|
127
|
+
git: cli.flags.git,
|
|
128
|
+
releaseName,
|
|
129
|
+
repositoryPlugin,
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case undefined: {
|
|
134
|
+
cli.showHelp();
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
default: {
|
|
138
|
+
throw new Error(`"${command}" is not a valid command. Please use the "--help" flag to view available commands.`);
|
|
139
|
+
}
|
|
106
140
|
}
|
|
107
|
-
const preReleaseComponents = semver.prerelease(input);
|
|
108
|
-
const preRelease = typeof preReleaseComponents?.[0] === 'string'
|
|
109
|
-
? preReleaseComponents[0]
|
|
110
|
-
: undefined;
|
|
111
|
-
const releaseName = await getReleaseName({
|
|
112
|
-
name: cli.flags.name,
|
|
113
|
-
preRelease,
|
|
114
|
-
});
|
|
115
|
-
await startReleaseProcess({
|
|
116
|
-
preRelease,
|
|
117
|
-
nextVersion: input,
|
|
118
|
-
git: cli.flags.git,
|
|
119
|
-
releaseName,
|
|
120
|
-
cwd: path.resolve(process.cwd(), cli.flags.cwd),
|
|
121
|
-
});
|
|
122
141
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import wrapWithLoading from './wrapWithLoading.js';
|
|
3
|
+
export default async function executeCommand(command, args, cwd) {
|
|
4
|
+
const log = `"${command} ${args.join(' ')}"`;
|
|
5
|
+
return await wrapWithLoading({
|
|
6
|
+
startText: `Running ${log}`,
|
|
7
|
+
failText: `Failed to run ${log}`,
|
|
8
|
+
}, async (spinner) => {
|
|
9
|
+
const result = await execa(command, args, {
|
|
10
|
+
cwd,
|
|
11
|
+
});
|
|
12
|
+
spinner.succeed(`Ran ${log}`);
|
|
13
|
+
return result;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import parseChangelog from 'changelog-parser';
|
|
3
|
+
import wrapWithLoading from './wrapWithLoading.js';
|
|
4
|
+
export default async function parseChangelogWithLoading(cwd) {
|
|
5
|
+
const changelogPath = path.join(cwd, 'CHANGELOG.md');
|
|
6
|
+
const parsedChangelog = await wrapWithLoading({
|
|
7
|
+
startText: `Parsing ${changelogPath}`,
|
|
8
|
+
failText: `Failed to parsed ${changelogPath}`,
|
|
9
|
+
}, async (spinner) => {
|
|
10
|
+
const parsedChangelog = await parseChangelog(changelogPath);
|
|
11
|
+
spinner.succeed(`Parsed ${changelogPath}`);
|
|
12
|
+
return parsedChangelog;
|
|
13
|
+
});
|
|
14
|
+
return { parsedChangelog, changelogPath };
|
|
15
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import enquirer from 'enquirer';
|
|
2
|
+
import semver from 'semver';
|
|
3
|
+
import getPreRelease from './getPreRelease.js';
|
|
4
|
+
export default async function promptForNextVersion({ repositoryPlugin, noPreRelease, }) {
|
|
5
|
+
const currentVersion = await repositoryPlugin.getCurrentVersion();
|
|
6
|
+
const currentSemverVersion = semver.parse(currentVersion);
|
|
7
|
+
if (!currentSemverVersion) {
|
|
8
|
+
throw new Error(`Could not determine current version for ${repositoryPlugin.displayType} repository: ${repositoryPlugin.cwd}`);
|
|
9
|
+
}
|
|
10
|
+
const autoIncrementVersion = await repositoryPlugin.autoIncrementVersion?.(currentSemverVersion);
|
|
11
|
+
if (autoIncrementVersion) {
|
|
12
|
+
return {
|
|
13
|
+
nextVersion: autoIncrementVersion,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return await enquirer.prompt({
|
|
17
|
+
type: 'input',
|
|
18
|
+
message: 'Next version? e.g. "1.2.3" or "1.2.3-beta.1"',
|
|
19
|
+
name: 'nextVersion',
|
|
20
|
+
required: true,
|
|
21
|
+
initial: currentVersion,
|
|
22
|
+
validate: (value) => {
|
|
23
|
+
if (value === currentVersion) {
|
|
24
|
+
return `Next version must be different to the current version (${currentVersion})`;
|
|
25
|
+
}
|
|
26
|
+
const nextSemverVersion = semver.valid(value);
|
|
27
|
+
if (!nextSemverVersion) {
|
|
28
|
+
return 'Next version must be valid semver';
|
|
29
|
+
}
|
|
30
|
+
if (noPreRelease && getPreRelease(value)) {
|
|
31
|
+
return 'Next version must not be a prerelease version';
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import enquirer from 'enquirer';
|
|
2
|
+
export default async function promptForReleaseName() {
|
|
3
|
+
const { releaseName } = await enquirer.prompt([
|
|
4
|
+
{
|
|
5
|
+
type: 'input',
|
|
6
|
+
message: 'Release name? i.e. JIRA release',
|
|
7
|
+
name: 'releaseName',
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
]);
|
|
11
|
+
return releaseName;
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import NpmPlugin from './NpmPlugin.js';
|
|
2
|
+
export default class NodeJsPlugin extends NpmPlugin {
|
|
3
|
+
displayType = 'NodeJS';
|
|
4
|
+
isDeploymentRequired = true;
|
|
5
|
+
async autoIncrementVersion(currentSemverVersion) {
|
|
6
|
+
// NodeJS repositories that are not being published to NPM
|
|
7
|
+
// don't need to follow semantic versioning as no user ever
|
|
8
|
+
// gets the option to choose a version. We will simply increment
|
|
9
|
+
// the existing version by a minor version.
|
|
10
|
+
return currentSemverVersion.inc('minor').version;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readPackageUp } from 'read-package-up';
|
|
2
|
+
import { main as packageDiffSummary } from '../package-diff-summary/index.js';
|
|
3
|
+
import executeCommand from '../executeCommand.js';
|
|
4
|
+
export default class NpmPlugin {
|
|
5
|
+
displayType = 'NPM';
|
|
6
|
+
isDeploymentRequired = false;
|
|
7
|
+
cwd;
|
|
8
|
+
constructor({ cwd }) {
|
|
9
|
+
this.cwd = cwd;
|
|
10
|
+
}
|
|
11
|
+
async getCurrentVersion() {
|
|
12
|
+
const result = await readPackageUp({
|
|
13
|
+
cwd: this.cwd,
|
|
14
|
+
});
|
|
15
|
+
return result?.packageJson.version;
|
|
16
|
+
}
|
|
17
|
+
async incrementVersion(nextSemverVersion) {
|
|
18
|
+
await executeCommand('npm', ['version', nextSemverVersion.version, '--no-git-tag-version'], this.cwd);
|
|
19
|
+
}
|
|
20
|
+
async generateDependenciesChangelog({ previousVersion, }) {
|
|
21
|
+
try {
|
|
22
|
+
const entries = await packageDiffSummary({
|
|
23
|
+
cwd: this.cwd,
|
|
24
|
+
previousVersion,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
result: 'ENTRIES',
|
|
28
|
+
entries,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof Error &&
|
|
33
|
+
error.message.includes(`git show ${previousVersion}:package.json`)) {
|
|
34
|
+
return {
|
|
35
|
+
result: 'WARNING',
|
|
36
|
+
message: `Skipping inserting the "Dependencies" heading in CHANGELOG.md as it relies on the last release's git tag having a "v" prefix (i.e. "${previousVersion}")`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import getPreRelease from '../getPreRelease.js';
|
|
4
|
+
export default class NugetPlugin {
|
|
5
|
+
isDeploymentRequired = false;
|
|
6
|
+
displayType = 'Nuget';
|
|
7
|
+
cwd;
|
|
8
|
+
relativeProjectFile;
|
|
9
|
+
constructor({ cwd, relativeProjectFile, }) {
|
|
10
|
+
this.cwd = cwd;
|
|
11
|
+
this.relativeProjectFile = relativeProjectFile;
|
|
12
|
+
}
|
|
13
|
+
async getCurrentVersion() {
|
|
14
|
+
const projectFile = path.join(this.cwd, this.relativeProjectFile);
|
|
15
|
+
const file = await readFile(projectFile, 'utf-8');
|
|
16
|
+
const matches = file.match(/<PackageVersion>(.*)<\/PackageVersion>/);
|
|
17
|
+
return matches?.[1];
|
|
18
|
+
}
|
|
19
|
+
async incrementVersion(nextSemverVersion) {
|
|
20
|
+
const projectFile = path.join(this.cwd, this.relativeProjectFile);
|
|
21
|
+
const fileContents = await readFile(projectFile, 'utf-8');
|
|
22
|
+
const preRelease = getPreRelease(nextSemverVersion.version);
|
|
23
|
+
const newFileContents = fileContents
|
|
24
|
+
.replace(/<PackageVersion>.*<\/PackageVersion>/, `<PackageVersion>${nextSemverVersion.version}</PackageVersion>`)
|
|
25
|
+
.replace(/<AssemblyVersion>.*<\/AssemblyVersion>/, `<AssemblyVersion>${nextSemverVersion.major}.${nextSemverVersion.minor}.${nextSemverVersion.patch}.${preRelease?.version ?? 0}</AssemblyVersion>`);
|
|
26
|
+
await writeFile(projectFile, newFileContents, 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import NpmPlugin from './NpmPlugin.js';
|
|
4
|
+
import NugetPlugin from './NugetPlugin.js';
|
|
5
|
+
import NodeJsPlugin from './NodeJsPlugin.js';
|
|
6
|
+
import CdnHostingPlugin from './CdnHostingPlugin.js';
|
|
7
|
+
async function exists(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
await fs.stat(filePath);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export default async function getRepositoryPlugin({ cwd, repositoryType, }) {
|
|
17
|
+
switch (repositoryType?.type) {
|
|
18
|
+
case 'CDN_HOSTING': {
|
|
19
|
+
return new CdnHostingPlugin({
|
|
20
|
+
cwd,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
case 'NODE_JS': {
|
|
24
|
+
return new NodeJsPlugin({
|
|
25
|
+
cwd,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
case 'NPM': {
|
|
29
|
+
return new NpmPlugin({
|
|
30
|
+
cwd,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
case 'NUGET': {
|
|
34
|
+
return new NugetPlugin({
|
|
35
|
+
cwd,
|
|
36
|
+
relativeProjectFile: repositoryType.relativeProjectFile,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// If we can't work it out based on type, we will check for certain files.
|
|
41
|
+
if (await exists(path.join(cwd, 'package.json'))) {
|
|
42
|
+
return new NpmPlugin({
|
|
43
|
+
cwd,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const dotnetSdkProjectFile = path.join('OneBlink.SDK', 'OneBlink.SDK.csproj');
|
|
47
|
+
if (await exists(path.join(cwd, dotnetSdkProjectFile))) {
|
|
48
|
+
return new NugetPlugin({
|
|
49
|
+
cwd,
|
|
50
|
+
relativeProjectFile: dotnetSdkProjectFile,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`Could not determine the type of repository: ${cwd}`);
|
|
54
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import enquirer from 'enquirer';
|
|
5
|
+
import executeCommand from './executeCommand.js';
|
|
6
|
+
import parseChangelogWithLoading from './parseChangelogWithLoading.js';
|
|
7
|
+
import promptForNextVersion from './promptForNextVersion.js';
|
|
8
|
+
import boxen from 'boxen';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import wrapWithLoading from './wrapWithLoading.js';
|
|
11
|
+
import startRepositoryRelease from './startRepositoryRelease.js';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import getRepositoryPlugin from './repositories-plugins/plugins-factory.js';
|
|
14
|
+
const gitCloneRepositories = [
|
|
15
|
+
{
|
|
16
|
+
repositoryName: 'apps',
|
|
17
|
+
type: 'NPM',
|
|
18
|
+
isPublic: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
repositoryName: 'apps-react',
|
|
22
|
+
type: 'NPM',
|
|
23
|
+
isPublic: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
repositoryName: 'cli',
|
|
27
|
+
isPublic: true,
|
|
28
|
+
type: 'NPM',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
repositoryName: 'forms-cdn',
|
|
32
|
+
isPublic: true,
|
|
33
|
+
type: 'CDN_HOSTING',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
repositoryName: 'product-api',
|
|
37
|
+
isPublic: false,
|
|
38
|
+
type: 'NODE_JS',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
repositoryName: 'product-approvals-api',
|
|
42
|
+
isPublic: false,
|
|
43
|
+
type: 'NODE_JS',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
repositoryName: 'product-approvals-client',
|
|
47
|
+
isPublic: false,
|
|
48
|
+
type: 'NODE_JS',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
repositoryName: 'product-cognito-hosted-login-css',
|
|
52
|
+
isPublic: false,
|
|
53
|
+
type: 'NODE_JS',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
repositoryName: 'product-console',
|
|
57
|
+
isPublic: false,
|
|
58
|
+
type: 'NODE_JS',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
repositoryName: 'product-form-store-client',
|
|
62
|
+
isPublic: false,
|
|
63
|
+
type: 'NODE_JS',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
repositoryName: 'product-forms-lambda-at-edge-authorisation',
|
|
67
|
+
isPublic: false,
|
|
68
|
+
type: 'NODE_JS',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
repositoryName: 'product-forms-renderer',
|
|
72
|
+
isPublic: false,
|
|
73
|
+
type: 'NODE_JS',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
repositoryName: 'product-infrastructure',
|
|
77
|
+
isPublic: false,
|
|
78
|
+
type: 'NODE_JS',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
repositoryName: 'product-pdf',
|
|
82
|
+
isPublic: false,
|
|
83
|
+
type: 'NODE_JS',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
repositoryName: 'product-s3-submission-events',
|
|
87
|
+
isPublic: false,
|
|
88
|
+
type: 'NODE_JS',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
repositoryName: 'product-volunteers-client',
|
|
92
|
+
isPublic: false,
|
|
93
|
+
type: 'NODE_JS',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
repositoryName: 'sdk-core-js',
|
|
97
|
+
isPublic: true,
|
|
98
|
+
type: 'NPM',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
repositoryName: 'sdk-dotnet',
|
|
102
|
+
isPublic: true,
|
|
103
|
+
type: 'NUGET',
|
|
104
|
+
relativeProjectFile: path.join('OneBlink.SDK', 'OneBlink.SDK.csproj'),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
repositoryName: 'sdk-node-js',
|
|
108
|
+
isPublic: true,
|
|
109
|
+
type: 'NPM',
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
export default async function startProductRelease({ releaseName, }) {
|
|
113
|
+
console.log('Beginning Product release process for:', releaseName);
|
|
114
|
+
const deploymentRequiredUrls = [];
|
|
115
|
+
for (const gitCloneRepository of gitCloneRepositories) {
|
|
116
|
+
const { repositoryName } = gitCloneRepository;
|
|
117
|
+
const repositoryWorkingDirectory = await wrapWithLoading({
|
|
118
|
+
startText: `Creating temporary directory to clone "${repositoryName}"`,
|
|
119
|
+
failText: `Failed to create temporary directory to clone "${repositoryName}"`,
|
|
120
|
+
}, async (spinner) => {
|
|
121
|
+
const directory = await mkdtemp(join(tmpdir(), repositoryName));
|
|
122
|
+
spinner.succeed(`Created temporary directory to clone "${repositoryName}"`);
|
|
123
|
+
return directory;
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
const cloneUrl = `git@github.com:oneblink/${repositoryName}.git`;
|
|
127
|
+
await executeCommand('git', ['clone', cloneUrl, repositoryWorkingDirectory], '.');
|
|
128
|
+
// Check if repository needs releasing
|
|
129
|
+
const { parsedChangelog } = await parseChangelogWithLoading(repositoryWorkingDirectory);
|
|
130
|
+
const unreleasedVersion = parsedChangelog.versions.find((version) => version.title.toLowerCase().includes('unreleased'));
|
|
131
|
+
if (!unreleasedVersion) {
|
|
132
|
+
await continuePromptWithWarning(`"${repositoryName}" CHANGELOG.md does not contain an "Unreleased" section
|
|
133
|
+
|
|
134
|
+
You need to checkout "${cloneUrl}" to fix this before trying again.`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const { stdout: lastCommitMessage } = await executeCommand('git', ['log', '-1', '--pretty=oneline'], repositoryWorkingDirectory);
|
|
138
|
+
const unreleasedChangelogEntries = unreleasedVersion.body.trim() ||
|
|
139
|
+
chalk.italic('There are no entries under the "Unreleased" heading.');
|
|
140
|
+
console.log(boxen(chalk.blue(`${unreleasedChangelogEntries}
|
|
141
|
+
|
|
142
|
+
Last Commit: ${lastCommitMessage}`), {
|
|
143
|
+
title: 'Unreleased Entries',
|
|
144
|
+
padding: 1,
|
|
145
|
+
margin: {
|
|
146
|
+
top: 1,
|
|
147
|
+
bottom: 1,
|
|
148
|
+
},
|
|
149
|
+
}));
|
|
150
|
+
const { isReleasing } = await enquirer.prompt({
|
|
151
|
+
type: 'select',
|
|
152
|
+
name: 'isReleasing',
|
|
153
|
+
message: `Would you like to release "${repositoryName}"? See unreleased section from changelog above to decide.`,
|
|
154
|
+
choices: [
|
|
155
|
+
{
|
|
156
|
+
message: `No! "${repositoryName}" does not need to be released.`,
|
|
157
|
+
name: 'no',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
message: 'Yes, release away!',
|
|
161
|
+
name: 'yes',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
});
|
|
165
|
+
if (isReleasing === 'no') {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const repositoryPlugin = await getRepositoryPlugin({
|
|
169
|
+
cwd: repositoryWorkingDirectory,
|
|
170
|
+
repositoryType: gitCloneRepository,
|
|
171
|
+
});
|
|
172
|
+
const { nextVersion } = await promptForNextVersion({
|
|
173
|
+
repositoryPlugin,
|
|
174
|
+
noPreRelease: true,
|
|
175
|
+
});
|
|
176
|
+
await startRepositoryRelease({
|
|
177
|
+
nextVersion,
|
|
178
|
+
git: true,
|
|
179
|
+
releaseName: gitCloneRepository.isPublic ? undefined : releaseName,
|
|
180
|
+
repositoryPlugin,
|
|
181
|
+
});
|
|
182
|
+
if (repositoryPlugin.isDeploymentRequired) {
|
|
183
|
+
deploymentRequiredUrls.push(`https://github.com/oneblink/${repositoryName}/actions`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
await wrapWithLoading({
|
|
188
|
+
startText: `Removing temporary directory for "${repositoryName}"`,
|
|
189
|
+
failText: `Failed to remove temporary directory for "${repositoryName}"`,
|
|
190
|
+
}, async (spinner) => {
|
|
191
|
+
await rm(repositoryWorkingDirectory, {
|
|
192
|
+
recursive: true,
|
|
193
|
+
force: true,
|
|
194
|
+
});
|
|
195
|
+
spinner.succeed(`Removed temporary directory for "${repositoryName}"`);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
console.log(boxen(chalk.green('Product Release Complete!!!'), {
|
|
200
|
+
padding: 1,
|
|
201
|
+
}));
|
|
202
|
+
if (deploymentRequiredUrls.length) {
|
|
203
|
+
console.log(boxen(`The following repositories need to be deployed when ready:
|
|
204
|
+
|
|
205
|
+
${deploymentRequiredUrls.join(`
|
|
206
|
+
`)}`, {
|
|
207
|
+
padding: 1,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function continuePromptWithWarning(warning) {
|
|
212
|
+
console.log(boxen(chalk.yellow(warning), {
|
|
213
|
+
padding: 1,
|
|
214
|
+
}));
|
|
215
|
+
await enquirer.prompt({
|
|
216
|
+
type: 'invisible',
|
|
217
|
+
name: 'continue',
|
|
218
|
+
message: 'Press ENTER to continue.',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
@@ -1,45 +1,27 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import util from 'util';
|
|
4
|
-
import { execa } from 'execa';
|
|
5
4
|
import prettier from 'prettier';
|
|
6
|
-
import parseChangelog from 'changelog-parser';
|
|
7
|
-
import { main as packageDiffSummary } from './package-diff-summary/index.js';
|
|
8
5
|
import semver from 'semver';
|
|
9
6
|
import ora from 'ora';
|
|
10
|
-
import
|
|
7
|
+
import wrapWithLoading from './wrapWithLoading.js';
|
|
8
|
+
import executeCommand from './executeCommand.js';
|
|
9
|
+
import parseChangelogWithLoading from './parseChangelogWithLoading.js';
|
|
10
|
+
import getPreRelease from './getPreRelease.js';
|
|
11
11
|
const readFileAsync = util.promisify(fs.readFile);
|
|
12
12
|
const writeFileAsync = util.promisify(fs.writeFile);
|
|
13
13
|
const UNRELEASED_VERSION_INDEX = 0;
|
|
14
14
|
const GIT_TAG_PREFIX = 'v';
|
|
15
|
-
async function
|
|
16
|
-
const
|
|
17
|
-
try {
|
|
18
|
-
const t = await fn(spinner);
|
|
19
|
-
if (spinner.isSpinning) {
|
|
20
|
-
spinner.stop();
|
|
21
|
-
}
|
|
22
|
-
return t;
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
spinner.fail(failText);
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
async function updateChangelog({ nextSemverVersion, cwd, releaseName, }) {
|
|
30
|
-
const changelogPath = path.join(cwd, 'CHANGELOG.md');
|
|
31
|
-
const parsedChangelog = await wrapWithLoading({
|
|
32
|
-
startText: `Parsing ${changelogPath}`,
|
|
33
|
-
failText: `Failed to parsed ${changelogPath}`,
|
|
34
|
-
}, async (spinner) => {
|
|
35
|
-
const parsedChangelog = (await parseChangelog(changelogPath));
|
|
36
|
-
spinner.succeed(`Parsed ${changelogPath}`);
|
|
37
|
-
return parsedChangelog;
|
|
38
|
-
});
|
|
15
|
+
async function updateChangelog({ nextSemverVersion, releaseName, repositoryPlugin, }) {
|
|
16
|
+
const { parsedChangelog, changelogPath } = await parseChangelogWithLoading(repositoryPlugin.cwd);
|
|
39
17
|
const dependenciesChangelogEntry = await wrapWithLoading({
|
|
40
18
|
startText: 'Checking if the "Dependencies" heading should be added to CHANGELOG.md',
|
|
41
19
|
failText: 'Failed to check if the "Dependencies" heading should be added to CHANGELOG.md',
|
|
42
20
|
}, async (spinner) => {
|
|
21
|
+
if (!repositoryPlugin.generateDependenciesChangelog) {
|
|
22
|
+
spinner.info(`Evaluating "Dependencies" is not supported for ${repositoryPlugin.displayType} repositories.`);
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
43
25
|
const unreleasedVersion = parsedChangelog.versions[UNRELEASED_VERSION_INDEX];
|
|
44
26
|
if (!unreleasedVersion ||
|
|
45
27
|
!unreleasedVersion.title.toLowerCase().includes('unreleased')) {
|
|
@@ -55,25 +37,15 @@ async function updateChangelog({ nextSemverVersion, cwd, releaseName, }) {
|
|
|
55
37
|
spinner.info('Skipping inserting the "Dependencies" heading in CHANGELOG.md as this is the first release according to the CHANGELOG.md.');
|
|
56
38
|
return '';
|
|
57
39
|
}
|
|
58
|
-
let dependenciesChangelogEntries = '';
|
|
59
40
|
const lastGitTag = `${GIT_TAG_PREFIX}${lastVersion.version}`;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
dependenciesChangelogEntries = result.trim();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
if (error.message.includes(`git show ${lastGitTag}:package.json`)) {
|
|
71
|
-
spinner.warn(`Skipping inserting the "Dependencies" heading in CHANGELOG.md as it relies on the last release's git tag having a "v" prefix (i.e. "${lastGitTag}")`);
|
|
72
|
-
return '';
|
|
73
|
-
}
|
|
74
|
-
throw error;
|
|
41
|
+
const dependenciesChangelog = await repositoryPlugin.generateDependenciesChangelog({
|
|
42
|
+
previousVersion: lastGitTag,
|
|
43
|
+
});
|
|
44
|
+
if (dependenciesChangelog.result === 'WARNING') {
|
|
45
|
+
spinner.warn(dependenciesChangelog.message);
|
|
46
|
+
return '';
|
|
75
47
|
}
|
|
76
|
-
if (!
|
|
48
|
+
if (!dependenciesChangelog.entries?.trim()) {
|
|
77
49
|
spinner.info(`Skipping inserting the "Dependencies" heading in CHANGELOG.md as there were no dependency changes since the last release (${lastVersion.version})`);
|
|
78
50
|
return '';
|
|
79
51
|
}
|
|
@@ -81,10 +53,10 @@ async function updateChangelog({ nextSemverVersion, cwd, releaseName, }) {
|
|
|
81
53
|
return `
|
|
82
54
|
${dependenciesChangelogHeading}
|
|
83
55
|
|
|
84
|
-
${
|
|
56
|
+
${dependenciesChangelog.entries}
|
|
85
57
|
`;
|
|
86
58
|
});
|
|
87
|
-
const nextReleaseTitle = `[${nextSemverVersion}] - ${new Date()
|
|
59
|
+
const nextReleaseTitle = `[${nextSemverVersion.version}] - ${new Date()
|
|
88
60
|
.toISOString()
|
|
89
61
|
.substring(0, 10)}`;
|
|
90
62
|
const releaseNameSubtitle = releaseName
|
|
@@ -98,7 +70,7 @@ ${dependenciesChangelogEntries}
|
|
|
98
70
|
}, async (spinner) => {
|
|
99
71
|
let prettierOptions = {};
|
|
100
72
|
try {
|
|
101
|
-
const s = await readFileAsync(path.join(cwd, '.prettierrc'), 'utf-8');
|
|
73
|
+
const s = await readFileAsync(path.join(repositoryPlugin.cwd, '.prettierrc'), 'utf-8');
|
|
102
74
|
prettierOptions = JSON.parse(s);
|
|
103
75
|
}
|
|
104
76
|
catch (error) {
|
|
@@ -136,52 +108,39 @@ ${body}
|
|
|
136
108
|
spinner.succeed(`Updated CHANGELOG.md with next release (${nextReleaseTitle})`);
|
|
137
109
|
});
|
|
138
110
|
}
|
|
139
|
-
async function
|
|
140
|
-
const
|
|
141
|
-
cwd,
|
|
142
|
-
});
|
|
143
|
-
return !!result?.packageJson;
|
|
144
|
-
}
|
|
145
|
-
async function executeCommand(command, args, cwd) {
|
|
146
|
-
await wrapWithLoading({
|
|
147
|
-
startText: `Running "${command} ${args.join(' ')}"`,
|
|
148
|
-
failText: `Failed to run "${command} ${args.join(' ')}"`,
|
|
149
|
-
}, async (spinner) => {
|
|
150
|
-
await execa(command, args, {
|
|
151
|
-
cwd,
|
|
152
|
-
});
|
|
153
|
-
spinner.succeed(`Ran "${command} ${args.join(' ')}"`);
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
export default async function startReleaseProcess({ nextVersion, preRelease, cwd, git, releaseName, }) {
|
|
157
|
-
const nextSemverVersion = semver.valid(nextVersion);
|
|
111
|
+
export default async function startRepositoryRelease({ nextVersion, git, releaseName, repositoryPlugin, }) {
|
|
112
|
+
const nextSemverVersion = semver.parse(nextVersion);
|
|
158
113
|
if (!nextSemverVersion) {
|
|
159
114
|
throw new Error('Next version is not valid semver');
|
|
160
115
|
}
|
|
161
|
-
const
|
|
116
|
+
const preRelease = getPreRelease(nextVersion);
|
|
162
117
|
if (preRelease) {
|
|
163
|
-
const text = `Skipping changelog updates for "${preRelease}" release`;
|
|
118
|
+
const text = `Skipping changelog updates for "${preRelease.tag}" release`;
|
|
164
119
|
ora(text).start().info(text);
|
|
165
120
|
}
|
|
166
121
|
else {
|
|
167
122
|
await updateChangelog({
|
|
168
123
|
nextSemverVersion,
|
|
169
|
-
cwd,
|
|
170
124
|
releaseName,
|
|
125
|
+
repositoryPlugin,
|
|
171
126
|
});
|
|
172
127
|
}
|
|
173
|
-
|
|
174
|
-
await executeCommand('npm', ['version', nextSemverVersion, '--no-git-tag-version'], cwd);
|
|
175
|
-
}
|
|
128
|
+
await repositoryPlugin.incrementVersion(nextSemverVersion);
|
|
176
129
|
if (!git) {
|
|
177
130
|
const text = `Skipping committing release changes using git`;
|
|
178
131
|
ora(text).start().info(text);
|
|
179
132
|
return;
|
|
180
133
|
}
|
|
181
|
-
const message = `[RELEASE] ${nextSemverVersion}${releaseName ? ` - ${releaseName}` : ''}`;
|
|
182
|
-
await executeCommand('git', ['add', '-A'], cwd);
|
|
183
|
-
await executeCommand('git', ['commit', '--message', message], cwd);
|
|
184
|
-
await executeCommand('git', ['push'], cwd);
|
|
185
|
-
await executeCommand('git', [
|
|
186
|
-
|
|
134
|
+
const message = `[RELEASE] ${nextSemverVersion.version}${releaseName ? ` - ${releaseName}` : ''}`;
|
|
135
|
+
await executeCommand('git', ['add', '-A'], repositoryPlugin.cwd);
|
|
136
|
+
await executeCommand('git', ['commit', '--message', message], repositoryPlugin.cwd);
|
|
137
|
+
await executeCommand('git', ['push'], repositoryPlugin.cwd);
|
|
138
|
+
await executeCommand('git', [
|
|
139
|
+
'tag',
|
|
140
|
+
'-a',
|
|
141
|
+
`${GIT_TAG_PREFIX}${nextSemverVersion.version}`,
|
|
142
|
+
'-m',
|
|
143
|
+
message,
|
|
144
|
+
], repositoryPlugin.cwd);
|
|
145
|
+
await executeCommand('git', ['push', '--tags'], repositoryPlugin.cwd);
|
|
187
146
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
export default async function wrapWithLoading({ startText, failText }, fn) {
|
|
3
|
+
const spinner = ora(startText).start();
|
|
4
|
+
try {
|
|
5
|
+
const t = await fn(spinner);
|
|
6
|
+
if (spinner.isSpinning) {
|
|
7
|
+
spinner.stop();
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
spinner.fail(failText);
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneblink/release-cli",
|
|
3
|
-
"description": "Used internally by OneBlink to release
|
|
4
|
-
"version": "
|
|
3
|
+
"description": "Used internally by OneBlink to release repositories quickly and consistently",
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"author": "OneBlink <developers@oneblink> (https://github.com/oneblink)",
|
|
6
6
|
"bin": {
|
|
7
7
|
"oneblink-release": "dist/bin.js"
|
|
@@ -9,8 +9,13 @@
|
|
|
9
9
|
"bugs": {
|
|
10
10
|
"url": "https://github.com/oneblink/release-cli/issues"
|
|
11
11
|
},
|
|
12
|
+
"bundleDependencies": [
|
|
13
|
+
"changelog-parser"
|
|
14
|
+
],
|
|
12
15
|
"dependencies": {
|
|
13
16
|
"@octokit/rest": "^20.0.2",
|
|
17
|
+
"boxen": "^7.1.1",
|
|
18
|
+
"chalk": "^5.3.0",
|
|
14
19
|
"changelog-parser": "^3.0.1",
|
|
15
20
|
"dependency-diff": "^1.0.4",
|
|
16
21
|
"enquirer": "^2.4.1",
|
|
@@ -24,9 +29,6 @@
|
|
|
24
29
|
"semver": "^7.5.4",
|
|
25
30
|
"update-notifier": "^7.0.0"
|
|
26
31
|
},
|
|
27
|
-
"bundleDependencies": [
|
|
28
|
-
"changelog-parser"
|
|
29
|
-
],
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"@types/changelog-parser": "^2.8.3",
|
|
32
34
|
"@types/github-url-from-git": "^1.5.2",
|
|
@@ -72,12 +74,13 @@
|
|
|
72
74
|
"build": "tsc --build",
|
|
73
75
|
"eslint": "eslint --fix --cache --quiet ./src",
|
|
74
76
|
"jest": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --silent",
|
|
77
|
+
"postinstall": "patch-package",
|
|
75
78
|
"prepare": "npm run build",
|
|
79
|
+
"prerelease": "npm run build",
|
|
76
80
|
"pretest": "npm run eslint",
|
|
81
|
+
"release": "./dist/bin.js repository --no-name --cwd .",
|
|
77
82
|
"test": "npm run jest",
|
|
78
|
-
"
|
|
79
|
-
"prerelease": "npm run build",
|
|
80
|
-
"release": "./dist/bin.js --no-name --cwd ."
|
|
83
|
+
"typescript": "tsc --noEmit"
|
|
81
84
|
},
|
|
82
85
|
"type": "module"
|
|
83
86
|
}
|