Skip to content

Commit a68f637

Browse files
committed
feat: migrate from Travis to GitHub Actions
1 parent 0258d54 commit a68f637

File tree

7 files changed

+280
-742
lines changed

7 files changed

+280
-742
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
"build": "tsc -p .",
3131
"watch": "tsc -w -p .",
3232
"package-schema": "download http://json.schemastore.org/package | json2ts | prettier --parser typescript > src/package-schema.ts",
33-
"travis-schema": "download http://json.schemastore.org/travis | json2ts | prettier --parser typescript > src/travis-schema.ts",
3433
"tsconfig-schema": "download http://json.schemastore.org/tsconfig | json2ts | prettier --parser typescript > src/tsconfig-schema.ts",
3534
"eslintrc-schema": "download http://json.schemastore.org/eslintrc | json2ts | prettier --parser typescript > src/eslintrc-schema.ts",
3635
"renovate-schema": "download http://json.schemastore.org/renovate | json2ts | prettier --parser typescript > src/renovate-schema.ts",
@@ -58,7 +57,8 @@
5857
"mkdirp-promise": "^5.0.1",
5958
"mz": "^2.7.0",
6059
"npm-registry-client": "^8.6.0",
61-
"source-map-support": "^0.5.16"
60+
"source-map-support": "^0.5.16",
61+
"tweetsodium": "^0.0.5"
6262
},
6363
"devDependencies": {
6464
"@commitlint/cli": "^11.0.0",
@@ -75,7 +75,7 @@
7575
"download-cli": "^1.1.1",
7676
"eslint": "^7.17.0",
7777
"husky": "^4.2.3",
78-
"json-schema-to-typescript": "^8.2.0",
78+
"json-schema-to-typescript": "^10.1.2",
7979
"prettier": "^2.1.2",
8080
"semantic-release": "^17.0.6",
8181
"typescript": "^4.1.3"

src/cli.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { createGitHubClient } from './github'
1111
import { JsonSchemaForNpmPackageJsonFiles } from './package-schema'
1212
import * as prompt from './prompt'
1313
import { JsonSchemaForRenovateConfigFilesHttpsRenovatebotCom } from './renovate-schema'
14-
import { createTravisClient, initTravis } from './travis'
14+
import { initGitHubWorkflow } from './github-workflow'
1515
import { JsonSchemaForTheTypeScriptCompilersConfigurationFile } from './tsconfig-schema'
1616
import { JSONSchemaForESLintConfigurationFiles } from './eslintrc-schema'
1717
import mkdirp from 'mkdirp-promise'
@@ -42,19 +42,6 @@ async function main(): Promise<void> {
4242
console.log('Using BUILDKITE_TOKEN from env var')
4343
const buildkiteClient = createBuildkiteClient({ token: process.env.BUILDKITE_TOKEN })
4444

45-
if (!process.env.TRAVIS_TOKEN) {
46-
throw createCLIError(
47-
[
48-
'No TRAVIS_TOKEN env var set.',
49-
'Copy it from https://travis-ci.org/profile or create one with the Travis CLI by running',
50-
'',
51-
' travis login --github-token $GITHUB_TOKEN',
52-
' travis token',
53-
].join('\n')
54-
)
55-
}
56-
const travisClient = createTravisClient({ token: process.env.TRAVIS_TOKEN })
57-
5845
console.log('*️⃣ Welcome to the Sourcegraph npm package initializer')
5946

6047
if (!(await exists('.git'))) {
@@ -382,8 +369,8 @@ async function main(): Promise<void> {
382369
})
383370
buildBadge = `[![build](${badgeUrl}?branch=master)](${webUrl})`
384371
} else {
385-
await initTravis({ repoName, hasTests, travisClient, githubClient })
386-
buildBadge = `[![build](https://travis-ci.org/sourcegraph/${repoName}.svg?branch=master)](https://travis-ci.org/sourcegraph/${repoName})`
372+
await initGitHubWorkflow({ repoName, hasTests, githubClient })
373+
buildBadge = `[![build](https://img.shields.io/github/workflow/status/sourcegraph/${repoName}/build/master)](https://github.com/sourcegraph/${repoName}/actions?query=branch%3Amaster+workflow%3Abuild)`
387374
}
388375

389376
if (await exists('README.md')) {

src/github-workflow.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/* eslint-disable no-template-curly-in-string */
2+
import { HTTPError } from 'got'
3+
import * as yaml from 'js-yaml'
4+
import { exists, writeFile } from 'mz/fs'
5+
import { GitHubClient } from './github'
6+
import { createSourcegraphBotNpmToken } from './npm'
7+
import * as sodium from 'tweetsodium'
8+
9+
const createGitHubSecret = async ({
10+
repoName,
11+
name,
12+
value,
13+
githubClient,
14+
}: {
15+
repoName: string
16+
name: string
17+
value: string
18+
githubClient: GitHubClient
19+
}): Promise<void> => {
20+
// Get public key for repository
21+
const { keyId, key } = await githubClient.get<{ keyId: string; key: string }>(
22+
`repos/sourcegraph/${repoName}/actions/secrets/public-key`,
23+
{
24+
responseType: 'json',
25+
resolveBodyOnly: true,
26+
}
27+
)
28+
29+
// Convert the message and key to Uint8Array's (Buffer implements that interface)
30+
const messageBytes = Buffer.from(value)
31+
const keyBytes = Buffer.from(key, 'base64')
32+
33+
// Encrypt using LibSodium.
34+
const encryptedBytes = sodium.seal(messageBytes, keyBytes)
35+
36+
// Base64 the encrypted secret
37+
const encryptedValue = Buffer.from(encryptedBytes).toString('base64')
38+
39+
await githubClient.post(`repos/sourcegraph/${repoName}/actions/secrets/${name}`, {
40+
json: {
41+
encrypted_value: encryptedValue,
42+
key_id: keyId,
43+
},
44+
})
45+
}
46+
47+
export async function initGitHubWorkflow({
48+
hasTests,
49+
repoName,
50+
githubClient,
51+
}: {
52+
hasTests: boolean
53+
repoName: string
54+
githubClient: GitHubClient
55+
}): Promise<void> {
56+
console.log('⚙️ Setting up GitHub Actions Workflow')
57+
if (await exists('.github/workflows/build.yml')) {
58+
console.log('.github/workflows/build.yml already exists, skipping')
59+
} else {
60+
const workflowYaml = {
61+
name: 'build',
62+
on: ['push', 'pull_request'],
63+
env: {
64+
FORCE_COLOR: 3,
65+
},
66+
jobs: {
67+
build: {
68+
'runs-on': 'ubuntu-latest',
69+
steps: [
70+
{ uses: 'actions/checkout@v2' },
71+
{
72+
name: 'Use Node.js',
73+
uses: 'actions/setup-node@v2',
74+
with: {
75+
'node-version': '14.x',
76+
},
77+
},
78+
{ run: 'yarn' },
79+
{ run: 'yarn run prettier-check' },
80+
{ run: 'yarn run eslint' },
81+
{ run: 'yarn run build' },
82+
...(hasTests
83+
? [
84+
{ run: 'yarn test' },
85+
{ run: 'nyc report --reporter json' },
86+
{
87+
name: 'Upload coverage to Codecov',
88+
uses: 'codecov/codecov-action@v1',
89+
},
90+
]
91+
: []),
92+
{
93+
name: 'release',
94+
if:
95+
"github.repository_owner == 'sourcegraph' && github.event_name == 'push' && github.ref == 'refs/heads/master'",
96+
run: 'yarn run semantic-release',
97+
env: {
98+
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}',
99+
NPM_TOKEN: '${{ secrets.NPM_TOKEN }}',
100+
},
101+
},
102+
],
103+
},
104+
},
105+
}
106+
await writeFile('.github/workflows/build.yml', yaml.dump(workflowYaml))
107+
}
108+
109+
const secretName = 'NPM_TOKEN'
110+
try {
111+
await githubClient.get(`repos/sourcegraph/${repoName}/actions/secrets/${secretName}`, {
112+
responseType: 'json',
113+
resolveBodyOnly: true,
114+
})
115+
console.log('🔑 NPM_TOKEN already set in GitHub secrets, skipping creation')
116+
} catch (error) {
117+
if (!(error instanceof HTTPError) || error.response.statusCode !== 404) {
118+
throw error
119+
}
120+
const npmToken = await createSourcegraphBotNpmToken()
121+
console.log('🔑 Setting NPM_TOKEN GitHub secret')
122+
await createGitHubSecret({
123+
repoName,
124+
name: secretName,
125+
value: npmToken,
126+
githubClient,
127+
})
128+
}
129+
}

src/github.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import got, { Got } from 'got'
2-
import * as prompt from './prompt'
32

43
export type GitHubClient = Got
54

@@ -13,32 +12,3 @@ export const createGitHubClient = ({ token }: { token: string }): GitHubClient =
1312
responseType: 'json',
1413
resolveBodyOnly: true,
1514
})
16-
17-
export async function createSourcegraphBotGitHubToken({
18-
repoName,
19-
githubClient,
20-
}: {
21-
repoName: string
22-
githubClient: GitHubClient
23-
}): Promise<string> {
24-
console.log(
25-
'See credentials in https://team-sourcegraph.1password.com/vaults/dnrhbauihkhjs5ag6vszsme45a/allitems/7s46bqcnl5hxzbutupu44va7gu'
26-
)
27-
const password = await prompt.password('@sourcegraph-bot GitHub password')
28-
const otp = await prompt.input('@sourcegraph-bot GitHub 2FA code')
29-
30-
const response = await githubClient.post<{ token: string }>('authorizations', {
31-
headers: {
32-
Authorization: 'Basic ' + Buffer.from('sourcegraph-bot:' + password).toString('base64'),
33-
'X-GitHub-OTP': otp,
34-
},
35-
json: {
36-
note: `semantic-release Travis CI for repo ${repoName}`,
37-
scopes: ['repo', 'read:org', 'user:email', 'repo_deployment', 'repo:status', 'write:repo_hook'],
38-
},
39-
responseType: 'json',
40-
resolveBodyOnly: true,
41-
})
42-
console.log('🔑 Created GitHub token for @sourcegraph-bot')
43-
return response.token
44-
}

0 commit comments

Comments
 (0)