1
0
Fork 0

Add findInPath method to locate all matching executables in the system path (#609)

Signed-off-by: Sora Morimoto <sora@morimoto.io>
pull/610/head
Sora Morimoto 2021-04-03 01:22:30 +09:00 committed by GitHub
parent de122731f3
commit bd9017e99f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 145 additions and 62 deletions

View File

@ -1,5 +1,9 @@
# @actions/io Releases # @actions/io Releases
### 1.1.0
- Add `findInPath` method to locate all matching executables in the system path
### 1.0.2 ### 1.0.2
- [Add \"types\" to package.json](https://github.com/actions/toolkit/pull/221) - [Add \"types\" to package.json](https://github.com/actions/toolkit/pull/221)

View File

@ -6,6 +6,10 @@ import * as io from '../src/io'
import * as ioUtil from '../src/io-util' import * as ioUtil from '../src/io-util'
describe('cp', () => { describe('cp', () => {
beforeAll(async () => {
await io.rmRF(getTestTemp())
})
it('copies file with no flags', async () => { it('copies file with no flags', async () => {
const root = path.join(getTestTemp(), 'cp_with_no_flags') const root = path.join(getTestTemp(), 'cp_with_no_flags')
const sourceFile = path.join(root, 'cp_source') const sourceFile = path.join(root, 'cp_source')
@ -166,6 +170,10 @@ describe('cp', () => {
}) })
describe('mv', () => { describe('mv', () => {
beforeAll(async () => {
await io.rmRF(getTestTemp())
})
it('moves file with no flags', async () => { it('moves file with no flags', async () => {
const root = path.join(getTestTemp(), ' mv_with_no_flags') const root = path.join(getTestTemp(), ' mv_with_no_flags')
const sourceFile = path.join(root, ' mv_source') const sourceFile = path.join(root, ' mv_source')
@ -264,6 +272,10 @@ describe('mv', () => {
}) })
describe('rmRF', () => { describe('rmRF', () => {
beforeAll(async () => {
await io.rmRF(getTestTemp())
})
it('removes single folder with rmRF', async () => { it('removes single folder with rmRF', async () => {
const testPath = path.join(getTestTemp(), 'testFolder') const testPath = path.join(getTestTemp(), 'testFolder')
@ -841,6 +853,10 @@ describe('mkdirP', () => {
}) })
describe('which', () => { describe('which', () => {
beforeAll(async () => {
await io.rmRF(getTestTemp())
})
it('which() finds file name', async () => { it('which() finds file name', async () => {
// create a executable file // create a executable file
const testPath = path.join(getTestTemp(), 'which-finds-file-name') const testPath = path.join(getTestTemp(), 'which-finds-file-name')
@ -1373,6 +1389,53 @@ describe('which', () => {
} }
}) })
describe('findInPath', () => {
beforeAll(async () => {
await io.rmRF(getTestTemp())
})
it('findInPath() not found', async () => {
expect(await io.findInPath('findInPath-test-no-such-file')).toEqual([])
})
it('findInPath() finds file names', async () => {
// create executable files
let fileName = 'FindInPath-Test-File'
if (process.platform === 'win32') {
fileName += '.exe'
}
const testPaths = ['1', '2', '3'].map(count =>
path.join(getTestTemp(), `findInPath-finds-file-names-${count}`)
)
for (const testPath of testPaths) {
await io.mkdirP(testPath)
}
const filePaths = testPaths.map(testPath => path.join(testPath, fileName))
for (const filePath of filePaths) {
await fs.writeFile(filePath, '')
if (process.platform !== 'win32') {
chmod(filePath, '+x')
}
}
const originalPath = process.env['PATH']
try {
// update the PATH
for (const testPath of testPaths) {
process.env[
'PATH'
] = `${process.env['PATH']}${path.delimiter}${testPath}`
}
// exact file names
expect(await io.findInPath(fileName)).toEqual(filePaths)
} finally {
process.env['PATH'] = originalPath
}
})
})
async function findsExecutableWithScopedPermissions( async function findsExecutableWithScopedPermissions(
chmodOptions: string chmodOptions: string
): Promise<void> { ): Promise<void> {

View File

@ -1,5 +1,5 @@
{ {
"name": "@actions/io", "name": "@actions/io",
"version": "1.0.2", "version": "1.1.0",
"lockfileVersion": 1 "lockfileVersion": 1
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@actions/io", "name": "@actions/io",
"version": "1.0.2", "version": "1.1.0",
"description": "Actions io lib", "description": "Actions io lib",
"keywords": [ "keywords": [
"github", "github",

View File

@ -192,69 +192,85 @@ export async function which(tool: string, check?: boolean): Promise<string> {
) )
} }
} }
return result
} }
try { const matches: string[] = await findInPath(tool)
// build the list of extensions to try
const extensions: string[] = []
if (ioUtil.IS_WINDOWS && process.env.PATHEXT) {
for (const extension of process.env.PATHEXT.split(path.delimiter)) {
if (extension) {
extensions.push(extension)
}
}
}
// if it's rooted, return it if exists. otherwise return empty. if (matches && matches.length > 0) {
if (ioUtil.isRooted(tool)) { return matches[0]
const filePath: string = await ioUtil.tryGetExecutablePath(
tool,
extensions
)
if (filePath) {
return filePath
}
return ''
}
// if any path separators, return empty
if (tool.includes('/') || (ioUtil.IS_WINDOWS && tool.includes('\\'))) {
return ''
}
// build the list of directories
//
// Note, technically "where" checks the current directory on Windows. From a toolkit perspective,
// it feels like we should not do this. Checking the current directory seems like more of a use
// case of a shell, and the which() function exposed by the toolkit should strive for consistency
// across platforms.
const directories: string[] = []
if (process.env.PATH) {
for (const p of process.env.PATH.split(path.delimiter)) {
if (p) {
directories.push(p)
}
}
}
// return the first match
for (const directory of directories) {
const filePath = await ioUtil.tryGetExecutablePath(
directory + path.sep + tool,
extensions
)
if (filePath) {
return filePath
}
}
return ''
} catch (err) {
throw new Error(`which failed with message ${err.message}`)
} }
return ''
}
/**
* Returns a list of all occurrences of the given tool on the system path.
*
* @returns Promise<string[]> the paths of the tool
*/
export async function findInPath(tool: string): Promise<string[]> {
if (!tool) {
throw new Error("parameter 'tool' is required")
}
// build the list of extensions to try
const extensions: string[] = []
if (ioUtil.IS_WINDOWS && process.env['PATHEXT']) {
for (const extension of process.env['PATHEXT'].split(path.delimiter)) {
if (extension) {
extensions.push(extension)
}
}
}
// if it's rooted, return it if exists. otherwise return empty.
if (ioUtil.isRooted(tool)) {
const filePath: string = await ioUtil.tryGetExecutablePath(tool, extensions)
if (filePath) {
return [filePath]
}
return []
}
// if any path separators, return empty
if (tool.includes(path.sep)) {
return []
}
// build the list of directories
//
// Note, technically "where" checks the current directory on Windows. From a toolkit perspective,
// it feels like we should not do this. Checking the current directory seems like more of a use
// case of a shell, and the which() function exposed by the toolkit should strive for consistency
// across platforms.
const directories: string[] = []
if (process.env.PATH) {
for (const p of process.env.PATH.split(path.delimiter)) {
if (p) {
directories.push(p)
}
}
}
// find all matches
const matches: string[] = []
for (const directory of directories) {
const filePath = await ioUtil.tryGetExecutablePath(
path.join(directory, tool),
extensions
)
if (filePath) {
matches.push(filePath)
}
}
return matches
} }
function readCopyOptions(options: CopyOptions): Required<CopyOptions> { function readCopyOptions(options: CopyOptions): Required<CopyOptions> {