Skip to content

Commit

Permalink
Merge branch 'github:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
alexis-opolka committed Aug 28, 2023
2 parents 77a6e53 + 8cb5524 commit d600061
Show file tree
Hide file tree
Showing 1,500 changed files with 955,416 additions and 45,181 deletions.
4 changes: 0 additions & 4 deletions .babelrc

This file was deleted.

60 changes: 36 additions & 24 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,56 @@ module.exports = {
es2020: true,
node: true,
},
parser: '@babel/eslint-parser',
extends: ['eslint:recommended', 'standard', 'plugin:import/errors', 'prettier'],
extends: [
"eslint:recommended",
"standard",
"plugin:import/errors",
"prettier",
],
parserOptions: {
ecmaVersion: 11,
requireConfigFile: 'false',
babelOptions: { configFile: './.babelrc' },
sourceType: 'module',
ecmaVersion: 2022,
requireConfigFile: "false",
sourceType: "module",
},
ignorePatterns: ['tmp/*', '!/.*', '/.next/', 'script/bookmarklets/*', 'rest-api-description/'],
ignorePatterns: [
"tmp/*",
"!/.*",
"/.next/",
"script/bookmarklets/*",
"rest-api-description/",
],
rules: {
'import/no-extraneous-dependencies': ['error', { packageDir: '.' }],
"import/no-extraneous-dependencies": ["error", { packageDir: "." }],
},
overrides: [
{
files: ['**/tests/**/*.js'],
files: ["**/tests/**/*.js"],
env: {
jest: true,
},
},
{
files: ['**/*.tsx', '**/*.ts'],
plugins: ['@typescript-eslint', 'jsx-a11y'],
extends: ['plugin:jsx-a11y/recommended'],
parser: '@typescript-eslint/parser',
files: ["**/*.tsx", "**/*.ts"],
plugins: ["@typescript-eslint", "primer-react", "jsx-a11y"],
extends: [
"plugin:primer-react/recommended",
"plugin:jsx-a11y/recommended",
],
parser: "@typescript-eslint/parser",
rules: {
camelcase: 'off',
'no-unused-vars': 'off',
'no-undef': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'jsx-a11y/no-onchange': 'off',
camelcase: "off",
"no-unused-vars": "off",
"no-undef": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-unused-vars": ["error"],
"jsx-a11y/no-onchange": "off",
},
},
],
settings: {
'import/resolver': {
"import/resolver": {
typescript: true,
node: true
}
}
}
node: true,
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@ body:
attributes:
value: |
Once all tasks are completed, please mention `@github/docs-content` for next steps.
/cc @github/partner-engineering for :eyes:.
/cc @github/technology-partnerships-and-engineering for :eyes:.
5 changes: 5 additions & 0 deletions .github/actions-scripts/find-past-built-pr.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ async function main() {
console.log('URL:', issue.html_url)
number = issue.number
if (number) {
// We've found the issue (pull request), but before we accept
// this `number`, check that the issue isn't locked.
if (issue.locked) {
number = ''
}
break
}
}
Expand Down
67 changes: 67 additions & 0 deletions .github/actions-scripts/purge-old-deployment-environments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env node

import assert from 'node:assert/strict'

import { getOctokit } from '@actions/github'

