Merge 26dd037a41465ba689818a0736e5325f5fdbbaaa into ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
This commit is contained in:
commit
9f3071bd12
@ -2,7 +2,7 @@ import * as core from '@actions/core'
|
|||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as io from '@actions/io'
|
import * as io from '@actions/io'
|
||||||
import {promises as fs} from 'fs'
|
import {promises as fs} from 'fs'
|
||||||
import {findFilesToUpload} from '../src/search'
|
import {findFilesToUpload, getDefaultGlobOptions} from '../src/search'
|
||||||
|
|
||||||
const root = path.join(__dirname, '_temp', 'search')
|
const root = path.join(__dirname, '_temp', 'search')
|
||||||
const searchItem1Path = path.join(
|
const searchItem1Path = path.join(
|
||||||
@ -110,6 +110,12 @@ describe('Search', () => {
|
|||||||
await fs.writeFile(amazingFileInFolderHPath, 'amazing file')
|
await fs.writeFile(amazingFileInFolderHPath, 'amazing file')
|
||||||
|
|
||||||
await fs.writeFile(lonelyFilePath, 'all by itself')
|
await fs.writeFile(lonelyFilePath, 'all by itself')
|
||||||
|
|
||||||
|
await fs.symlink(
|
||||||
|
path.join(root, 'folder-d'),
|
||||||
|
path.join(root, 'symlink-to-folder-d')
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Directory structure of files that get created:
|
Directory structure of files that get created:
|
||||||
root/
|
root/
|
||||||
@ -136,6 +142,7 @@ describe('Search', () => {
|
|||||||
folder-j/
|
folder-j/
|
||||||
folder-k/
|
folder-k/
|
||||||
lonely-file.txt
|
lonely-file.txt
|
||||||
|
symlink-to-folder-d/ -> ./folder-d/
|
||||||
search-item5.txt
|
search-item5.txt
|
||||||
*/
|
*/
|
||||||
})
|
})
|
||||||
@ -227,7 +234,8 @@ describe('Search', () => {
|
|||||||
it('Wildcard search - Absolute Path', async () => {
|
it('Wildcard search - Absolute Path', async () => {
|
||||||
const searchPath = path.join(root, '**/*[Ss]earch*')
|
const searchPath = path.join(root, '**/*[Ss]earch*')
|
||||||
const searchResult = await findFilesToUpload(searchPath)
|
const searchResult = await findFilesToUpload(searchPath)
|
||||||
expect(searchResult.filesToUpload.length).toEqual(10)
|
// folder-d items included twice because symlink is followed by default
|
||||||
|
expect(searchResult.filesToUpload.length).toEqual(14)
|
||||||
|
|
||||||
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
|
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
|
||||||
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
|
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
|
||||||
@ -261,7 +269,8 @@ describe('Search', () => {
|
|||||||
'**/*[Ss]earch*'
|
'**/*[Ss]earch*'
|
||||||
)
|
)
|
||||||
const searchResult = await findFilesToUpload(searchPath)
|
const searchResult = await findFilesToUpload(searchPath)
|
||||||
expect(searchResult.filesToUpload.length).toEqual(10)
|
// folder-d items included twice because symlink is followed by default
|
||||||
|
expect(searchResult.filesToUpload.length).toEqual(14)
|
||||||
|
|
||||||
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
|
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
|
||||||
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
|
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
|
||||||
@ -352,4 +361,15 @@ describe('Search', () => {
|
|||||||
)
|
)
|
||||||
expect(searchResult.filesToUpload.includes(lonelyFilePath)).toEqual(true)
|
expect(searchResult.filesToUpload.includes(lonelyFilePath)).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Declines to follow symlinks when requested', async () => {
|
||||||
|
const searchPath = path.join(root, 'symlink-to-folder-d')
|
||||||
|
const globOptions = {
|
||||||
|
...getDefaultGlobOptions(),
|
||||||
|
followSymbolicLinks: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResult = await findFilesToUpload(searchPath, globOptions)
|
||||||
|
expect(searchResult.filesToUpload.length).toEqual(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -23,6 +23,12 @@ inputs:
|
|||||||
|
|
||||||
Minimum 1 day.
|
Minimum 1 day.
|
||||||
Maximum 90 days unless changed from the repository settings page.
|
Maximum 90 days unless changed from the repository settings page.
|
||||||
|
follow-symlinks:
|
||||||
|
description: >
|
||||||
|
Whether symbolic links should be followed and expanded when building the set of files to be
|
||||||
|
archived (true), or if symbolic links should be included in the archived artifact verbatim
|
||||||
|
(false).
|
||||||
|
default: true
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
20
dist/index.js
vendored
20
dist/index.js
vendored
@ -4028,7 +4028,8 @@ function run() {
|
|||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
try {
|
try {
|
||||||
const inputs = input_helper_1.getInputs();
|
const inputs = input_helper_1.getInputs();
|
||||||
const searchResult = yield search_1.findFilesToUpload(inputs.searchPath);
|
const globOptions = Object.assign(Object.assign({}, search_1.getDefaultGlobOptions()), { followSymbolicLinks: inputs.followSymlinks });
|
||||||
|
const searchResult = yield search_1.findFilesToUpload(inputs.searchPath, globOptions);
|
||||||
if (searchResult.filesToUpload.length === 0) {
|
if (searchResult.filesToUpload.length === 0) {
|
||||||
// No files were found, different use cases warrant different types of behavior if nothing is found
|
// No files were found, different use cases warrant different types of behavior if nothing is found
|
||||||
switch (inputs.ifNoFilesFound) {
|
switch (inputs.ifNoFilesFound) {
|
||||||
@ -6440,6 +6441,7 @@ function getDefaultGlobOptions() {
|
|||||||
omitBrokenSymbolicLinks: true
|
omitBrokenSymbolicLinks: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
exports.getDefaultGlobOptions = getDefaultGlobOptions;
|
||||||
/**
|
/**
|
||||||
* If multiple paths are specific, the least common ancestor (LCA) of the search paths is used as
|
* If multiple paths are specific, the least common ancestor (LCA) of the search paths is used as
|
||||||
* the delimiter to control the directory structure for the artifact. This function returns the LCA
|
* the delimiter to control the directory structure for the artifact. This function returns the LCA
|
||||||
@ -6494,7 +6496,8 @@ function getMultiPathLCA(searchPaths) {
|
|||||||
function findFilesToUpload(searchPath, globOptions) {
|
function findFilesToUpload(searchPath, globOptions) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const searchResults = [];
|
const searchResults = [];
|
||||||
const globber = yield glob.create(searchPath, globOptions || getDefaultGlobOptions());
|
const resolvedGlobOptions = globOptions || getDefaultGlobOptions();
|
||||||
|
const globber = yield glob.create(searchPath, resolvedGlobOptions);
|
||||||
const rawSearchResults = yield globber.glob();
|
const rawSearchResults = yield globber.glob();
|
||||||
/*
|
/*
|
||||||
Files are saved with case insensitivity. Uploading both a.txt and A.txt will files to be overwritten
|
Files are saved with case insensitivity. Uploading both a.txt and A.txt will files to be overwritten
|
||||||
@ -6506,8 +6509,11 @@ function findFilesToUpload(searchPath, globOptions) {
|
|||||||
directories so filter any directories out from the raw search results
|
directories so filter any directories out from the raw search results
|
||||||
*/
|
*/
|
||||||
for (const searchResult of rawSearchResults) {
|
for (const searchResult of rawSearchResults) {
|
||||||
const fileStats = yield stats(searchResult);
|
/* isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
||||||
// isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
* if we're following symlinks so that stat follows the symlink too */
|
||||||
|
const fileStats = resolvedGlobOptions.followSymbolicLinks
|
||||||
|
? yield stats(searchResult)
|
||||||
|
: yield fs_1.promises.lstat(searchResult);
|
||||||
if (!fileStats.isDirectory()) {
|
if (!fileStats.isDirectory()) {
|
||||||
core_1.debug(`File:${searchResult} was found using the provided searchPath`);
|
core_1.debug(`File:${searchResult} was found using the provided searchPath`);
|
||||||
searchResults.push(searchResult);
|
searchResults.push(searchResult);
|
||||||
@ -6577,6 +6583,8 @@ function getInputs() {
|
|||||||
const name = core.getInput(constants_1.Inputs.Name);
|
const name = core.getInput(constants_1.Inputs.Name);
|
||||||
const path = core.getInput(constants_1.Inputs.Path, { required: true });
|
const path = core.getInput(constants_1.Inputs.Path, { required: true });
|
||||||
const ifNoFilesFound = core.getInput(constants_1.Inputs.IfNoFilesFound);
|
const ifNoFilesFound = core.getInput(constants_1.Inputs.IfNoFilesFound);
|
||||||
|
// getBooleanInput is not released yet :(
|
||||||
|
const followSymlinks = core.getInput(constants_1.Inputs.FollowSymlinks).toLowerCase() == 'true';
|
||||||
const noFileBehavior = constants_1.NoFileOptions[ifNoFilesFound];
|
const noFileBehavior = constants_1.NoFileOptions[ifNoFilesFound];
|
||||||
if (!noFileBehavior) {
|
if (!noFileBehavior) {
|
||||||
core.setFailed(`Unrecognized ${constants_1.Inputs.IfNoFilesFound} input. Provided: ${ifNoFilesFound}. Available options: ${Object.keys(constants_1.NoFileOptions)}`);
|
core.setFailed(`Unrecognized ${constants_1.Inputs.IfNoFilesFound} input. Provided: ${ifNoFilesFound}. Available options: ${Object.keys(constants_1.NoFileOptions)}`);
|
||||||
@ -6584,7 +6592,8 @@ function getInputs() {
|
|||||||
const inputs = {
|
const inputs = {
|
||||||
artifactName: name,
|
artifactName: name,
|
||||||
searchPath: path,
|
searchPath: path,
|
||||||
ifNoFilesFound: noFileBehavior
|
ifNoFilesFound: noFileBehavior,
|
||||||
|
followSymlinks
|
||||||
};
|
};
|
||||||
const retentionDaysStr = core.getInput(constants_1.Inputs.RetentionDays);
|
const retentionDaysStr = core.getInput(constants_1.Inputs.RetentionDays);
|
||||||
if (retentionDaysStr) {
|
if (retentionDaysStr) {
|
||||||
@ -7521,6 +7530,7 @@ var Inputs;
|
|||||||
Inputs["Path"] = "path";
|
Inputs["Path"] = "path";
|
||||||
Inputs["IfNoFilesFound"] = "if-no-files-found";
|
Inputs["IfNoFilesFound"] = "if-no-files-found";
|
||||||
Inputs["RetentionDays"] = "retention-days";
|
Inputs["RetentionDays"] = "retention-days";
|
||||||
|
Inputs["FollowSymlinks"] = "follow-symlinks";
|
||||||
})(Inputs = exports.Inputs || (exports.Inputs = {}));
|
})(Inputs = exports.Inputs || (exports.Inputs = {}));
|
||||||
var NoFileOptions;
|
var NoFileOptions;
|
||||||
(function (NoFileOptions) {
|
(function (NoFileOptions) {
|
||||||
|
12329
package-lock.json
generated
12329
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/actions/upload-artifact#readme",
|
"homepage": "https://github.com/actions/upload-artifact#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/artifact": "^0.5.1",
|
"@actions/artifact": "https://github.com/fourieraudio/toolkit-artifact/tarball/main",
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/glob": "^0.1.0",
|
"@actions/glob": "^0.1.0",
|
||||||
"@actions/io": "^1.0.2"
|
"@actions/io": "^1.0.2"
|
||||||
|
@ -2,7 +2,8 @@ export enum Inputs {
|
|||||||
Name = 'name',
|
Name = 'name',
|
||||||
Path = 'path',
|
Path = 'path',
|
||||||
IfNoFilesFound = 'if-no-files-found',
|
IfNoFilesFound = 'if-no-files-found',
|
||||||
RetentionDays = 'retention-days'
|
RetentionDays = 'retention-days',
|
||||||
|
FollowSymlinks = 'follow-symlinks'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NoFileOptions {
|
export enum NoFileOptions {
|
||||||
|
@ -10,6 +10,11 @@ export function getInputs(): UploadInputs {
|
|||||||
const path = core.getInput(Inputs.Path, {required: true})
|
const path = core.getInput(Inputs.Path, {required: true})
|
||||||
|
|
||||||
const ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound)
|
const ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound)
|
||||||
|
|
||||||
|
// getBooleanInput is not released yet :(
|
||||||
|
const followSymlinks =
|
||||||
|
core.getInput(Inputs.FollowSymlinks).toLowerCase() == 'true'
|
||||||
|
|
||||||
const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound]
|
const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound]
|
||||||
|
|
||||||
if (!noFileBehavior) {
|
if (!noFileBehavior) {
|
||||||
@ -25,7 +30,8 @@ export function getInputs(): UploadInputs {
|
|||||||
const inputs = {
|
const inputs = {
|
||||||
artifactName: name,
|
artifactName: name,
|
||||||
searchPath: path,
|
searchPath: path,
|
||||||
ifNoFilesFound: noFileBehavior
|
ifNoFilesFound: noFileBehavior,
|
||||||
|
followSymlinks
|
||||||
} as UploadInputs
|
} as UploadInputs
|
||||||
|
|
||||||
const retentionDaysStr = core.getInput(Inputs.RetentionDays)
|
const retentionDaysStr = core.getInput(Inputs.RetentionDays)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as glob from '@actions/glob'
|
import * as glob from '@actions/glob'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import {debug, info} from '@actions/core'
|
import {debug, info} from '@actions/core'
|
||||||
import {stat} from 'fs'
|
import {promises as fsPromises, stat} from 'fs'
|
||||||
import {dirname} from 'path'
|
import {dirname} from 'path'
|
||||||
import {promisify} from 'util'
|
import {promisify} from 'util'
|
||||||
const stats = promisify(stat)
|
const stats = promisify(stat)
|
||||||
@ -11,7 +11,7 @@ export interface SearchResult {
|
|||||||
rootDirectory: string
|
rootDirectory: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultGlobOptions(): glob.GlobOptions {
|
export function getDefaultGlobOptions(): glob.GlobOptions {
|
||||||
return {
|
return {
|
||||||
followSymbolicLinks: true,
|
followSymbolicLinks: true,
|
||||||
implicitDescendants: true,
|
implicitDescendants: true,
|
||||||
@ -83,10 +83,8 @@ export async function findFilesToUpload(
|
|||||||
globOptions?: glob.GlobOptions
|
globOptions?: glob.GlobOptions
|
||||||
): Promise<SearchResult> {
|
): Promise<SearchResult> {
|
||||||
const searchResults: string[] = []
|
const searchResults: string[] = []
|
||||||
const globber = await glob.create(
|
const resolvedGlobOptions = globOptions || getDefaultGlobOptions()
|
||||||
searchPath,
|
const globber = await glob.create(searchPath, resolvedGlobOptions)
|
||||||
globOptions || getDefaultGlobOptions()
|
|
||||||
)
|
|
||||||
const rawSearchResults: string[] = await globber.glob()
|
const rawSearchResults: string[] = await globber.glob()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -100,8 +98,12 @@ export async function findFilesToUpload(
|
|||||||
directories so filter any directories out from the raw search results
|
directories so filter any directories out from the raw search results
|
||||||
*/
|
*/
|
||||||
for (const searchResult of rawSearchResults) {
|
for (const searchResult of rawSearchResults) {
|
||||||
const fileStats = await stats(searchResult)
|
/* isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
||||||
// isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
* if we're following symlinks so that stat follows the symlink too */
|
||||||
|
const fileStats = resolvedGlobOptions.followSymbolicLinks
|
||||||
|
? await stats(searchResult)
|
||||||
|
: await fsPromises.lstat(searchResult)
|
||||||
|
|
||||||
if (!fileStats.isDirectory()) {
|
if (!fileStats.isDirectory()) {
|
||||||
debug(`File:${searchResult} was found using the provided searchPath`)
|
debug(`File:${searchResult} was found using the provided searchPath`)
|
||||||
searchResults.push(searchResult)
|
searchResults.push(searchResult)
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {create, UploadOptions} from '@actions/artifact'
|
import {create, UploadOptions} from '@actions/artifact'
|
||||||
import {findFilesToUpload} from './search'
|
import {findFilesToUpload, getDefaultGlobOptions} from './search'
|
||||||
import {getInputs} from './input-helper'
|
import {getInputs} from './input-helper'
|
||||||
import {NoFileOptions} from './constants'
|
import {NoFileOptions} from './constants'
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const inputs = getInputs()
|
const inputs = getInputs()
|
||||||
const searchResult = await findFilesToUpload(inputs.searchPath)
|
const globOptions = {
|
||||||
|
...getDefaultGlobOptions(),
|
||||||
|
followSymbolicLinks: inputs.followSymlinks
|
||||||
|
}
|
||||||
|
const searchResult = await findFilesToUpload(inputs.searchPath, globOptions)
|
||||||
|
|
||||||
if (searchResult.filesToUpload.length === 0) {
|
if (searchResult.filesToUpload.length === 0) {
|
||||||
// No files were found, different use cases warrant different types of behavior if nothing is found
|
// No files were found, different use cases warrant different types of behavior if nothing is found
|
||||||
switch (inputs.ifNoFilesFound) {
|
switch (inputs.ifNoFilesFound) {
|
||||||
|
@ -20,4 +20,11 @@ export interface UploadInputs {
|
|||||||
* Duration after which artifact will expire in days
|
* Duration after which artifact will expire in days
|
||||||
*/
|
*/
|
||||||
retentionDays: number
|
retentionDays: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether symbolic links should be followed and expanded when building the set of files to be
|
||||||
|
* archived (true), or if symbolic links should be included in the archived artifact verbatim
|
||||||
|
* (false).
|
||||||
|
*/
|
||||||
|
followSymlinks: boolean
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user