forked from actions/setup-node
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.ts
216 lines (182 loc) · 7.24 KB
/
main.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import { info, setFailed, getInput as getInputCore, warning, debug, startGroup, endGroup, error } from '@actions/core'
import axios from 'axios'
import path from 'path'
import simpleGit, { Response } from 'simple-git'
import { Input } from './inputs'
const baseDir = path.join(process.cwd(), getInput('cwd') || '')
const git = simpleGit({ baseDir })
console.log(`Running in ${baseDir}`);
(async () => {
await checkInputs().catch(setFailed)
startGroup('Internal logs')
info('> Staging files...')
if (getInput('add')) {
info('> Adding files...')
await add()
} else info('> No files to add.')
if (getInput('remove')) {
info('> Removing files...')
await remove()
} else info('> No files to remove.')
info('> Checking for uncommitted changes in the git working tree...')
const changedFiles = (await git.diffSummary(['--cached'])).files.length
if (changedFiles > 0) {
info(`> Found ${changedFiles} changed files.`)
await git
.addConfig('user.email', getInput('author_email'), undefined, log)
.addConfig('user.name', getInput('author_name'), undefined, log)
debug('> Current git config\n' + JSON.stringify((await git.listConfig()).all, null, 2))
await git.fetch(['--tags', '--force'], log)
info('> Switching/creating branch...')
await git
.checkout(getInput('branch'), undefined, log)
.catch(() => git.checkoutLocalBranch(getInput('branch'), log))
info('> Pulling from remote...')
await git
.fetch(undefined, log)
.pull(undefined, undefined, {
[getInput('pull_strategy')]: null
}, log)
info('> Re-staging files...')
if (getInput('add')) await add({ ignoreErrors: true })
if (getInput('remove')) await remove({ ignoreErrors: true })
info('> Creating commit...')
await git.commit(getInput('message'), undefined, {
'--author': `"${getInput('author_name')} <${getInput('author_email')}>"`,
...(getInput('signoff') ? {
'--signoff': null
} : {})
}, log)
if (getInput('tag')) {
info('> Tagging commit...')
await git.tag(getInput('tag').split(' '), log)
} else info('> No tag info provided.')
info('> Pushing commit to repo...')
await git.push('origin', getInput('branch'), { '--set-upstream': null }, log)
if (getInput('tag')) {
info('> Pushing tags to repo...')
await git.pushTags('origin', (e, d?) => log(undefined, e || d)).catch(() => {
info('> Tag push failed: deleting remote tag and re-pushing...')
return git.push(undefined, undefined, {
'--delete': null,
'origin': null,
[getInput('tag').split(' ').filter(w => !w.startsWith('-'))[0]]: null
}, log)
.pushTags('origin', log)
})
} else info('> No tags to push.')
endGroup()
info('> Task completed.')
} else {
endGroup()
info('> Working tree clean. Nothing to commit.')
}
})().catch(e => {
endGroup()
setFailed(e)
})
async function checkInputs() {
function setInput(input: Input, value: string | undefined) {
if (value) return process.env[`INPUT_${input.toUpperCase()}`] = value
else return delete process.env[`INPUT_${input.toUpperCase()}`]
}
function setDefault(input: Input, value: string) {
if (!getInput(input)) setInput(input, value)
return getInput(input)
}
const eventPath = process.env.GITHUB_EVENT_PATH,
event = eventPath && require(eventPath),
token = process.env.GITHUB_TOKEN,
isPR = process.env.GITHUB_EVENT_NAME?.includes('pull_request'),
sha = (event?.pull_request?.head?.sha || process.env.GITHUB_SHA) as string,
defaultBranch = isPR
? event?.pull_request?.head?.ref as string
: process.env.GITHUB_REF?.substring(11)
// #region GITHUB_TOKEN
if (!token) warning('The GITHUB_TOKEN env variable is missing: the action may not work as expected.')
// #endregion
// #region add, remove
if (!getInput('add') && !getInput('remove'))
throw new Error('Both \'add\' and \'remove\' are empty, the action has nothing to do.')
// #endregion
// #region author_name, author_email
let author = event?.head_commit?.author
if (sha && !author) {
info('> Unable to get commit from workflow event: trying with the GitHub API...')
// https://docs.github.com/en/rest/reference/repos#get-a-commit--code-samples
const url = `https://api.github.com/repos/${process.env.GITHUB_REPOSITORY}/commits/${sha}`,
headers = token ? {
Authorization: `Bearer ${token}`
} : undefined,
commit = (await axios.get(url, { headers }).catch(err => {
startGroup('Request error:')
info(`> Request URL: ${url}\b${err}`)
endGroup()
return undefined
}))?.data
author = commit?.commit?.author
}
if (author) {
setDefault('author_name', author.name)
setDefault('author_email', author.email)
}
if (!getInput('author_name') || !getInput('author_email')) {
const reason = !eventPath
? 'event path'
: isPR
? sha
? 'fetch commit'
: 'find commit sha'
: !event?.head_commit
? 'find commit'
: 'find commit author'
warning(`Unable to fetch author info: couldn't ${reason}.`)
setDefault('author_name', 'Add & Commit Action')
setDefault('author_email', 'actions@github.com')
}
info(`> Using '${getInput('author_name')} <${getInput('author_email')}>' as author.`)
// #endregion
// #region message
setDefault('message', `Commit from GitHub Actions (${process.env.GITHUB_WORKFLOW})`)
info(`> Using "${getInput('message')}" as commit message.`)
// #endregion
// #region branch
const branch = setDefault('branch', defaultBranch || '')
if (isPR) info(`> Running for a PR, the action will use '${branch}' as ref.`)
// #endregion
// #region signoff
if (getInput('signoff')) try {
const parsed = JSON.parse(getInput('signoff'))
if (typeof parsed == 'boolean' && !parsed)
setInput('signoff', undefined)
debug(`Current signoff option: ${getInput('signoff')} (${typeof getInput('signoff')})`)
} catch {
throw new Error(`"${getInput('signoff')}" is not a valid value for the 'signoff' input: only "true" and "false" are allowed.`)
}
// #endregion
}
function getInput(name: Input) {
return getInputCore(name)
}
function log(err: any | Error, data?: any) {
if (data) console.log(data)
if (err) error(err)
}
function add({ logWarning = true, ignoreErrors = false } = {}): Promise<void | Response<void>> | void {
if (getInput('add'))
return git.add(getInput('add').split(' '), (e: any, d?: any) => log(ignoreErrors ? null : e, d)).catch((e: Error) => {
if (ignoreErrors) return
if (e.message.includes('fatal: pathspec') && e.message.includes('did not match any files'))
logWarning && warning('Add command did not match any file.')
else throw e
})
}
function remove({ logWarning = true, ignoreErrors = false } = {}): Promise<void | Response<void>> | void {
if (getInput('remove'))
return git.rm(getInput('remove').split(' '), (e: any, d?: any) => log(ignoreErrors ? null : e, d)).catch((e: Error) => {
if (ignoreErrors) return
if (e.message.includes('fatal: pathspec') && e.message.includes('did not match any files'))
logWarning && warning('Remove command did not match any file.')
else throw e
})
}