main()
async function main() {
const DRY_RUN = Boolean(JSON.parse(process.env.DRY_RUN || 'false'))
const MAX_DELETIONS = parseInt(JSON.parse(process.env.MAX_DELETIONS || '10'))
const MIN_AGE_DAYS = parseInt(process.env.MIN_AGE_DAYS || '90', 10)

const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
if (!owner || !repo) {
throw new Error('GITHUB_REPOSITORY environment variable not set')
}
const token = process.env.GITHUB_TOKEN
if (!token) {
throw new Error(`GITHUB_TOKEN environment variable not set`)
}
const github = getOctokit(token)

// The sort order is not explicitly listed for this API endpoint.
// In practice it appears to list those that are oldest first.
// But to guarantee that it reaches the oldest, we paginate over
// all of them.
const environments = await github.paginate('GET /repos/{owner}/{repo}/environments', {
owner,
repo,
})

console.log(`Found ${environments.length.toLocaleString()} environments in total`)

let countDeletions = 0
for (const environment of environments) {
const ageDays = (Date.now() - Date.parse(environment.created_at)) / 1000 / 60 / 60 / 24
if (ageDays > MIN_AGE_DAYS) {
console.log(
`Deleting environment ${environment.name} created ${Math.ceil(ageDays)} days ago`,
DRY_RUN ? '(DRY RUN)' : '',
)
if (!DRY_RUN) {
const { status } = await github.request(
'DELETE /repos/{owner}/{repo}/environments/{name}',
{
owner,
repo,
name: environment.name,
},
)
assert(status === 204, `Expected status 204, got ${status}`)
}
countDeletions++
if (MAX_DELETIONS && countDeletions >= MAX_DELETIONS) {
console.log(`Reached max number of deletions: ${MAX_DELETIONS}`)
break
}
} else {
console.log(
`Environment ${environment.name} (${environment.id}) created ${Math.ceil(
ageDays,
)} days ago, is *not* old enough`,
)
}
}
console.log(`Deleted ${countDeletions} environments`, DRY_RUN ? '(DRY RUN)' : '')
}
226 changes: 226 additions & 0 deletions .github/actions-scripts/purge-old-workflow-runs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#!/usr/bin/env node

/**
*
* The only mandatory environment variables for this scrips are:
*
* - GITHUB_TOKEN
* - GITHUB_REPOSITORY (e.g. "github/docs")
*
* To delete old workflows, it first downloads all the workflows.
* The list of workflows is sorted by: A) does the `workflow.path`
* exist in the repo any more, B) each workflow's `updated_at` date.
*
* Then, one workflow at a time, it searches that workflow for runs.
* The search for runs uses a `created` filter that depends on the
* `MIN_AGE_DAYS` environment variable. The default is 90 days.
*
* For every run found, it deletes its logs and its run.
*
* The total number of deletions is limited by the `MAX_DELETIONS`
* environment variable. The default is 100.
* */

import fs from 'fs'
import assert from 'node:assert/strict'

import { getOctokit } from '@actions/github'

main()
async function main() {
const DRY_RUN = Boolean(JSON.parse(process.env.DRY_RUN || 'false'))
const MAX_DELETIONS = parseInt(JSON.parse(process.env.MAX_DELETIONS || '100'))
const MIN_AGE_DAYS = parseInt(process.env.MIN_AGE_DAYS || '90', 10)

const [owner, repo] = (process.env.GITHUB_REPOSITORY || 'github/docs-internal').split('/')
if (!owner || !repo) {
throw new Error('GITHUB_REPOSITORY environment variable not set')
}
const token = process.env.GITHUB_TOKEN
if (!token) {
throw new Error(`GITHUB_TOKEN environment variable not set`)
}
const github = getOctokit(token)

// The sort order is not explicitly listed for this API endpoint.
// In practice it appears to list those that are oldest first.
// But to guarantee that it reaches the oldest, we paginate over
// all of them.
let allWorkflows = []

try {
allWorkflows = await github.paginate('GET /repos/{owner}/{repo}/actions/workflows', {
owner,
repo,
})
} catch (error) {
console.log('Error happened when getting workflows')
console.warn('Status: %O', error.status)
console.warn('Message: %O', error.message)

// Generally, if it fails, it's because of a network error or
// because busy servers. It's not our fault, but considering that
// this script is supposed to run on frequent schedule, we don't
// need to fret. We'll just try again next time.
if (isOperationalError(error.status, error.message)) {
return
} else {
throw error
}
}

const validWorkflows = allWorkflows.filter((w) => !w.path.startsWith('dynamic/'))

const sortByDate = (a, b) => a.updated_at.localeCompare(b.updated_at)
const workflows = [
...validWorkflows.filter((w) => !fs.existsSync(w.path)).sort(sortByDate),
...validWorkflows.filter((w) => fs.existsSync(w.path)).sort(sortByDate),
]

let deletions = 0
for (const workflow of workflows) {
console.log('WORKFLOW', workflow)
console.log(
fs.existsSync(workflow.path)
? `${workflow.path} still exists on disk`
: `${workflow.path} no longer exists on disk`,
)
try {
deletions += await deleteWorkflowRuns(github, owner, repo, workflow, {
dryRun: DRY_RUN,
minAgeDays: MIN_AGE_DAYS,
maxDeletions: MAX_DELETIONS - deletions,
})
} catch (error) {
console.log("Error happened when calling 'deleteWorkflowRuns'")
console.warn('Status: %O', error.status)
console.warn('Message: %O', error.message)

// Generally, if it fails, it's because of a network error or
// because busy servers. It's not our fault, but considering that
// this script is supposed to run on frequent schedule, we don't
// need to fret. We'll just try again next time.
if (isOperationalError(error.status, error.message)) {
break
} else {
throw error
}
}

if (deletions >= MAX_DELETIONS) {
console.log(`Reached max number of deletions: ${MAX_DELETIONS}`)
break
}
}
console.log(`Deleted ${deletions} runs in total`)
}

