Skip to content

Commit 2757b80

Browse files
committed
Guard object scans entries and read npm plugins from cached scans context
1 parent 4cadf62 commit 2757b80

5 files changed

Lines changed: 37 additions & 16 deletions

File tree

.github/actions/find/src/findForUrl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export async function findForUrl(
4141
const scansContext = getScansContext()
4242

4343
if (scansContext.shouldRunPlugins) {
44-
const plugins = await loadPlugins(scansContext.npmPlugins)
44+
const plugins = await loadPlugins()
4545
for (const plugin of plugins) {
4646
if (scansContext.scansToPerform.includes(plugin.name)) {
4747
core.info(`Running plugin: ${plugin.name}`)

.github/actions/find/src/pluginManager/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {fileURLToPath} from 'url'
44
import * as core from '@actions/core'
55
import {loadPluginViaJsFile, loadPluginViaTsFile} from './pluginFileLoaders.js'
66
import {loadPluginViaNpm} from './pluginNpmLoader.js'
7-
import type {NpmPluginRequest, Plugin, PluginDefaultParams} from './types.js'
7+
import type {Plugin, PluginDefaultParams} from './types.js'
8+
import {getScansContext} from '../scansContextProvider.js'
89

910
// Helper to get __dirname equivalent in ES Modules
1011
const __filename = fileURLToPath(import.meta.url)
@@ -21,13 +22,13 @@ export function getPlugins() {
2122
}
2223
let pluginsLoaded = false
2324

24-
export async function loadPlugins(npmPlugins: NpmPluginRequest[] = []) {
25+
export async function loadPlugins() {
2526
try {
2627
if (!pluginsLoaded) {
2728
core.info('loading plugins')
2829
await loadBuiltInPlugins()
2930
await loadCustomPlugins()
30-
await loadNpmPlugins(npmPlugins)
31+
await loadNpmPlugins()
3132
}
3233
} catch {
3334
plugins.length = 0
@@ -91,7 +92,8 @@ export async function loadCustomPlugins() {
9192
const FIRST_PARTY_NPM_PLUGINS = ['@github/accessibility-scanner-alt-text-plugin']
9293

9394
// exported for mocking/testing. not for actual use
94-
export async function loadNpmPlugins(npmPlugins: NpmPluginRequest[]) {
95+
export async function loadNpmPlugins() {
96+
const {npmPlugins} = getScansContext()
9597
if (npmPlugins.length === 0) {
9698
return
9799
}

.github/actions/find/src/scansContextProvider.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ export function getScansContext() {
2828
for (const entry of rawScans) {
2929
if (typeof entry === 'string') {
3030
scansToPerform.push(entry)
31-
} else if (entry && typeof entry.name === 'string' && typeof entry.package === 'string') {
31+
} else if (
32+
typeof entry === 'object' &&
33+
entry !== null &&
34+
typeof entry.name === 'string' &&
35+
typeof entry.package === 'string'
36+
) {
3237
scansToPerform.push(entry.name)
3338
npmPlugins.push({
3439
name: entry.name,

.github/actions/find/tests/findForUrl.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,13 @@ describe('findForUrl', () => {
117117
expect(loadedPlugins[1].default).toHaveBeenCalledTimes(0)
118118
})
119119

120-
it('forwards object-form NPM plugin entries to loadPlugins', async () => {
120+
it('runs plugins when a scans entry is an object-form NPM plugin', async () => {
121121
loadedPlugins = []
122122
actionInput = JSON.stringify([{name: 'alt-text-scan', package: '@github/accessibility-scanner-alt-text-plugin'}])
123123
clearAll()
124124

125125
await findForUrl('test.com')
126-
expect(pluginManager.loadPlugins).toHaveBeenCalledWith([
127-
{name: 'alt-text-scan', package: '@github/accessibility-scanner-alt-text-plugin', version: undefined},
128-
])
126+
expect(pluginManager.loadPlugins).toHaveBeenCalledTimes(1)
129127
})
130128
})
131129

.github/actions/find/tests/pluginNpmLoader.test.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,25 @@ import * as childProcess from 'child_process'
44
import * as core from '@actions/core'
55
import * as pluginManager from '../src/pluginManager/index.js'
66
import * as npmPluginLoader from '../src/pluginManager/pluginNpmLoader.js'
7-
import type {Plugin} from '../src/pluginManager/types.js'
7+
import * as scansContextProvider from '../src/scansContextProvider.js'
8+
import type {Plugin, NpmPluginRequest} from '../src/pluginManager/types.js'
89

910
vi.mock('child_process', {spy: true})
1011
vi.mock('@actions/core', {spy: true})
1112
vi.mock('../src/pluginManager/pluginNpmLoader.js', {spy: true})
13+
vi.mock('../src/scansContextProvider.js', {spy: true})
1214

1315
const ALLOWED = '@github/accessibility-scanner-alt-text-plugin'
1416

17+
function mockNpmPlugins(npmPlugins: NpmPluginRequest[]) {
18+
vi.spyOn(scansContextProvider, 'getScansContext').mockReturnValue({
19+
scansToPerform: npmPlugins.map(plugin => plugin.name),
20+
npmPlugins,
21+
shouldPerformAxeScan: false,
22+
shouldRunPlugins: true,
23+
})
24+
}
25+
1526
describe('npmPluginLoader', () => {
1627
beforeEach(() => {
1728
vi.restoreAllMocks()
@@ -62,14 +73,16 @@ describe('loadNpmPlugins', () => {
6273

6374
it('loads a plugin from a first-party package', async () => {
6475
vi.spyOn(npmPluginLoader, 'loadPluginViaNpm').mockResolvedValue({name: 'alt-text-scan', default: vi.fn()})
65-
await pluginManager.loadNpmPlugins([{name: 'alt-text-scan', package: ALLOWED}])
76+
mockNpmPlugins([{name: 'alt-text-scan', package: ALLOWED}])
77+
await pluginManager.loadNpmPlugins()
6678
expect(pluginManager.getPlugins().map(plugin => plugin.name)).toContain('alt-text-scan')
6779
})
6880

6981
it('skips and warns when a package is not first-party', async () => {
7082
const loadSpy = vi.spyOn(npmPluginLoader, 'loadPluginViaNpm').mockResolvedValue(undefined)
7183
const warnSpy = vi.spyOn(core, 'warning').mockImplementation(() => {})
72-
await pluginManager.loadNpmPlugins([{name: 'evil', package: 'evil-pkg'}])
84+
mockNpmPlugins([{name: 'evil', package: 'evil-pkg'}])
85+
await pluginManager.loadNpmPlugins()
7386
expect(loadSpy).not.toHaveBeenCalled()
7487
expect(warnSpy).toHaveBeenCalled()
7588
expect(pluginManager.getPlugins().length).toBe(0)
@@ -78,23 +91,26 @@ describe('loadNpmPlugins', () => {
7891
it('skips a package that does not export a valid plugin', async () => {
7992
vi.spyOn(npmPluginLoader, 'loadPluginViaNpm').mockResolvedValue({name: 'bad'} as unknown as Plugin)
8093
const warnSpy = vi.spyOn(core, 'warning').mockImplementation(() => {})
81-
await pluginManager.loadNpmPlugins([{name: 'bad', package: ALLOWED}])
94+
mockNpmPlugins([{name: 'bad', package: ALLOWED}])
95+
await pluginManager.loadNpmPlugins()
8296
expect(warnSpy).toHaveBeenCalled()
8397
expect(pluginManager.getPlugins().length).toBe(0)
8498
})
8599

86100
it('skips an NPM plugin whose exported name does not match the requested name', async () => {
87101
vi.spyOn(npmPluginLoader, 'loadPluginViaNpm').mockResolvedValue({name: 'actual-name', default: vi.fn()})
88102
const warnSpy = vi.spyOn(core, 'warning').mockImplementation(() => {})
89-
await pluginManager.loadNpmPlugins([{name: 'requested-name', package: ALLOWED}])
103+
mockNpmPlugins([{name: 'requested-name', package: ALLOWED}])
104+
await pluginManager.loadNpmPlugins()
90105
expect(warnSpy).toHaveBeenCalled()
91106
expect(pluginManager.getPlugins().length).toBe(0)
92107
})
93108

94109
it('skips an NPM plugin whose name collides with an already-loaded plugin', async () => {
95110
pluginManager.getPlugins().push({name: 'dup', default: vi.fn()})
96111
vi.spyOn(npmPluginLoader, 'loadPluginViaNpm').mockResolvedValue({name: 'dup', default: vi.fn()})
97-
await pluginManager.loadNpmPlugins([{name: 'dup', package: ALLOWED}])
112+
mockNpmPlugins([{name: 'dup', package: ALLOWED}])
113+
await pluginManager.loadNpmPlugins()
98114
expect(pluginManager.getPlugins().filter(plugin => plugin.name === 'dup').length).toBe(1)
99115
})
100116
})

0 commit comments

Comments
 (0)