This repository was archived by the owner on Jul 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscript.js
164 lines (136 loc) Β· 4.88 KB
/
script.js
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
import {appAuth} from '@stoe/octoherd-script-common'
import {composeCreatePullRequest} from 'octokit-plugin-create-pull-request'
/**
* @param {import('@octoherd/cli').Octokit} octokit
* @param {import('@octoherd/cli').Repository} repository
* @param {object} options
* @param {int} [options.appId=0]
* @param {string} [options.privateKey='']
* @param {boolean} [options.dryRun=false]
*/
export async function script(octokit, repository, {appId = 0, privateKey = '', dryRun = false}) {
const {
archived,
default_branch,
disabled,
fork,
name: repo,
owner: {login: owner},
size,
clone_url: url,
} = repository
// skip archived, disabled, forked and empty repos
if (archived || disabled || fork || size === 0) return
try {
let ok = octokit
if (appId && privateKey) {
try {
ok = await appAuth(repository, appId, privateKey)
octokit.log.info(` π€ authenticated as app`)
} catch (error) {
octokit.log.info({error}, ` β failed to authenticate as app`)
return
}
}
// https://docs.github.com/en/rest/reference/repos#get-repository-content
const {data: paths} = await ok.request('GET /repos/{owner}/{repo}/contents/{path}', {
owner,
repo,
path: '.github/workflows',
})
const actionsMap = new Map()
for (const file of paths) {
const {data} = await ok.request('GET /repos/{owner}/{repo}/contents/{path}', {
owner,
repo,
path: file.path,
})
const content = Buffer.from(data.content, 'base64').toString('utf-8')
// extract action and version
const _actions = content.match(/uses: (?<action>\S+)@(?<version>\S+)/gi)
actionsMap.set(file.path, {content, actions: _actions.map(a => a.split(': ')[1])})
}
const changes = new Map()
let hasChanges = false
for (const [path, tmp] of actionsMap) {
let content = tmp.content
for await (const action of tmp.actions) {
const [name, version] = action.split('@')
const [actionOwner, actionRepo] = name.split('/')
// skip if version is a SHA
if (version.length === 40 || version.length === 64) {
octokit.log.info({change: false}, ` π ${action} is already a SHA`)
continue
}
// skip if internal action
if (name.startsWith('./')) {
octokit.log.info({change: false}, ` π ${action} is an internal action`)
continue
}
// skip if reusable workflow
if (name.endsWith('.yml') || name.endsWith('.yaml')) {
octokit.log.info({change: false}, ` π ${action} is a reusable workflow`)
continue
}
try {
// https://docs.github.com/en/rest/releases/releases#get-the-latest-release
const {data: release} = await ok.request('GET /repos/{owner}/{repo}/releases/latest', {
owner: actionOwner,
repo: actionRepo,
})
// get sha of the latest release tag
const {
data: {
object: {sha},
},
} = await ok.request('GET /repos/{owner}/{repo}/git/ref/tags/{ref}', {
owner: actionOwner,
repo: actionRepo,
ref: release.tag_name,
})
// replace action version with sha
const replace = `${name}@${sha}`
content = content.replace(action, replace)
hasChanges = true
octokit.log.info({change: !dryRun}, ` ${dryRun ? 'π’ dry-run:' : 'π'} replacing ${action} with ${replace}`)
} catch (error) {
octokit.log.info({change: false}, ` π no release found for ${action}`)
continue
}
}
if (hasChanges) changes.set(path, content)
}
const options = {
owner,
repo,
title: 'π actions: Change action versions to SHAs',
head: 'octoherd-script/workflow-action-shas',
base: default_branch,
body: 'This pull request updates workflows with the latest release SHAs',
createWhenEmpty: false,
}
const files = Object.fromEntries(changes)
if (dryRun) {
octokit.log.info({change: false, files: Object.keys(files)}, ` π’ dry-run`)
return
}
const PR = {
...options,
changes: [
{
files,
commit: `π€ actions: Change action versions to SHAs`,
emptyCommit: false,
},
],
}
// open a pull request to replace action version with latest realease sha
const {data: pr} = await composeCreatePullRequest(ok, PR)
octokit.log.info({change: true, pr: pr.html_url}, ` π€ pull request created`)
} catch (error) {
octokit.log.info({change: false}, ` β
no workflows need changing `)
return
}
octokit.log.info(` β
${url}`)
return
}