Skip to content

Commit 351995a

Browse files
committed
Add support for stubbing @actions/github
1 parent 8146570 commit 351995a

17 files changed

+474
-11
lines changed
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"action": "opened",
3+
"issue": {
4+
"assignee": {
5+
"login": "ncalteen"
6+
},
7+
"assignees": [
8+
{
9+
"login": "ncalteen"
10+
}
11+
],
12+
"body": "This is an issue!",
13+
"number": 1,
14+
"state": "open",
15+
"title": "New Issue",
16+
"user": {
17+
"login": "ncalteen"
18+
}
19+
},
20+
"organization": {
21+
"login": "github"
22+
},
23+
"repository": {
24+
"full_name": "github/local-action",
25+
"name": "local-action",
26+
"owner": {
27+
"login": "github"
28+
}
29+
},
30+
"sender": {
31+
"login": "ncalteen"
32+
}
33+
}
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { jest } from '@jest/globals'
2+
import path from 'path'
3+
import { Context } from '../../../src/stubs/github/context.js'
4+
5+
let envBackup: NodeJS.ProcessEnv
6+
7+
describe('github/context', () => {
8+
beforeEach(() => {
9+
envBackup = process.env
10+
process.env.GITHUB_REPOSITORY = 'github/local-action'
11+
})
12+
13+
afterEach(() => {
14+
process.env = envBackup
15+
jest.resetAllMocks()
16+
})
17+
18+
describe('Context', () => {
19+
it('Creates a Context object', () => {
20+
const context = new Context()
21+
expect(context).toBeInstanceOf(Context)
22+
})
23+
24+
it('Gets the event payload', () => {
25+
process.env.GITHUB_EVENT_PATH = path.join(
26+
process.cwd(),
27+
'__fixtures__',
28+
'payloads',
29+
'issue_opened.json'
30+
)
31+
32+
const context = new Context()
33+
expect(context.payload.action).toBe('opened')
34+
})
35+
36+
it('Does not get the event payload if the path does not exist', () => {
37+
process.env.GITHUB_EVENT_PATH = path.join(
38+
process.cwd(),
39+
'__fixtures__',
40+
'payloads',
41+
'does_not_exist.json'
42+
)
43+
44+
const context = new Context()
45+
expect(context.payload.action).toBeUndefined()
46+
})
47+
48+
it('Gets the issue payload', () => {
49+
process.env.GITHUB_EVENT_PATH = path.join(
50+
process.cwd(),
51+
'__fixtures__',
52+
'payloads',
53+
'issue_opened.json'
54+
)
55+
56+
const context = new Context()
57+
expect(context.issue.number).toBe(1)
58+
})
59+
60+
it('Gets the repo payload', () => {
61+
process.env.GITHUB_EVENT_PATH = path.join(
62+
process.cwd(),
63+
'__fixtures__',
64+
'payloads',
65+
'issue_opened.json'
66+
)
67+
68+
const context = new Context()
69+
expect(context.repo.owner).toBe('github')
70+
expect(context.repo.repo).toBe('local-action')
71+
})
72+
})
73+
})

__tests__/stubs/github/github.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { jest } from '@jest/globals'
2+
import * as github from '../../../src/stubs/github/github.js'
3+
import { GitHub } from '../../../src/stubs/github/utils.js'
4+
5+
describe('github/github', () => {
6+
afterEach(() => {
7+
jest.resetAllMocks()
8+
})
9+
10+
describe('getOctokit', () => {
11+
it('Returns the options', () => {
12+
expect(github.getOctokit('test')).toBeInstanceOf(GitHub)
13+
})
14+
})
15+
})

__tests__/stubs/github/utils.test.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { jest } from '@jest/globals'
2+
import * as utils from '../../../src/stubs/github/utils.js'
3+
4+
describe('github/utils', () => {
5+
afterEach(() => {
6+
jest.resetAllMocks()
7+
})
8+
9+
describe('getOctokitOptions', () => {
10+
it('Returns the options', () => {
11+
expect(utils.getOctokitOptions('test')).toEqual({
12+
auth: 'token test'
13+
})
14+
})
15+
})
16+
})

docs/supported-functionality.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ to the `local-action` command.
6868
| `toPlatformPath()` | :white_check_mark: | |
6969
| `platform.*` | :white_check_mark: | |
7070

