mirror of https://github.com/actions/toolkit
Add findInPath method to locate all matching executables in the system path (#609)
Signed-off-by: Sora Morimoto <sora@morimoto.io>pull/610/head
parent
de122731f3
commit
bd9017e99f
|
@ -1,9 +1,13 @@
|
|||
# @actions/io Releases
|
||||
|
||||
### 1.1.0
|
||||
|
||||
- Add `findInPath` method to locate all matching executables in the system path
|
||||
|
||||
### 1.0.2
|
||||
|
||||
- [Add \"types\" to package.json](https://github.com/actions/toolkit/pull/221)
|
||||
|
||||
### 1.0.0
|
||||
|
||||
- Initial release
|
||||
- Initial release
|
||||
|
|
|
@ -6,6 +6,10 @@ import * as io from '../src/io'
|
|||
import * as ioUtil from '../src/io-util'
|
||||
|
||||
describe('cp', () => {
|
||||
beforeAll(async () => {
|
||||
await io.rmRF(getTestTemp())
|
||||
})
|
||||
|
||||
it('copies file with no flags', async () => {
|
||||
const root = path.join(getTestTemp(), 'cp_with_no_flags')
|
||||
const sourceFile = path.join(root, 'cp_source')
|
||||
|
@ -166,6 +170,10 @@ describe('cp', () => {
|
|||
})
|
||||
|
||||
describe('mv', () => {
|
||||
beforeAll(async () => {
|
||||
await io.rmRF(getTestTemp())
|
||||
})
|
||||
|
||||
it('moves file with no flags', async () => {
|
||||
const root = path.join(getTestTemp(), ' mv_with_no_flags')
|
||||
const sourceFile = path.join(root, ' mv_source')
|
||||
|
@ -264,6 +272,10 @@ describe('mv', () => {
|
|||
})
|
||||
|
||||
describe('rmRF', () => {
|
||||
beforeAll(async () => {
|
||||
await io.rmRF(getTestTemp())
|
||||
})
|
||||
|
||||
it('removes single folder with rmRF', async () => {
|
||||
const testPath = path.join(getTestTemp(), 'testFolder')
|
||||
|
||||
|
@ -841,6 +853,10 @@ describe('mkdirP', () => {
|
|||
})
|
||||
|
||||
describe('which', () => {
|
||||
beforeAll(async () => {
|
||||
await io.rmRF(getTestTemp())
|
||||
})
|
||||
|
||||
it('which() finds file name', async () => {
|
||||
// create a executable file
|
||||
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(
|
||||
chmodOptions: string
|
||||
): Promise<void> {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@actions/io",
|
||||
"version": "1.0.2",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 1
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@actions/io",
|
||||
"version": "1.0.2",
|
||||
"version": "1.1.0",
|
||||
"description": "Actions io lib",
|
||||
"keywords": [
|
||||
"github",
|
||||
|
|
|
@ -192,69 +192,85 @@ export async function which(tool: string, check?: boolean): Promise<string> {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
try {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
const matches: string[] = await findInPath(tool)
|
||||
|
||||
// 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('/') || (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}`)
|
||||
if (matches && matches.length > 0) {
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
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> {
|
||||
|
|
Loading…
Reference in New Issue