deploy-vir 3.1.0 → 3.3.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/dist/cli/cli.js CHANGED
@@ -48,7 +48,7 @@ export async function runDeployVir(args, config) {
48
48
  const branchConfig = assertWrap.isDefined(repoConfig.deploys.find((deploy) => deploy.deployName === args.deployName, `Failed to find a deploy named '${args.deployName}' in repo '${args.repoName}'.`));
49
49
  const remoteName = await getGitRemoteName(git, repoConfig);
50
50
  log.faint(`Remote: ${remoteName}`);
51
- await pushDeploy(git, branchConfig, repoConfig, remoteName, config.notifications, args.bypassConfirmation);
51
+ await pushDeploy(git, branchConfig, repoConfig, remoteName, config.notifications, args.bypassConfirmation, config.hooks);
52
52
  }
53
53
  catch (error) {
54
54
  if (error instanceof KnownError) {
package/dist/config.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
2
+ import { type DeployResult } from './git.js';
1
3
  /**
2
4
  * The full deploy-vir config object.
3
5
  *
@@ -6,7 +8,46 @@
6
8
  export type DeployVirConfig = {
7
9
  repos: DeployVirRepoConfig[];
8
10
  notifications?: NotificationConfig[] | undefined;
11
+ hooks?: DeployVirHooks | undefined;
9
12
  };
13
+ /**
14
+ * Hooks to run at various points during a deploy.
15
+ *
16
+ * @category Config
17
+ */
18
+ export type DeployVirHooks = {
19
+ /**
20
+ * Runs after the user accepts (or bypasses confirmation for) a deploy and the push has
21
+ * completed successfully. Fires once per branch deploy. May optionally return values that
22
+ * modify the resulting notification message.
23
+ */
24
+ postAccept?: ((params: Readonly<PostAcceptHookParams>) => MaybePromise<PostAcceptHookResult | void | undefined>) | undefined;
25
+ };
26
+ /**
27
+ * Params passed to a {@link DeployVirHooks.postAccept} hook.
28
+ *
29
+ * @category Config
30
+ */
31
+ export type PostAcceptHookParams = {
32
+ repoConfig: Readonly<DeployVirRepoConfig>;
33
+ branchConfig: Readonly<DeployVirBranchConfig>;
34
+ fromBranch: string;
35
+ toBranch: string;
36
+ remoteName: string;
37
+ deployResult: Readonly<DeployResult>;
38
+ };
39
+ /**
40
+ * Optional values that a {@link DeployVirHooks.postAccept} hook can return to augment the resulting
41
+ * notification message.
42
+ *
43
+ * @category Config
44
+ */
45
+ export type PostAcceptHookResult = PartialWithUndefined<{
46
+ /** Inserted into the notification immediately after the "<X> Pushed" header line. */
47
+ prependToNotification: string;
48
+ /** Appended to the very end of the notification message. */
49
+ appendToNotification: string;
50
+ }>;
10
51
  /**
11
52
  * All available targets for sending notifications to.
12
53
  *
package/dist/git.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type DefaultLogFields, type ListLogLine, type SimpleGit } from 'simple-git';
2
- import { type DeployVirBranchConfig, type DeployVirRepoConfig, type NotificationConfig } from './config.js';
2
+ import { type DeployVirBranchConfig, type DeployVirHooks, type DeployVirRepoConfig, type NotificationConfig } from './config.js';
3
3
  /**
4
4
  * Find the git remote's name, or create a new one.
5
5
  *
@@ -45,4 +45,4 @@ export type DeployResult = {
45
45
  *
46
46
  * @category Internal
47
47
  */
48
- export declare function pushDeploy(git: Readonly<SimpleGit>, branchConfig: Readonly<DeployVirBranchConfig>, repoConfig: Readonly<DeployVirRepoConfig>, remoteName: string, notifications: ReadonlyArray<Readonly<NotificationConfig>> | undefined, bypassConfirmation?: boolean): Promise<DeployResult[]>;
48
+ export declare function pushDeploy(git: Readonly<SimpleGit>, branchConfig: Readonly<DeployVirBranchConfig>, repoConfig: Readonly<DeployVirRepoConfig>, remoteName: string, notifications: ReadonlyArray<Readonly<NotificationConfig>> | undefined, bypassConfirmation: boolean | undefined, hooks: Readonly<DeployVirHooks> | undefined): Promise<DeployResult[]>;
package/dist/git.js CHANGED
@@ -31,7 +31,7 @@ export async function getGitRemoteName(git, repoConfig) {
31
31
  *
32
32
  * @category Internal
33
33
  */
34
- export async function pushDeploy(git, branchConfig, repoConfig, remoteName, notifications, bypassConfirmation = false) {
34
+ export async function pushDeploy(git, branchConfig, repoConfig, remoteName, notifications, bypassConfirmation = false, hooks) {
35
35
  const { deployName, branches } = branchConfig;
36
36
  return await awaitedBlockingMap(branches, async ({ fromBranch, toBranch, enableNotifications }) => {
37
37
  assert.isTruthy(fromBranch, `Deploy '${deployName}' fromBranch cannot be empty.`);
@@ -100,6 +100,14 @@ export async function pushDeploy(git, branchConfig, repoConfig, remoteName, noti
100
100
  after: afterCommit,
101
101
  },
102
102
  };
103
+ const hookResult = (await hooks?.postAccept?.({
104
+ branchConfig,
105
+ deployResult,
106
+ fromBranch,
107
+ remoteName,
108
+ repoConfig,
109
+ toBranch,
110
+ })) || undefined;
103
111
  const resolvedNotifications = resolveNotifications({
104
112
  branchConfig,
105
113
  enableNotifications,
@@ -110,6 +118,7 @@ export async function pushDeploy(git, branchConfig, repoConfig, remoteName, noti
110
118
  await sendNotifications(resolvedNotifications, {
111
119
  branchConfig,
112
120
  deployResult,
121
+ hookResult: hookResult || undefined,
113
122
  repoConfig,
114
123
  });
115
124
  }
@@ -1,4 +1,4 @@
1
- import { type DeployVirBranchConfig, type DeployVirRepoConfig, type NotificationConfig, type NotificationTarget } from '../config.js';
1
+ import { type DeployVirBranchConfig, type DeployVirRepoConfig, type NotificationConfig, type NotificationTarget, type PostAcceptHookResult } from '../config.js';
2
2
  import { type DeployResult } from '../git.js';
3
3
  /**
4
4
  * Params for sending a notification.
@@ -12,4 +12,5 @@ export type NotificationParams<Target extends NotificationTarget = NotificationT
12
12
  notification: Readonly<Extract<NotificationConfig, {
13
13
  target: Target;
14
14
  }>>;
15
+ hookResult?: Readonly<PostAcceptHookResult> | undefined;
15
16
  };
@@ -5,4 +5,4 @@ export { type ChatPostMessageArguments } from '@slack/web-api';
5
5
  *
6
6
  * @category Internal
7
7
  */
8
- export declare function sendNotificationToSlack({ deployResult: { deployCommits: { deployedCommits, overwrittenCommits }, branchStatus: { after, before }, toBranchName, }, notification, repoConfig, }: Readonly<NotificationParams>): Promise<void>;
8
+ export declare function sendNotificationToSlack({ deployResult: { deployCommits: { deployedCommits, overwrittenCommits }, branchStatus: { after, before }, toBranchName, }, hookResult, notification, repoConfig, }: Readonly<NotificationParams>): Promise<void>;
@@ -41,7 +41,7 @@ function chunkSectionLines(lines) {
41
41
  *
42
42
  * @category Internal
43
43
  */
44
- export async function sendNotificationToSlack({ deployResult: { deployCommits: { deployedCommits, overwrittenCommits }, branchStatus: { after, before }, toBranchName, }, notification, repoConfig, }) {
44
+ export async function sendNotificationToSlack({ deployResult: { deployCommits: { deployedCommits, overwrittenCommits }, branchStatus: { after, before }, toBranchName, }, hookResult, notification, repoConfig, }) {
45
45
  const deployedCommitBullets = deployedCommits.map((commit) => formatCommit(repoConfig.commitBaseUrl, commit));
46
46
  const overwrittenCommitBullets = overwrittenCommits.map((commit) => formatCommit(repoConfig.commitBaseUrl, commit));
47
47
  const deployedCommitBulletLines = deployedCommitBullets.map((bullet) => `- ${bullet}`);
@@ -62,6 +62,17 @@ export async function sendNotificationToSlack({ deployResult: { deployCommits: {
62
62
  text: `*${setFirstLetterCasing(repoConfig.name, StringCase.Upper)} ${setFirstLetterCasing(toBranchName, StringCase.Upper)}* Pushed`,
63
63
  },
64
64
  },
65
+ ...(hookResult?.prependToNotification
66
+ ? [
67
+ {
68
+ type: 'section',
69
+ text: {
70
+ type: 'mrkdwn',
71
+ text: hookResult.prependToNotification,
72
+ },
73
+ },
74
+ ]
75
+ : []),
65
76
  {
66
77
  type: 'context',
67
78
  elements: [
@@ -107,6 +118,17 @@ export async function sendNotificationToSlack({ deployResult: { deployCommits: {
107
118
  }),
108
119
  ]
109
120
  : []),
121
+ ...(hookResult?.appendToNotification
122
+ ? [
123
+ {
124
+ type: 'section',
125
+ text: {
126
+ type: 'mrkdwn',
127
+ text: hookResult.appendToNotification,
128
+ },
129
+ },
130
+ ]
131
+ : []),
110
132
  ];
111
133
  await sendSlackMessage(notification, {
112
134
  attachments: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deploy-vir",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Deploy a branch to another branch with notifications.",
5
5
  "keywords": [
6
6
  "deploy",
@@ -41,51 +41,51 @@
41
41
  "test:update": "npm run test update"
42
42
  },
43
43
  "dependencies": {
44
- "@augment-vir/assert": "^31.68.2",
45
- "@augment-vir/common": "^31.68.2",
46
- "@augment-vir/node": "^31.68.2",
47
- "@inquirer/prompts": "^8.3.2",
48
- "@slack/web-api": "^7.15.0",
44
+ "@augment-vir/assert": "^31.68.4",
45
+ "@augment-vir/common": "^31.68.4",
46
+ "@augment-vir/node": "^31.68.4",
47
+ "@inquirer/prompts": "^8.4.2",
48
+ "@slack/web-api": "^7.15.1",
49
49
  "cli-vir": "^1.0.3",
50
- "simple-git": "^3.33.0",
50
+ "simple-git": "^3.36.0",
51
51
  "tsx": "^4.21.0",
52
- "url-vir": "^2.1.7"
52
+ "url-vir": "^2.1.8"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@eslint/eslintrc": "^3.3.5",
56
56
  "@eslint/js": "^9.39.2",
57
57
  "@stylistic/eslint-plugin": "^5.10.0",
58
58
  "@stylistic/eslint-plugin-ts": "^4.4.1",
59
- "@types/node": "^25.5.0",
60
- "@typescript-eslint/eslint-plugin": "^8.57.1",
61
- "cspell": "^9.7.0",
62
- "dependency-cruiser": "^17.3.9",
63
- "esbuild": "^0.27.4",
59
+ "@types/node": "^25.6.0",
60
+ "@typescript-eslint/eslint-plugin": "^8.59.0",
61
+ "cspell": "^10.0.0",
62
+ "dependency-cruiser": "^17.3.10",
63
+ "esbuild": "^0.28.0",
64
64
  "eslint": "^9.39.2",
65
65
  "eslint-config-prettier": "^10.1.8",
66
- "eslint-plugin-jsdoc": "^62.8.0",
66
+ "eslint-plugin-jsdoc": "^62.9.0",
67
67
  "eslint-plugin-monorepo-cop": "^1.0.2",
68
- "eslint-plugin-playwright": "^2.9.0",
68
+ "eslint-plugin-playwright": "^2.10.2",
69
69
  "eslint-plugin-prettier": "^5.5.5",
70
70
  "eslint-plugin-require-extensions": "^0.1.3",
71
- "eslint-plugin-sonarjs": "^4.0.2",
72
- "eslint-plugin-unicorn": "^63.0.0",
71
+ "eslint-plugin-sonarjs": "^4.0.3",
72
+ "eslint-plugin-unicorn": "^64.0.0",
73
73
  "istanbul-smart-text-reporter": "^1.1.5",
74
- "markdown-code-example-inserter": "^3.0.3",
75
- "npm-check-updates": "^19.6.5",
74
+ "markdown-code-example-inserter": "^3.0.5",
75
+ "npm-check-updates": "^21.0.3",
76
76
  "prettier": "~3.3.3",
77
77
  "prettier-plugin-interpolated-html-tags": "^2.0.1",
78
78
  "prettier-plugin-jsdoc": "^1.8.0",
79
- "prettier-plugin-multiline-arrays": "^4.1.5",
79
+ "prettier-plugin-multiline-arrays": "^4.1.7",
80
80
  "prettier-plugin-organize-imports": "^4.3.0",
81
81
  "prettier-plugin-packagejson": "^3.0.2",
82
82
  "prettier-plugin-sort-json": "^4.2.0",
83
83
  "prettier-plugin-toml": "^2.0.6",
84
84
  "runstorm": "^1.0.0",
85
- "typedoc": "^0.28.17",
85
+ "typedoc": "^0.28.19",
86
86
  "typescript": "^5.9.3",
87
87
  "typescript-eslint": "^8.57.1",
88
- "virmator": "^14.9.1"
88
+ "virmator": "^14.12.2"
89
89
  },
90
90
  "engines": {
91
91
  "node": ">=22"
package/src/cli/cli.ts CHANGED
@@ -112,6 +112,7 @@ export async function runDeployVir(
112
112
  remoteName,
113
113
  config.notifications,
114
114
  args.bypassConfirmation,
115
+ config.hooks,
115
116
  );
116
117
  } catch (error) {
117
118
  if (error instanceof KnownError) {
package/src/config.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import {type MaybePromise, type PartialWithUndefined} from '@augment-vir/common';
2
+ import {type DeployResult} from './git.js';
3
+
1
4
  /**
2
5
  * The full deploy-vir config object.
3
6
  *
@@ -6,8 +9,54 @@
6
9
  export type DeployVirConfig = {
7
10
  repos: DeployVirRepoConfig[];
8
11
  notifications?: NotificationConfig[] | undefined;
12
+ hooks?: DeployVirHooks | undefined;
13
+ };
14
+
15
+ /**
16
+ * Hooks to run at various points during a deploy.
17
+ *
18
+ * @category Config
19
+ */
20
+ export type DeployVirHooks = {
21
+ /**
22
+ * Runs after the user accepts (or bypasses confirmation for) a deploy and the push has
23
+ * completed successfully. Fires once per branch deploy. May optionally return values that
24
+ * modify the resulting notification message.
25
+ */
26
+ postAccept?:
27
+ | ((
28
+ params: Readonly<PostAcceptHookParams>,
29
+ ) => MaybePromise<PostAcceptHookResult | void | undefined>)
30
+ | undefined;
31
+ };
32
+
33
+ /**
34
+ * Params passed to a {@link DeployVirHooks.postAccept} hook.
35
+ *
36
+ * @category Config
37
+ */
38
+ export type PostAcceptHookParams = {
39
+ repoConfig: Readonly<DeployVirRepoConfig>;
40
+ branchConfig: Readonly<DeployVirBranchConfig>;
41
+ fromBranch: string;
42
+ toBranch: string;
43
+ remoteName: string;
44
+ deployResult: Readonly<DeployResult>;
9
45
  };
10
46
 
47
+ /**
48
+ * Optional values that a {@link DeployVirHooks.postAccept} hook can return to augment the resulting
49
+ * notification message.
50
+ *
51
+ * @category Config
52
+ */
53
+ export type PostAcceptHookResult = PartialWithUndefined<{
54
+ /** Inserted into the notification immediately after the "<X> Pushed" header line. */
55
+ prependToNotification: string;
56
+ /** Appended to the very end of the notification message. */
57
+ appendToNotification: string;
58
+ }>;
59
+
11
60
  /**
12
61
  * All available targets for sending notifications to.
13
62
  *
package/src/git.ts CHANGED
@@ -5,6 +5,7 @@ import {type DefaultLogFields, type ListLogLine, type SimpleGit} from 'simple-gi
5
5
  import {
6
6
  type DeployNotificationConfig,
7
7
  type DeployVirBranchConfig,
8
+ type DeployVirHooks,
8
9
  type DeployVirRepoConfig,
9
10
  type NotificationConfig,
10
11
  } from './config.js';
@@ -100,6 +101,7 @@ export async function pushDeploy(
100
101
  remoteName: string,
101
102
  notifications: ReadonlyArray<Readonly<NotificationConfig>> | undefined,
102
103
  bypassConfirmation = false,
104
+ hooks: Readonly<DeployVirHooks> | undefined,
103
105
  ): Promise<DeployResult[]> {
104
106
  const {deployName, branches} = branchConfig;
105
107
  return await awaitedBlockingMap(
@@ -209,6 +211,16 @@ export async function pushDeploy(
209
211
  },
210
212
  };
211
213
 
214
+ const hookResult =
215
+ (await hooks?.postAccept?.({
216
+ branchConfig,
217
+ deployResult,
218
+ fromBranch,
219
+ remoteName,
220
+ repoConfig,
221
+ toBranch,
222
+ })) || undefined;
223
+
212
224
  const resolvedNotifications = resolveNotifications({
213
225
  branchConfig,
214
226
  enableNotifications,
@@ -220,6 +232,7 @@ export async function pushDeploy(
220
232
  await sendNotifications(resolvedNotifications, {
221
233
  branchConfig,
222
234
  deployResult,
235
+ hookResult: hookResult || undefined,
223
236
  repoConfig,
224
237
  });
225
238
  }
@@ -3,6 +3,7 @@ import {
3
3
  type DeployVirRepoConfig,
4
4
  type NotificationConfig,
5
5
  type NotificationTarget,
6
+ type PostAcceptHookResult,
6
7
  } from '../config.js';
7
8
  import {type DeployResult} from '../git.js';
8
9
 
@@ -16,4 +17,5 @@ export type NotificationParams<Target extends NotificationTarget = NotificationT
16
17
  branchConfig: Readonly<DeployVirBranchConfig>;
17
18
  repoConfig: Readonly<Pick<DeployVirRepoConfig, 'commitBaseUrl' | 'name'>>;
18
19
  notification: Readonly<Extract<NotificationConfig, {target: Target}>>;
20
+ hookResult?: Readonly<PostAcceptHookResult> | undefined;
19
21
  };
@@ -61,6 +61,7 @@ export async function sendNotificationToSlack({
61
61
  branchStatus: {after, before},
62
62
  toBranchName,
63
63
  },
64
+ hookResult,
64
65
  notification,
65
66
  repoConfig,
66
67
  }: Readonly<NotificationParams>) {
@@ -92,6 +93,17 @@ export async function sendNotificationToSlack({
92
93
  text: `*${setFirstLetterCasing(repoConfig.name, StringCase.Upper)} ${setFirstLetterCasing(toBranchName, StringCase.Upper)}* Pushed`,
93
94
  },
94
95
  },
96
+ ...(hookResult?.prependToNotification
97
+ ? ([
98
+ {
99
+ type: 'section',
100
+ text: {
101
+ type: 'mrkdwn',
102
+ text: hookResult.prependToNotification,
103
+ },
104
+ },
105
+ ] satisfies KnownBlock[])
106
+ : []),
95
107
  {
96
108
  type: 'context',
97
109
  elements: [
@@ -138,6 +150,17 @@ export async function sendNotificationToSlack({
138
150
  }),
139
151
  ] satisfies KnownBlock[])
140
152
  : []),
153
+ ...(hookResult?.appendToNotification
154
+ ? ([
155
+ {
156
+ type: 'section',
157
+ text: {
158
+ type: 'mrkdwn',
159
+ text: hookResult.appendToNotification,
160
+ },
161
+ },
162
+ ] satisfies KnownBlock[])
163
+ : []),
141
164
  ];
142
165
 
143
166
  await sendSlackMessage(notification, {