deploy-vir 0.3.0 → 2.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 +12 -4
- package/dist/cli/cli.d.ts +6 -1
- package/dist/cli/cli.js +17 -9
- package/dist/cli/cli.script.js +15 -4
- package/dist/config.d.ts +4 -2
- package/dist/git.d.ts +1 -1
- package/dist/git.js +66 -63
- package/package.json +32 -31
- package/src/cli/cli.script.ts +21 -6
- package/src/cli/cli.ts +37 -9
- package/src/config.ts +4 -2
- package/src/git.ts +92 -87
package/README.md
CHANGED
|
@@ -26,13 +26,21 @@ export default defineDeployVirConfig({
|
|
|
26
26
|
deploys: [
|
|
27
27
|
{
|
|
28
28
|
deployName: 'staging',
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
branches: [
|
|
30
|
+
{
|
|
31
|
+
fromBranch: 'dev',
|
|
32
|
+
toBranch: 'staging',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
31
35
|
},
|
|
32
36
|
{
|
|
33
37
|
deployName: 'prod',
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
branches: [
|
|
39
|
+
{
|
|
40
|
+
fromBranch: 'staging',
|
|
41
|
+
toBranch: 'prod',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
36
44
|
},
|
|
37
45
|
],
|
|
38
46
|
},
|
package/dist/cli/cli.d.ts
CHANGED
|
@@ -4,7 +4,10 @@ import { type DeployVirConfig } from '../config.js';
|
|
|
4
4
|
*
|
|
5
5
|
* @category Internal
|
|
6
6
|
*/
|
|
7
|
-
export declare function runDeployVirCli(args
|
|
7
|
+
export declare function runDeployVirCli({ args, y, }: {
|
|
8
|
+
y: boolean;
|
|
9
|
+
args: ReadonlyArray<string>;
|
|
10
|
+
}, cwd?: string): Promise<void>;
|
|
8
11
|
/**
|
|
9
12
|
* Args for {@link runDeployVir}.
|
|
10
13
|
*
|
|
@@ -16,6 +19,8 @@ export type DeployVirArgs = {
|
|
|
16
19
|
/** This much match a deploy name in your config. */
|
|
17
20
|
deployName: string;
|
|
18
21
|
cwd: string;
|
|
22
|
+
/** If true, manual confirmation is not required. */
|
|
23
|
+
bypassConfirmation: boolean;
|
|
19
24
|
};
|
|
20
25
|
/**
|
|
21
26
|
* Run a deploy.
|
package/dist/cli/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assertWrap } from '@augment-vir/assert';
|
|
2
|
-
import { extractErrorMessage, log } from '@augment-vir/common';
|
|
2
|
+
import { awaitedForEach, extractErrorMessage, log } from '@augment-vir/common';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import simpleGit from 'simple-git';
|
|
5
5
|
import { getGitRemoteName, pushDeploy } from '../git.js';
|
|
@@ -10,12 +10,17 @@ import { sendNotifications } from '../notifications/send-notifications.js';
|
|
|
10
10
|
*
|
|
11
11
|
* @category Internal
|
|
12
12
|
*/
|
|
13
|
-
export async function runDeployVirCli(args, cwd = process.cwd()) {
|
|
13
|
+
export async function runDeployVirCli({ args, y, }, cwd = process.cwd()) {
|
|
14
14
|
const configPath = assertWrap.isTruthy(args[0], 'Missing config path.');
|
|
15
15
|
const repoName = assertWrap.isTruthy(args[1], 'Missing repo name.');
|
|
16
16
|
const deployName = assertWrap.isTruthy(args[2], 'Missing deploy name.');
|
|
17
17
|
const config = (await import(resolve(cwd, configPath))).default;
|
|
18
|
-
await runDeployVir({
|
|
18
|
+
await runDeployVir({
|
|
19
|
+
cwd,
|
|
20
|
+
deployName,
|
|
21
|
+
repoName,
|
|
22
|
+
bypassConfirmation: y,
|
|
23
|
+
}, config);
|
|
19
24
|
}
|
|
20
25
|
/**
|
|
21
26
|
* Run a deploy.
|
|
@@ -29,12 +34,15 @@ export async function runDeployVir(args, config) {
|
|
|
29
34
|
const branchConfig = assertWrap.isDefined(repoConfig.deploys.find((deploy) => deploy.deployName === args.deployName, `Failed to find a deploy named '${args.deployName}' in repo '${args.repoName}'.`));
|
|
30
35
|
const remoteName = await getGitRemoteName(git, repoConfig);
|
|
31
36
|
log.faint(`Remote: ${remoteName}`);
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const deployResults = await pushDeploy(git, branchConfig, remoteName, args.bypassConfirmation);
|
|
38
|
+
const notifications = config.notifications;
|
|
39
|
+
if (notifications?.length) {
|
|
40
|
+
await awaitedForEach(deployResults, async (deployResult) => {
|
|
41
|
+
await sendNotifications(notifications, {
|
|
42
|
+
branchConfig,
|
|
43
|
+
deployResult,
|
|
44
|
+
repoConfig,
|
|
45
|
+
});
|
|
38
46
|
});
|
|
39
47
|
}
|
|
40
48
|
}
|
package/dist/cli/cli.script.js
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FlagRequirement, parseArgs } from 'cli-vir';
|
|
2
2
|
import { runDeployVirCli } from './cli.js';
|
|
3
|
-
await runDeployVirCli(
|
|
3
|
+
await runDeployVirCli(parseArgs(process.argv, {
|
|
4
|
+
y: {
|
|
5
|
+
flag: {
|
|
6
|
+
valueRequirement: FlagRequirement.Blocked,
|
|
7
|
+
},
|
|
8
|
+
description: 'If set, bypass manual validation.',
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
position: {
|
|
12
|
+
rest: true,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
}, {
|
|
4
16
|
binName: 'deploy-vir',
|
|
5
|
-
|
|
6
|
-
rawArgs: process.argv,
|
|
17
|
+
importMeta: import.meta,
|
|
7
18
|
}), process.cwd());
|
package/dist/config.d.ts
CHANGED
|
@@ -90,8 +90,10 @@ export type DeployVirRepoConfig = {
|
|
|
90
90
|
*/
|
|
91
91
|
export type DeployVirBranchConfig = {
|
|
92
92
|
deployName: string;
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
branches: {
|
|
94
|
+
fromBranch: string;
|
|
95
|
+
toBranch: string;
|
|
96
|
+
}[];
|
|
95
97
|
};
|
|
96
98
|
/**
|
|
97
99
|
* Define a type safe deploy-vir config.
|
package/dist/git.d.ts
CHANGED
|
@@ -44,4 +44,4 @@ export type DeployResult = {
|
|
|
44
44
|
*
|
|
45
45
|
* @category Internal
|
|
46
46
|
*/
|
|
47
|
-
export declare function pushDeploy(git: Readonly<SimpleGit>, { deployName,
|
|
47
|
+
export declare function pushDeploy(git: Readonly<SimpleGit>, { deployName, branches }: Readonly<DeployVirBranchConfig>, remoteName: string, bypassConfirmation?: boolean): Promise<DeployResult[]>;
|
package/dist/git.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert, assertWrap, check } from '@augment-vir/assert';
|
|
2
|
-
import { log, logColors } from '@augment-vir/common';
|
|
2
|
+
import { awaitedBlockingMap, log, logColors } from '@augment-vir/common';
|
|
3
3
|
import { confirm } from '@inquirer/prompts';
|
|
4
4
|
import { KnownError } from './known.error.js';
|
|
5
5
|
/**
|
|
@@ -30,69 +30,72 @@ export async function getGitRemoteName(git, repoConfig) {
|
|
|
30
30
|
*
|
|
31
31
|
* @category Internal
|
|
32
32
|
*/
|
|
33
|
-
export async function pushDeploy(git, { deployName,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
if (requiresForcePush) {
|
|
67
|
-
log.info(`\nOnly on ${logColors.bold}${toBranch}${logColors.reset}:`);
|
|
68
|
-
commitsBehind.forEach((commit, index) => {
|
|
33
|
+
export async function pushDeploy(git, { deployName, branches }, remoteName, bypassConfirmation = false) {
|
|
34
|
+
return await awaitedBlockingMap(branches, async ({ fromBranch, toBranch }) => {
|
|
35
|
+
assert.isTruthy(fromBranch, `Deploy '${deployName}' fromBranch cannot be empty.`);
|
|
36
|
+
assert.isTruthy(toBranch, `Deploy '${deployName}' toBranch cannot be empty.`);
|
|
37
|
+
assert.isTruthy(remoteName, 'Remote name cannot be empty.');
|
|
38
|
+
log.faint(`Pushing ${remoteName}/${fromBranch} to ${toBranch}`);
|
|
39
|
+
await git.fetch(remoteName, fromBranch);
|
|
40
|
+
await git.fetch(remoteName, toBranch);
|
|
41
|
+
const pushString = `${remoteName}/${fromBranch}:${toBranch}`;
|
|
42
|
+
// Get the current commit on the target branch before pushing
|
|
43
|
+
const beforeCommit = assertWrap.isDefined((await git.log([
|
|
44
|
+
`${remoteName}/${toBranch}`,
|
|
45
|
+
'-1',
|
|
46
|
+
])).latest, `Failed to get current commit for ${remoteName}/${toBranch}`);
|
|
47
|
+
const afterCommit = assertWrap.isDefined((await git.log([
|
|
48
|
+
`${remoteName}/${fromBranch}`,
|
|
49
|
+
'-1',
|
|
50
|
+
])).latest, `Failed to get current commit for ${remoteName}/${fromBranch}`);
|
|
51
|
+
/** Get commits that are on {@link toBranch} but not on {@link fromBranch}. */
|
|
52
|
+
const commitsAhead = (await git.log([
|
|
53
|
+
`${remoteName}/${toBranch}..${remoteName}/${fromBranch}`,
|
|
54
|
+
])).all;
|
|
55
|
+
if (!commitsAhead.length) {
|
|
56
|
+
throw new KnownError('No commit diff: nothing to deploy!');
|
|
57
|
+
}
|
|
58
|
+
/** Get commits that are on {@link toBranch} but not on {@link fromBranch}. */
|
|
59
|
+
const commitsBehind = (await git.log([
|
|
60
|
+
`${remoteName}/${fromBranch}..${remoteName}/${toBranch}`,
|
|
61
|
+
])).all;
|
|
62
|
+
const requiresForcePush = commitsBehind.length > 0;
|
|
63
|
+
log.info(`\n${requiresForcePush ? 'Only on' : 'Releasing from'} ${logColors.bold}${fromBranch}${logColors.reset}:`);
|
|
64
|
+
commitsAhead.forEach((commit, index) => {
|
|
69
65
|
log.faint(` ${index + 1}. ${commit.hash.slice(0, 7)} (${commit.author_name}) - ${commit.message}`);
|
|
70
66
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
if (requiresForcePush) {
|
|
68
|
+
log.info(`\nOnly on ${logColors.bold}${toBranch}${logColors.reset}:`);
|
|
69
|
+
commitsBehind.forEach((commit, index) => {
|
|
70
|
+
log.faint(` ${index + 1}. ${commit.hash.slice(0, 7)} (${commit.author_name}) - ${commit.message}`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const shouldPush = bypassConfirmation ||
|
|
74
|
+
(await confirm({
|
|
75
|
+
message: requiresForcePush
|
|
76
|
+
? `\n${logColors.warning}Do you want to force push ${logColors.bold}${fromBranch}${logColors.normalWeight} to ${logColors.bold}${toBranch}${logColors.normalWeight}?\n\nThis will overwrite the commits only on ${logColors.bold}${toBranch}${logColors.normalWeight}.${logColors.reset}?`
|
|
77
|
+
: 'Ready to deploy?',
|
|
78
|
+
default: false,
|
|
79
|
+
}));
|
|
80
|
+
if (!shouldPush) {
|
|
81
|
+
throw new KnownError(`Deploy aborted.`);
|
|
82
|
+
}
|
|
83
|
+
if (requiresForcePush) {
|
|
84
|
+
await git.push(remoteName, pushString, ['--force']);
|
|
85
|
+
log.warning(`Force pushed ${logColors.bold}${fromBranch}${logColors.normalWeight} to ${logColors.bold}${toBranch}${logColors.reset}.`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
await git.push(remoteName, pushString);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
deployCommits: {
|
|
92
|
+
deployedCommits: commitsAhead,
|
|
93
|
+
overwrittenCommits: commitsBehind,
|
|
94
|
+
},
|
|
95
|
+
branchStatus: {
|
|
96
|
+
before: beforeCommit,
|
|
97
|
+
after: afterCommit,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
77
100
|
});
|
|
78
|
-
if (!shouldPush) {
|
|
79
|
-
throw new KnownError(`Deploy aborted.`);
|
|
80
|
-
}
|
|
81
|
-
if (requiresForcePush) {
|
|
82
|
-
await git.push(remoteName, pushString, ['--force']);
|
|
83
|
-
log.warning(`Force pushed ${logColors.bold}${fromBranch}${logColors.normalWeight} to ${logColors.bold}${toBranch}${logColors.reset}.`);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
await git.push(remoteName, pushString);
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
deployCommits: {
|
|
90
|
-
deployedCommits: commitsAhead,
|
|
91
|
-
overwrittenCommits: commitsBehind,
|
|
92
|
-
},
|
|
93
|
-
branchStatus: {
|
|
94
|
-
before: beforeCommit,
|
|
95
|
-
after: afterCommit,
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
101
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deploy-vir",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Deploy a branch to another branch with notifications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deploy",
|
|
@@ -41,50 +41,51 @@
|
|
|
41
41
|
"test:update": "npm run test update"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@augment-vir/assert": "^31.
|
|
45
|
-
"@augment-vir/common": "^31.
|
|
46
|
-
"@augment-vir/node": "^31.
|
|
47
|
-
"@inquirer/prompts": "^
|
|
48
|
-
"@slack/web-api": "^7.
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
44
|
+
"@augment-vir/assert": "^31.57.1",
|
|
45
|
+
"@augment-vir/common": "^31.57.1",
|
|
46
|
+
"@augment-vir/node": "^31.57.1",
|
|
47
|
+
"@inquirer/prompts": "^8.1.0",
|
|
48
|
+
"@slack/web-api": "^7.13.0",
|
|
49
|
+
"cli-vir": "^1.0.3",
|
|
50
|
+
"simple-git": "^3.30.0",
|
|
51
|
+
"tsx": "^4.21.0",
|
|
52
|
+
"url-vir": "^2.1.7"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
|
-
"@eslint/eslintrc": "^3.3.
|
|
55
|
-
"@eslint/js": "^9.
|
|
56
|
-
"@stylistic/eslint-plugin": "^5.
|
|
55
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
56
|
+
"@eslint/js": "^9.39.2",
|
|
57
|
+
"@stylistic/eslint-plugin": "^5.6.1",
|
|
57
58
|
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
|
58
|
-
"@types/node": "^
|
|
59
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
60
|
-
"cspell": "^9.
|
|
61
|
-
"dependency-cruiser": "^17.
|
|
62
|
-
"esbuild": "^0.
|
|
63
|
-
"eslint": "^9.
|
|
59
|
+
"@types/node": "^25.0.3",
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
61
|
+
"cspell": "^9.4.0",
|
|
62
|
+
"dependency-cruiser": "^17.3.5",
|
|
63
|
+
"esbuild": "^0.27.2",
|
|
64
|
+
"eslint": "^9.39.2",
|
|
64
65
|
"eslint-config-prettier": "^10.1.8",
|
|
65
|
-
"eslint-plugin-jsdoc": "^
|
|
66
|
+
"eslint-plugin-jsdoc": "^61.5.0",
|
|
66
67
|
"eslint-plugin-monorepo-cop": "^1.0.2",
|
|
67
|
-
"eslint-plugin-playwright": "^2.
|
|
68
|
+
"eslint-plugin-playwright": "^2.4.0",
|
|
68
69
|
"eslint-plugin-prettier": "^5.5.4",
|
|
69
70
|
"eslint-plugin-require-extensions": "^0.1.3",
|
|
70
71
|
"eslint-plugin-sonarjs": "^3.0.5",
|
|
71
|
-
"eslint-plugin-unicorn": "^
|
|
72
|
+
"eslint-plugin-unicorn": "^62.0.0",
|
|
72
73
|
"istanbul-smart-text-reporter": "^1.1.5",
|
|
73
74
|
"markdown-code-example-inserter": "^3.0.3",
|
|
74
|
-
"npm-check-updates": "^
|
|
75
|
+
"npm-check-updates": "^19.2.0",
|
|
75
76
|
"prettier": "~3.3.3",
|
|
76
77
|
"prettier-plugin-interpolated-html-tags": "^2.0.1",
|
|
77
|
-
"prettier-plugin-jsdoc": "^1.
|
|
78
|
-
"prettier-plugin-multiline-arrays": "^4.
|
|
79
|
-
"prettier-plugin-organize-imports": "^4.
|
|
80
|
-
"prettier-plugin-packagejson": "^2.5.
|
|
78
|
+
"prettier-plugin-jsdoc": "^1.8.0",
|
|
79
|
+
"prettier-plugin-multiline-arrays": "^4.1.3",
|
|
80
|
+
"prettier-plugin-organize-imports": "^4.3.0",
|
|
81
|
+
"prettier-plugin-packagejson": "^2.5.20",
|
|
81
82
|
"prettier-plugin-sort-json": "^4.1.1",
|
|
82
83
|
"prettier-plugin-toml": "^2.0.6",
|
|
83
|
-
"runstorm": "^0.
|
|
84
|
-
"typedoc": "^0.28.
|
|
85
|
-
"typescript": "^5.9.
|
|
86
|
-
"typescript-eslint": "^8.
|
|
87
|
-
"virmator": "^14.0
|
|
84
|
+
"runstorm": "^1.0.0",
|
|
85
|
+
"typedoc": "^0.28.15",
|
|
86
|
+
"typescript": "^5.9.3",
|
|
87
|
+
"typescript-eslint": "^8.50.1",
|
|
88
|
+
"virmator": "^14.4.0"
|
|
88
89
|
},
|
|
89
90
|
"engines": {
|
|
90
91
|
"node": ">=22"
|
package/src/cli/cli.script.ts
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {FlagRequirement, parseArgs} from 'cli-vir';
|
|
2
2
|
import {runDeployVirCli} from './cli.js';
|
|
3
3
|
|
|
4
4
|
await runDeployVirCli(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
parseArgs(
|
|
6
|
+
process.argv,
|
|
7
|
+
{
|
|
8
|
+
y: {
|
|
9
|
+
flag: {
|
|
10
|
+
valueRequirement: FlagRequirement.Blocked,
|
|
11
|
+
},
|
|
12
|
+
description: 'If set, bypass manual validation.',
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
position: {
|
|
16
|
+
rest: true,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
binName: 'deploy-vir',
|
|
22
|
+
importMeta: import.meta,
|
|
23
|
+
},
|
|
24
|
+
),
|
|
10
25
|
process.cwd(),
|
|
11
26
|
);
|
package/src/cli/cli.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {assertWrap} from '@augment-vir/assert';
|
|
2
|
-
import {extractErrorMessage, log} from '@augment-vir/common';
|
|
2
|
+
import {awaitedForEach, extractErrorMessage, log} from '@augment-vir/common';
|
|
3
3
|
import {resolve} from 'node:path';
|
|
4
4
|
import simpleGit from 'simple-git';
|
|
5
5
|
import {type DeployVirConfig} from '../config.js';
|
|
@@ -12,14 +12,31 @@ import {sendNotifications} from '../notifications/send-notifications.js';
|
|
|
12
12
|
*
|
|
13
13
|
* @category Internal
|
|
14
14
|
*/
|
|
15
|
-
export async function runDeployVirCli(
|
|
15
|
+
export async function runDeployVirCli(
|
|
16
|
+
{
|
|
17
|
+
args,
|
|
18
|
+
y,
|
|
19
|
+
}: {
|
|
20
|
+
y: boolean;
|
|
21
|
+
args: ReadonlyArray<string>;
|
|
22
|
+
},
|
|
23
|
+
cwd = process.cwd(),
|
|
24
|
+
) {
|
|
16
25
|
const configPath = assertWrap.isTruthy(args[0], 'Missing config path.');
|
|
17
26
|
const repoName = assertWrap.isTruthy(args[1], 'Missing repo name.');
|
|
18
27
|
const deployName = assertWrap.isTruthy(args[2], 'Missing deploy name.');
|
|
19
28
|
|
|
20
29
|
const config = (await import(resolve(cwd, configPath))).default;
|
|
21
30
|
|
|
22
|
-
await runDeployVir(
|
|
31
|
+
await runDeployVir(
|
|
32
|
+
{
|
|
33
|
+
cwd,
|
|
34
|
+
deployName,
|
|
35
|
+
repoName,
|
|
36
|
+
bypassConfirmation: y,
|
|
37
|
+
},
|
|
38
|
+
config,
|
|
39
|
+
);
|
|
23
40
|
}
|
|
24
41
|
|
|
25
42
|
/**
|
|
@@ -33,6 +50,8 @@ export type DeployVirArgs = {
|
|
|
33
50
|
/** This much match a deploy name in your config. */
|
|
34
51
|
deployName: string;
|
|
35
52
|
cwd: string;
|
|
53
|
+
/** If true, manual confirmation is not required. */
|
|
54
|
+
bypassConfirmation: boolean;
|
|
36
55
|
};
|
|
37
56
|
|
|
38
57
|
/**
|
|
@@ -62,13 +81,22 @@ export async function runDeployVir(
|
|
|
62
81
|
const remoteName = await getGitRemoteName(git, repoConfig);
|
|
63
82
|
log.faint(`Remote: ${remoteName}`);
|
|
64
83
|
|
|
65
|
-
const
|
|
84
|
+
const deployResults = await pushDeploy(
|
|
85
|
+
git,
|
|
86
|
+
branchConfig,
|
|
87
|
+
remoteName,
|
|
88
|
+
args.bypassConfirmation,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const notifications = config.notifications;
|
|
66
92
|
|
|
67
|
-
if (
|
|
68
|
-
await
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
93
|
+
if (notifications?.length) {
|
|
94
|
+
await awaitedForEach(deployResults, async (deployResult) => {
|
|
95
|
+
await sendNotifications(notifications, {
|
|
96
|
+
branchConfig,
|
|
97
|
+
deployResult,
|
|
98
|
+
repoConfig,
|
|
99
|
+
});
|
|
72
100
|
});
|
|
73
101
|
}
|
|
74
102
|
} catch (error) {
|
package/src/config.ts
CHANGED
package/src/git.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {assert, assertWrap, check} from '@augment-vir/assert';
|
|
2
|
-
import {log, logColors} from '@augment-vir/common';
|
|
2
|
+
import {awaitedBlockingMap, log, logColors} from '@augment-vir/common';
|
|
3
3
|
import {confirm} from '@inquirer/prompts';
|
|
4
4
|
import {type DefaultLogFields, type ListLogLine, type SimpleGit} from 'simple-git';
|
|
5
5
|
import {type DeployVirBranchConfig, type DeployVirRepoConfig} from './config.js';
|
|
@@ -88,107 +88,112 @@ export type DeployResult = {
|
|
|
88
88
|
*/
|
|
89
89
|
export async function pushDeploy(
|
|
90
90
|
git: Readonly<SimpleGit>,
|
|
91
|
-
{deployName,
|
|
91
|
+
{deployName, branches}: Readonly<DeployVirBranchConfig>,
|
|
92
92
|
remoteName: string,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
bypassConfirmation = false,
|
|
94
|
+
): Promise<DeployResult[]> {
|
|
95
|
+
return await awaitedBlockingMap(branches, async ({fromBranch, toBranch}) => {
|
|
96
|
+
assert.isTruthy(fromBranch, `Deploy '${deployName}' fromBranch cannot be empty.`);
|
|
97
|
+
assert.isTruthy(toBranch, `Deploy '${deployName}' toBranch cannot be empty.`);
|
|
98
|
+
assert.isTruthy(remoteName, 'Remote name cannot be empty.');
|
|
99
|
+
|
|
100
|
+
log.faint(`Pushing ${remoteName}/${fromBranch} to ${toBranch}`);
|
|
101
|
+
|
|
102
|
+
await git.fetch(remoteName, fromBranch);
|
|
103
|
+
await git.fetch(remoteName, toBranch);
|
|
104
|
+
|
|
105
|
+
const pushString = `${remoteName}/${fromBranch}:${toBranch}`;
|
|
106
|
+
|
|
107
|
+
// Get the current commit on the target branch before pushing
|
|
108
|
+
const beforeCommit = assertWrap.isDefined(
|
|
109
|
+
(
|
|
110
|
+
await git.log([
|
|
111
|
+
`${remoteName}/${toBranch}`,
|
|
112
|
+
'-1',
|
|
113
|
+
])
|
|
114
|
+
).latest,
|
|
115
|
+
`Failed to get current commit for ${remoteName}/${toBranch}`,
|
|
116
|
+
);
|
|
102
117
|
|
|
103
|
-
|
|
118
|
+
const afterCommit = assertWrap.isDefined(
|
|
119
|
+
(
|
|
120
|
+
await git.log([
|
|
121
|
+
`${remoteName}/${fromBranch}`,
|
|
122
|
+
'-1',
|
|
123
|
+
])
|
|
124
|
+
).latest,
|
|
125
|
+
`Failed to get current commit for ${remoteName}/${fromBranch}`,
|
|
126
|
+
);
|
|
104
127
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
(
|
|
128
|
+
/** Get commits that are on {@link toBranch} but not on {@link fromBranch}. */
|
|
129
|
+
const commitsAhead = (
|
|
108
130
|
await git.log([
|
|
109
|
-
`${remoteName}/${toBranch}`,
|
|
110
|
-
'-1',
|
|
131
|
+
`${remoteName}/${toBranch}..${remoteName}/${fromBranch}`,
|
|
111
132
|
])
|
|
112
|
-
).
|
|
113
|
-
|
|
114
|
-
|
|
133
|
+
).all;
|
|
134
|
+
|
|
135
|
+
if (!commitsAhead.length) {
|
|
136
|
+
throw new KnownError('No commit diff: nothing to deploy!');
|
|
137
|
+
}
|
|
115
138
|
|
|
116
|
-
|
|
117
|
-
(
|
|
139
|
+
/** Get commits that are on {@link toBranch} but not on {@link fromBranch}. */
|
|
140
|
+
const commitsBehind = (
|
|
118
141
|
await git.log([
|
|
119
|
-
`${remoteName}/${fromBranch}`,
|
|
120
|
-
'-1',
|
|
142
|
+
`${remoteName}/${fromBranch}..${remoteName}/${toBranch}`,
|
|
121
143
|
])
|
|
122
|
-
).
|
|
123
|
-
`Failed to get current commit for ${remoteName}/${fromBranch}`,
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
/** Get commits that are on {@link toBranch} but not on {@link fromBranch}. */
|
|
127
|
-
const commitsAhead = (
|
|
128
|
-
await git.log([
|
|
129
|
-
`${remoteName}/${toBranch}..${remoteName}/${fromBranch}`,
|
|
130
|
-
])
|
|
131
|
-
).all;
|
|
132
|
-
|
|
133
|
-
if (!commitsAhead.length) {
|
|
134
|
-
throw new KnownError('No commit diff: nothing to deploy!');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Get commits that are on {@link toBranch} but not on {@link fromBranch}. */
|
|
138
|
-
const commitsBehind = (
|
|
139
|
-
await git.log([
|
|
140
|
-
`${remoteName}/${fromBranch}..${remoteName}/${toBranch}`,
|
|
141
|
-
])
|
|
142
|
-
).all;
|
|
144
|
+
).all;
|
|
143
145
|
|
|
144
|
-
|
|
146
|
+
const requiresForcePush = commitsBehind.length > 0;
|
|
145
147
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
);
|
|
149
|
-
commitsAhead.forEach((commit, index) => {
|
|
150
|
-
log.faint(
|
|
151
|
-
` ${index + 1}. ${commit.hash.slice(0, 7)} (${commit.author_name}) - ${commit.message}`,
|
|
148
|
+
log.info(
|
|
149
|
+
`\n${requiresForcePush ? 'Only on' : 'Releasing from'} ${logColors.bold}${fromBranch}${logColors.reset}:`,
|
|
152
150
|
);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (requiresForcePush) {
|
|
156
|
-
log.info(`\nOnly on ${logColors.bold}${toBranch}${logColors.reset}:`);
|
|
157
|
-
commitsBehind.forEach((commit, index) => {
|
|
151
|
+
commitsAhead.forEach((commit, index) => {
|
|
158
152
|
log.faint(
|
|
159
153
|
` ${index + 1}. ${commit.hash.slice(0, 7)} (${commit.author_name}) - ${commit.message}`,
|
|
160
154
|
);
|
|
161
155
|
});
|
|
162
|
-
}
|
|
163
156
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
if (requiresForcePush) {
|
|
158
|
+
log.info(`\nOnly on ${logColors.bold}${toBranch}${logColors.reset}:`);
|
|
159
|
+
commitsBehind.forEach((commit, index) => {
|
|
160
|
+
log.faint(
|
|
161
|
+
` ${index + 1}. ${commit.hash.slice(0, 7)} (${commit.author_name}) - ${commit.message}`,
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const shouldPush =
|
|
167
|
+
bypassConfirmation ||
|
|
168
|
+
(await confirm({
|
|
169
|
+
message: requiresForcePush
|
|
170
|
+
? `\n${logColors.warning}Do you want to force push ${logColors.bold}${fromBranch}${logColors.normalWeight} to ${logColors.bold}${toBranch}${logColors.normalWeight}?\n\nThis will overwrite the commits only on ${logColors.bold}${toBranch}${logColors.normalWeight}.${logColors.reset}?`
|
|
171
|
+
: 'Ready to deploy?',
|
|
172
|
+
default: false,
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
if (!shouldPush) {
|
|
176
|
+
throw new KnownError(`Deploy aborted.`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (requiresForcePush) {
|
|
180
|
+
await git.push(remoteName, pushString, ['--force']);
|
|
181
|
+
log.warning(
|
|
182
|
+
`Force pushed ${logColors.bold}${fromBranch}${logColors.normalWeight} to ${logColors.bold}${toBranch}${logColors.reset}.`,
|
|
183
|
+
);
|
|
184
|
+
} else {
|
|
185
|
+
await git.push(remoteName, pushString);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
deployCommits: {
|
|
190
|
+
deployedCommits: commitsAhead,
|
|
191
|
+
overwrittenCommits: commitsBehind,
|
|
192
|
+
},
|
|
193
|
+
branchStatus: {
|
|
194
|
+
before: beforeCommit,
|
|
195
|
+
after: afterCommit,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
169
198
|
});
|
|
170
|
-
|
|
171
|
-
if (!shouldPush) {
|
|
172
|
-
throw new KnownError(`Deploy aborted.`);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (requiresForcePush) {
|
|
176
|
-
await git.push(remoteName, pushString, ['--force']);
|
|
177
|
-
log.warning(
|
|
178
|
-
`Force pushed ${logColors.bold}${fromBranch}${logColors.normalWeight} to ${logColors.bold}${toBranch}${logColors.reset}.`,
|
|
179
|
-
);
|
|
180
|
-
} else {
|
|
181
|
-
await git.push(remoteName, pushString);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
deployCommits: {
|
|
186
|
-
deployedCommits: commitsAhead,
|
|
187
|
-
overwrittenCommits: commitsBehind,
|
|
188
|
-
},
|
|
189
|
-
branchStatus: {
|
|
190
|
-
before: beforeCommit,
|
|
191
|
-
after: afterCommit,
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
199
|
}
|