function isOperationalError(status, message) {
if (status && status >= 500) {
return true
}
if (/Unable to delete logs while the workflow is running/.test(message)) {
return true
}
if (status === 403 && /API rate limit exceeded/.test(message)) {
return true
}

return false
}

async function deleteWorkflowRuns(
github,
owner,
repo,
workflow,
{ dryRun = false, minAgeDays = 100, maxDeletions = 1000 },
) {
// https://docs.github.com/en/search-github/getting-started-with-searching-on-github/understanding-the-search-syntax#query-for-dates
const minCreated = new Date(Date.now() - minAgeDays * 24 * 60 * 60 * 1000)
const minCreatedSearch = `<=${minCreated.toISOString().split('T')[0]}`
// Delete is 10, but max is 100. But if we're only going to delete,
// 30, use 30. And if we're only going to delete 5, use the default
// per_page value of 10.
const perPage = Math.max(100, Math.max(10, maxDeletions))
// We could use github.paginate(...) but given that we can use a
// filter on `created` and we can set a decent `per_page`, there's no
// reason to request data that we're not going to use.
const { data } = await github.request(
'GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs',
{
owner,
repo,
workflow_id: workflow.id,
per_page: perPage,
created: minCreatedSearch,
},
)
const runs = data.workflow_runs
console.log(
`Total runs in workflow "${
workflow.name
}" (${minCreatedSearch}): ${data.total_count.toLocaleString()}`,
)

let deletions = 0
let notDeletions = 0
for (const run of runs) {
const created = new Date(run.created_at)
if (created < minCreated) {
const ageDays = Math.round((Date.now() - created.getTime()) / (24 * 60 * 60 * 1000))
console.log(
'DELETE',
{
id: run.id,
created_at: run.created_at,
name: run.name,
display_title: run.display_title,
},
`${ageDays} days old`,
)

if (!dryRun) {
const { status } = await github.request(
'DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs',
{
owner,
repo,
run_id: run.id,
},
)
assert(status === 204, `Unexpected status deleting logs for run ${run.id}: ${status}`)
}

if (!dryRun) {
const { status } = await github.request(
'DELETE /repos/{owner}/{repo}/actions/runs/{run_id}',
{
owner,
repo,
run_id: run.id,
},
)
assert(status === 204, `Unexpected status deleting logs for run ${run.id}: ${status}`)
}

deletions++
if (maxDeletions && deletions >= maxDeletions) {
console.log(
`Reached max number of deletions (${maxDeletions}) for one workflow: ${workflow.name}`,
)
break
} else {
console.log(`Deleted ${deletions} of ${maxDeletions} runs for workflow: ${workflow.name}`)
}
} else {
notDeletions++
}
}
console.log(`Deleted ${deletions} runs in total for workflow: ${workflow.name}`)
if (notDeletions) {
console.log(`Skipped ${notDeletions} runs for workflow: ${workflow.name}`)
}

return deletions
}
Loading

0 comments on commit d600061

Please sign in to comment.