Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

remove-stale-staging-apps.js 3.8 KB

You have to be logged in to leave a comment. Sign In
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
  1. #!/usr/bin/env node
  2. // [start-readme]
  3. //
  4. // This script removes all stale Heroku staging apps that outlasted the closure
  5. // of their corresponding pull requests, or correspond to spammy pull requests.
  6. //
  7. // [end-readme]
  8. require('dotenv').config()
  9. const { chain } = require('lodash')
  10. const chalk = require('chalk')
  11. const Heroku = require('heroku-client')
  12. const getOctokit = require('./helpers/github')
  13. // Check for required Heroku API token
  14. if (!process.env.HEROKU_API_TOKEN) {
  15. console.error('Error! You must have a HEROKU_API_TOKEN environment variable for deployer-level access.')
  16. process.exit(1)
  17. }
  18. // Check for required GitHub PAT
  19. if (!process.env.GITHUB_TOKEN) {
  20. console.error('Error! You must have a GITHUB_TOKEN environment variable for repo access.')
  21. process.exit(1)
  22. }
  23. const heroku = new Heroku({ token: process.env.HEROKU_API_TOKEN })
  24. // This helper uses the `GITHUB_TOKEN` implicitly
  25. const octokit = getOctokit()
  26. const protectedAppNames = ['help-docs', 'help-docs-deployer']
  27. main()
  28. async function main () {
  29. const apps = chain(await heroku.get('/apps'))
  30. .orderBy('name')
  31. .value()
  32. const prInfoMatch = /^(?<repo>docs(?:-internal)?)-(?<pullNumber>\d+)--.*$/
  33. const appsPlusPullIds = apps
  34. .map(app => {
  35. const match = prInfoMatch.exec(app.name)
  36. const { repo, pullNumber } = ((match || {}).groups || {})
  37. return {
  38. app,
  39. repo,
  40. pullNumber: parseInt(pullNumber, 10) || null
  41. }
  42. })
  43. const appsWithPullIds = appsPlusPullIds.filter(appi => appi.repo && appi.pullNumber > 0)
  44. const nonMatchingAppNames = appsPlusPullIds
  45. .filter(appi => !(appi.repo && appi.pullNumber > 0))
  46. .map(appi => appi.app.name)
  47. .filter(name => !protectedAppNames.includes(name))
  48. let staleCount = 0
  49. let spammyCount = 0
  50. for (const awpi of appsWithPullIds) {
  51. const { isStale, isSpammy } = await assessPullRequest(awpi.repo, awpi.pullNumber)
  52. if (isSpammy) spammyCount++
  53. if (isStale) {
  54. staleCount++
  55. await deleteHerokuApp(awpi.app.name)
  56. }
  57. }
  58. const matchingCount = appsWithPullIds.length
  59. const counts = {
  60. total: matchingCount,
  61. alive: matchingCount - staleCount,
  62. stale: {
  63. total: staleCount,
  64. spammy: spammyCount,
  65. closed: staleCount - spammyCount
  66. }
  67. }
  68. console.log(`🧮 COUNTS!\n${JSON.stringify(counts, null, 2)}`)
  69. const nonMatchingCount = nonMatchingAppNames.length
  70. if (nonMatchingCount > 0) {
  71. console.log('⚠️ 👀', chalk.yellow(`Non-matching app names (${nonMatchingCount}):\n - ${nonMatchingAppNames.join('\n - ')}`))
  72. }
  73. }
  74. function displayParams (params) {
  75. const { owner, repo, pull_number: pullNumber } = params
  76. return `${owner}/${repo}#${pullNumber}`
  77. }
  78. async function assessPullRequest (repo, pullNumber) {
  79. const params = {
  80. owner: 'github',
  81. repo: repo,
  82. pull_number: pullNumber
  83. }
  84. let isStale = false
  85. let isSpammy = false
  86. try {
  87. const { data: pullRequest } = await octokit.pulls.get(params)
  88. if (pullRequest && pullRequest.state === 'closed') {
  89. isStale = true
  90. console.debug(chalk.green(`STALE: ${displayParams(params)} is closed`))
  91. }
  92. } catch (error) {
  93. // Using a standard GitHub PAT, PRs from spammy users will respond as 404
  94. if (error.status === 404) {
  95. isStale = true
  96. isSpammy = true
  97. console.debug(chalk.yellow(`STALE: ${displayParams(params)} is spammy or deleted`))
  98. } else {
  99. console.debug(chalk.red(`ERROR: ${displayParams(params)} - ${error.message}`))
  100. }
  101. }
  102. return { isStale, isSpammy }
  103. }
  104. async function deleteHerokuApp (appName) {
  105. try {
  106. await heroku.delete(`/apps/${appName}`)
  107. console.log('✅', chalk.green(`Removed stale app "${appName}"`))
  108. } catch (error) {
  109. console.log('❌', chalk.red(`ERROR: Failed to remove stale app "${appName}" - ${error.message}`))
  110. }
  111. }
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...