-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
151 lines (131 loc) · 4.83 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/**
* Copyright Zendesk, Inc.
*
* Use of this source code is governed under the Apache License, Version 2.0
* found at http://www.apache.org/licenses/LICENSE-2.0.
*/
import { commit as getCommit, repository as getRepository, token as getToken } from '../index.js';
import { handleErrorMessage, handleSuccessMessage } from '../../utils/index.js';
import { Command } from 'commander';
import { Octokit } from '@octokit/rest';
import { Ora } from 'ora';
import { execa } from 'execa';
interface IGitHubDeployArgs {
command: (...args: unknown[]) => Promise<string | { url: string; logUrl: string } | undefined>;
path?: string;
production?: boolean;
token?: string;
ref?: string;
message?: string;
spinner?: Ora;
}
/**
* Execute the `github-deploy` command.
*
* @param {function} args.command Deployment command to execute.
* @param {string} [args.path] Path to a git directory.
* @param {boolean} [args.production] Determine whether this is a production deployment.
* @param {string} [args.token] GitHub personal access token.
* @param {string} [args.ref] GitHub ref (commit SHA, branch, tag).
* @param {string} [args.message] Deployment message.
* @param {Ora} [args.spinner] Terminal spinner.
*
* @returns {Promise<string>} The result of the deployment command.
*/
export const execute = async (args: IGitHubDeployArgs): Promise<string | undefined> => {
let retVal: string | undefined;
try {
const auth = args.token || (await getToken(args.spinner));
const github = new Octokit({ auth });
const repository = (await getRepository(args.path, args.spinner))!;
const ref = (args.ref || (await getCommit({ ...args })))!;
const environment = args.production ? 'production' : 'staging';
/* https://octokit.github.io/rest.js/v17#repos-create-deployment */
const deployment = await github.repos.createDeployment({
owner: repository.owner,
repo: repository.repo,
ref,
environment,
description: args.message,
auto_merge: false,
required_contexts: [],
transient_environment: environment !== 'production'
});
let result;
let error;
try {
result = await args.command();
} catch (err: unknown) {
error = err;
}
/* https://octokit.github.io/rest.js/v17#repos-create-deployment-status */
await github.repos.createDeploymentStatus({
owner: repository.owner,
repo: repository.repo,
/* eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion */
deployment_id: (deployment.data as any).id, // HACK for types broken in oktokit 17.9.1
state: error ? 'error' : 'success',
environment_url: typeof result === 'object' ? result.url : result,
log_url: typeof result === 'object' ? result.logUrl : undefined,
environment,
description: args.message
});
if (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
throw error as Error;
}
retVal = typeof result === 'object' ? result.url : result;
} catch (error: unknown) {
handleErrorMessage(error, 'github-deploy', args.spinner);
throw error;
}
return retVal;
};
export default (spinner: Ora): Command => {
const command = new Command('github-deploy');
return command
.description('execute a GitHub deployment based on a <command> that outputs a URL')
.argument('<command>', 'deployment command to execute')
.argument('[args...]', 'deployment command arguments')
.option('-p, --production', 'production deployment')
.option('-d, --path <path>', 'git directory')
.option('-c, --commit <commit>', 'GitHub commit SHA')
.option('-t, --token <token>', 'access token')
.option('-m, --message <message>', 'deployment message')
.action(async (subcommand: string, args: string[]) => {
try {
spinner.start();
const options = command.opts();
const url = await execute({
command: async () => {
let retVal: string | undefined;
try {
const result = await execa(subcommand, args);
retVal = result.stdout.toString();
} catch (error: unknown) {
handleErrorMessage(error, subcommand, spinner);
throw error;
}
return retVal;
},
path: options.path,
production: options.production,
token: options.token,
ref: options.commit,
message: options.message,
spinner
});
if (url) {
handleSuccessMessage(url, spinner);
} else {
throw new Error();
}
} catch {
const cmd = args.length > 0 ? `${subcommand} ${args.join(' ')}` : subcommand;
spinner.fail(`Unable to deploy '${cmd}'`);
process.exitCode = 1;
} finally {
spinner.stop();
}
});
};