Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/get-graphql-schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ async function fetchFileForSchema(schema, octokit) {
path: schema.pathToFile,
ref: branch,
})
console.log(`LFS download via ${download_url}...`)
console.log('Downloading LFS schema content...')
content = await fetch(download_url).then(res => {
if (!res.ok) {
const error = new Error(`HTTP ${res.status}: ${res.statusText}`)
Expand Down
53 changes: 33 additions & 20 deletions bin/github-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,58 @@ import {runCommand} from './run-command.js'
import {createPullRequest} from 'octokit-plugin-create-pull-request'

/**
* @param {typeof runCommand} executeCommand
* @returns {Promise<string>}
*/
async function getGithubPasswordFromDev() {
async function getGithubTokenFromDev(executeCommand) {
try {
// Uses token from `dev`
return (await runCommand('/opt/dev/bin/dev', ['github', 'print-auth', '--password'])).trim()
} catch (error) {
console.warn(`Soft-error fetching password from dev: ${error.message}. Try running \`dev github print-auth\` manually.`)
process.exit(0)
const token = (
await executeCommand('/opt/dev/bin/dev', ['github', 'print-auth', '--password'], {printOutput: false})
).trim()
if (!token) throw new Error('The GitHub token returned by dev is empty.')
return token
} catch {
throw new Error('Failed to fetch a GitHub token from dev. Try running `dev github auth`.')
}
}

/**
* @param {string} owner
* @param {function(import('@octokit/rest').Octokit): Promise<boolean>} func
* @returns {Promise<boolean>}
* @param {{environment?: NodeJS.ProcessEnv, executeCommand?: typeof runCommand, log?: (message: string) => void}} [options]
* @returns {Promise<string>}
*/
export async function withOctokit(owner, func) {
let password = undefined

export async function getGithubToken(
owner,
{environment = process.env, executeCommand = runCommand, log = console.log} = {},
) {
const tokenEnvSources = [
`GITHUB_TOKEN_${owner.toUpperCase()}`,
`GH_TOKEN_${owner.toUpperCase()}`,
'GITHUB_TOKEN',
'GH_TOKEN',
]
let tokenFromEnv = undefined

for (const source of tokenEnvSources) {
if (process.env[source]) {
tokenFromEnv = process.env[source]
console.log(`Using token from ${source}: ${tokenFromEnv}`)
break
const token = environment[source]
if (token) {
log(`Using GitHub token from ${source}`)
return token
}
}
if (!tokenFromEnv) {
password = await getGithubPasswordFromDev()
console.log(`Using password from dev: ${password}`)
}
const authToken = password || tokenFromEnv

const token = await getGithubTokenFromDev(executeCommand)
log('Using GitHub token from dev')
return token
}

/**
* @param {string} owner
* @param {function(import('@octokit/rest').Octokit): Promise<boolean>} func
* @returns {Promise<boolean>}
*/
export async function withOctokit(owner, func) {
const authToken = await getGithubToken(owner)

const OctokitWithPlugin = Octokit.plugin(createPullRequest)
const octokit = new OctokitWithPlugin({
Expand Down
50 changes: 50 additions & 0 deletions bin/github-utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import assert from 'node:assert/strict'
import {test} from 'node:test'

import {getGithubToken} from './github-utils.js'

test('does not print a GitHub token sourced from the environment', async () => {
const token = 'environment-secret'
const logs = []

const result = await getGithubToken('shop', {
environment: {GITHUB_TOKEN_SHOP: token},
log: (message) => logs.push(message),
})

assert.equal(result, token)
assert.deepEqual(logs, ['Using GitHub token from GITHUB_TOKEN_SHOP'])
assert.equal(logs.join(' ').includes(token), false)
})

test('captures a GitHub token from dev without printing it', async () => {
const token = 'dev-secret'
const logs = []
const receivedCommands = []

const result = await getGithubToken('shop', {
environment: {},
executeCommand: (...command) => {
receivedCommands.push(command)
return Promise.resolve(`${token}\n`)
},
log: (message) => logs.push(message),
})

assert.equal(result, token)
assert.deepEqual(receivedCommands, [
['/opt/dev/bin/dev', ['github', 'print-auth', '--password'], {printOutput: false}],
])
assert.deepEqual(logs, ['Using GitHub token from dev'])
assert.equal(logs.join(' ').includes(token), false)
})

test('fails when dev cannot provide a GitHub token', async () => {
await assert.rejects(
getGithubToken('shop', {
environment: {},
executeCommand: () => Promise.reject(new Error('Authentication failed')),
}),
new Error('Failed to fetch a GitHub token from dev. Try running `dev github auth`.'),
)
})
7 changes: 4 additions & 3 deletions bin/run-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ import {spawn} from 'child_process'
/**
* @param {string} command
* @param {string[]} args
* @param {{printOutput?: boolean}} [options]
* @returns {Promise<string>}
*/
export function runCommand(command, args) {
export function runCommand(command, args, {printOutput = true} = {}) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {stdio: ['inherit', 'pipe', 'pipe']})

let output = ''
let errorOutput = ''

child.stdout.on('data', (data) => {
console.log(data.toString())
if (printOutput) console.log(data.toString())
output += data.toString()
})

child.stderr.on('data', (data) => {
console.log(data.toString())
if (printOutput) console.log(data.toString())
errorOutput += data.toString()
})

Expand Down
16 changes: 16 additions & 0 deletions bin/run-command.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import assert from 'node:assert/strict'
import {test} from 'node:test'

import {runCommand} from './run-command.js'

test('captures command output without printing it when requested', async (context) => {
const sensitiveOutput = 'sensitive command output'
const consoleLog = context.mock.method(console, 'log', () => {})

const output = await runCommand(process.execPath, ['-e', `process.stdout.write('${sensitiveOutput}')`], {
printOutput: false,
})

assert.equal(output, sensitiveOutput)
assert.equal(consoleLog.mock.callCount(), 0)
})
Loading