Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/io/RELEASES.md
Original file line numberDiff line numberDiff line change
@@ -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
63 changes: 63 additions & 0 deletions packages/io/__tests__/io.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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')
Expand DownExpand Up@@ -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')
Expand DownExpand Up@@ -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')

Expand DownExpand Up@@ -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')
Expand DownExpand Up@@ -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> {
Expand Down
2 changes: 1 addition & 1 deletion packages/io/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/io/package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "@actions/io",
"version": "1.0.2",
"version": "1.1.0",
"description": "Actions io lib",
"keywords": [
"github",
Expand Down
114 changes: 65 additions & 49 deletions packages/io/src/io.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 (matches && matches.length > 0) {
return matches[0]
}

if (filePath) {
return filePath
}
return ''
}

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")
}

// if any path separators, return empty
if (tool.includes('/') || (ioUtil.IS_WINDOWS && tool.includes('\\'))) {
return ''
// 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)
}
}
}

// 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)
}
}
// 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 the first match
for (const directory of directories) {
const filePath = await ioUtil.tryGetExecutablePath(
directory + path.sep + tool,
extensions
)
if (filePath) {
return filePath
return []
}

// if any path separators, return empty
if (tool.includes(path.sep)) {
Comment thread
smorimoto marked this conversation as resolved.
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[] = []

return ''
} catch (err) {
throw new Error(`which failed with message ${err.message}`)
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> {
Expand Down