71+
## [`@actions/github`](https://github.com/actions/toolkit/tree/main/packages/github)
72+
73+
The stubbed version of `@actions/github` functions the same as the real package.
74+
However, the functionality is stubbed in order to ensure that all needed
75+
environment variables are pulled from the `.env` file passed to the
76+
`local-action` command. Otherwise, things like `github.context.eventName` will
77+
be `undefined`. For more information, see
78+
[#149](https://github.com/github/local-action/issues/149).
79+
7180
## Under Investigation
7281

7382
The following packages are under investigation for how to integrate with
@@ -85,7 +94,6 @@ this doesn't work correctly, please
8594
[open an issue!](https://github.com/github/local-action/issues/new)
8695

8796
- [`@actions/exec`](https://github.com/actions/toolkit/tree/main/packages/exec)
88-
- [`@actions/github`](https://github.com/actions/toolkit/tree/main/packages/github)
8997
- [`@actions/glob`](https://github.com/actions/toolkit/tree/main/packages/glob)
9098
- [`@actions/http-client`](https://github.com/actions/toolkit/tree/main/packages/http-client)
9199
- [`@actions/io`](https://github.com/actions/toolkit/tree/main/packages/io)

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@github/local-action",
33
"description": "Local Debugging for GitHub Actions",
4-
"version": "2.5.1",
4+
"version": "2.6.0",
55
"type": "module",
66
"author": "Nick Alteen <ncalteen@github.com>",
77
"private": false,

src/commands/run.ts

+36
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import quibble from 'quibble'
44
import { ARTIFACT_STUBS } from '../stubs/artifact/artifact.js'
55
import { CORE_STUBS, CoreMeta } from '../stubs/core/core.js'
66
import { EnvMeta } from '../stubs/env.js'
7+
import { Context } from '../stubs/github/context.js'
8+
import { getOctokit } from '../stubs/github/github.ts'
79
import type { Action } from '../types.js'
810
import { printTitle } from '../utils/output.js'
911
import { isESM } from '../utils/package.js'
@@ -124,6 +126,23 @@ export async function action(): Promise<void> {
124126
// local-action require a different approach depending on if the called action
125127
// is written in ESM.
126128
if (isESM()) {
129+
await quibble.esm(
130+
path.resolve(
131+
dirs.join(path.sep),
132+
'node_modules',
133+
'@actions',
134+
'github',
135+
'lib',
136+
'github.js'
137+
),
138+
{
139+
getOctokit,
140+
// The context object needs to be created **after** the dotenv file is
141+
// loaded. Otherwise, the GITHUB_* environment variables will not be
142+
// available to the action.
143+
context: new Context()
144+
}
145+
)
127146
await quibble.esm(
128147
path.resolve(
129148
dirs.join(path.sep),
@@ -158,6 +177,23 @@ export async function action(): Promise<void> {
158177

159178
await run()
160179
} else {
180+
quibble(
181+
path.resolve(
182+
dirs.join(path.sep),
183+
'node_modules',
184+
'@actions',
185+
'github',
186+
'lib',
187+
'github.js'
188+
),
189+
{
190+
getOctokit,
191+
// The context object needs to be created **after** the dotenv file is
192+
// loaded. Otherwise, the GITHUB_* environment variables will not be
193+
// available to the action.
194+
context: new Context()
195+
}
196+
)
161197
quibble(
162198
path.resolve(
163199
dirs.join(path.sep),

src/stubs/artifact/internal/delete/delete-artifact.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { getOctokit } from '@actions/github'
2-
import { defaults as defaultGitHubOptions } from '@actions/github/lib/utils.js'
31
import type { OctokitOptions } from '@octokit/core'
42
import { requestLog } from '@octokit/plugin-request-log'
53
import { retry } from '@octokit/plugin-retry'
64
import fs from 'fs'
75
import path from 'path'
86
import { EnvMeta } from '../../../../stubs/env.js'
7+
import { getOctokit } from '../../../../stubs/github/github.ts'
98
import * as core from '../../../core/core.js'
9+
import { defaults as defaultGitHubOptions } from '../../../github/utils.js'
1010
import { getArtifactPublic } from '../find/get-artifact.js'
1111
import { getRetryOptions } from '../find/retry-options.js'
1212
import {

src/stubs/artifact/internal/download/download-artifact.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { getOctokit } from '@actions/github'
21
import * as httpClient from '@actions/http-client'
32
import fs from 'fs'
43
import path from 'path'
54
import { finished } from 'stream/promises'
65
import unzip from 'unzip-stream'
76
import { EnvMeta } from '../../../../stubs/env.js'
87
import * as core from '../../../core/core.js'
8+
import { getOctokit } from '../../../github/github.js'
99
import { getGitHubWorkspaceDir } from '../shared/config.js'
1010
import { ArtifactNotFoundError } from '../shared/errors.js'
1111
import type {

src/stubs/artifact/internal/find/get-artifact.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { getOctokit } from '@actions/github'
2-
import { defaults as defaultGitHubOptions } from '@actions/github/lib/utils.js'
31
import type { OctokitOptions } from '@octokit/core'
42
import { requestLog } from '@octokit/plugin-request-log'
53
import { retry } from '@octokit/plugin-retry'
64
import { EnvMeta } from '../../../../stubs/env.js'
75
import * as core from '../../../core/core.js'
6+
import { getOctokit } from '../../../github/github.js'
7+
import { defaults as defaultGitHubOptions } from '../../../github/utils.js'
88
import {
99
ArtifactNotFoundError,
1010
InvalidResponseError

src/stubs/artifact/internal/find/list-artifacts.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { getOctokit } from '@actions/github'
2-
import { defaults as defaultGitHubOptions } from '@actions/github/lib/utils.js'
31
import type { OctokitOptions } from '@octokit/core'
42
import { requestLog } from '@octokit/plugin-request-log'
53
import { retry } from '@octokit/plugin-retry'
64
import { EnvMeta } from '../../../../stubs/env.js'
75
import * as core from '../../../core/core.js'
6+
import { getOctokit } from '../../../github/github.js'
7+
import { defaults as defaultGitHubOptions } from '../../../github/utils.js'
88
import type { Artifact, ListArtifactsResponse } from '../shared/interfaces.js'
99
import { getUserAgentString } from '../shared/user-agent.js'
1010
import { getRetryOptions } from './retry-options.js'

src/stubs/github/context.ts

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { existsSync, readFileSync } from 'fs'
2+
import { EOL } from 'os'
3+
import { WebhookPayload } from './interfaces.js'
4+
5+
export class Context {
6+
/**
7+
* Webhook payload object that triggered the workflow
8+
*/
9+
payload: WebhookPayload
10+
11+
eventName: string
12+
sha: string
13+
ref: string
14+
workflow: string
15+
action: string
16+
actor: string
17+
job: string
18+
runAttempt: number
19+
runNumber: number
20+
runId: number
21+
apiUrl: string
22+
serverUrl: string
23+
graphqlUrl: string
24+
25+
/**
26+
* Hydrate the context from the environment
27+
*/
28+
constructor() {
29+
this.payload = {}
30+
31+
if (process.env.GITHUB_EVENT_PATH) {
32+
console.log(process.env.GITHUB_EVENT_PATH)
33+
if (existsSync(process.env.GITHUB_EVENT_PATH)) {
34+
this.payload = JSON.parse(
35+
readFileSync(process.env.GITHUB_EVENT_PATH, { encoding: 'utf8' })
36+
)
37+
} else {
38+
const path = process.env.GITHUB_EVENT_PATH
39+
process.stdout.write(`GITHUB_EVENT_PATH ${path} does not exist${EOL}`)
40+
}
41+
}
42+
43+
this.eventName = process.env.GITHUB_EVENT_NAME as string
44+
this.sha = process.env.GITHUB_SHA as string
45+
this.ref = process.env.GITHUB_REF as string
46+
this.workflow = process.env.GITHUB_WORKFLOW as string
47+
this.action = process.env.GITHUB_ACTION as string
48+
this.actor = process.env.GITHUB_ACTOR as string
49+
this.job = process.env.GITHUB_JOB as string
50+
this.runAttempt = parseInt(process.env.GITHUB_RUN_ATTEMPT as string, 10)
51+
this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER as string, 10)
52+
this.runId = parseInt(process.env.GITHUB_RUN_ID as string, 10)
53+
this.apiUrl = process.env.GITHUB_API_URL ?? 'https://api.github.com'
54+
this.serverUrl = process.env.GITHUB_SERVER_URL ?? 'https://github.com'
55+
this.graphqlUrl =
56+
process.env.GITHUB_GRAPHQL_URL ?? 'https://api.github.com/graphql'
57+
}
58+
59+
get issue(): { owner: string; repo: string; number: number } {
60+
const payload = this.payload
61+
62+
/* istanbul ignore next */
63+
return {
64+
...this.repo,
65+
number: (payload.issue || payload.pull_request || payload).number
66+
}
67+
}
68+
69+
get repo(): { owner: string; repo: string } {
70+
if (process.env.GITHUB_REPOSITORY) {
71+
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
72+
return { owner, repo }
73+
}
74+
75+
/* istanbul ignore next */
76+
if (this.payload.repository) {
77+
return {
78+
owner: this.payload.repository.owner.login,
79+
repo: this.payload.repository.name
80+
}
81+
}
82+
83+
/* istanbul ignore next */
84+
throw new Error(
85+
"context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'"
86+
)
87+
}
88+
}

0 commit comments

Comments
 (0)