add follow symbolic links options

This commit is contained in:
Pepe Franco 2024-03-15 16:27:05 +11:00
parent ef09cdac3e
commit 264caab452
No known key found for this signature in database
GPG Key ID: E08F34018F21BD9C
10 changed files with 133 additions and 7 deletions

View File

@ -84,6 +84,11 @@ For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
# Optional. Default is 'warn' # Optional. Default is 'warn'
if-no-files-found: if-no-files-found:
# If true, symlink directories will be used to search files.
# If false, symlink directories will be ignored to search files.
# Optional. Default is 'true'
followSymbolicLinks:
# Duration after which artifact will expire in days. 0 means using default retention. # Duration after which artifact will expire in days. 0 means using default retention.
# 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.

View File

@ -61,6 +61,18 @@ const lonelyFilePath = path.join(
'lonely-file.txt' 'lonely-file.txt'
) )
const symbolicLinkExtraSearchItem4Path = path.join(
root,
'folder-l',
'extraSearch-item4.txt'
)
const symbolicLinkExtraSearchItem5Path = path.join(
root,
'folder-l',
'extraSearch-item5.txt'
)
describe('Search', () => { describe('Search', () => {
beforeAll(async () => { beforeAll(async () => {
// mock all output so that there is less noise when running tests // mock all output so that there is less noise when running tests
@ -110,6 +122,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-h', 'folder-i'),
path.join(root, 'folder-l'),
'dir'
)
/* /*
Directory structure of files that get created: Directory structure of files that get created:
root/ root/
@ -136,6 +154,7 @@ describe('Search', () => {
folder-j/ folder-j/
folder-k/ folder-k/
lonely-file.txt lonely-file.txt
folder-l/ (symbolic link to folder-i)
search-item5.txt search-item5.txt
*/ */
}) })
@ -168,9 +187,36 @@ describe('Search', () => {
) )
}) })
it('Single file search - Symbolic Link', async () => {
const relativePath = path.join(
'__tests__',
'_temp',
'search',
'folder-l',
'extraSearch-item4.txt'
)
const searchResult = await findFilesToUpload(relativePath)
expect(searchResult.filesToUpload.length).toEqual(1)
expect(searchResult.filesToUpload[0]).toEqual(
symbolicLinkExtraSearchItem4Path
)
expect(searchResult.rootDirectory).toEqual(path.join(root, 'folder-l'))
})
it('Single file search - Symbolic Link - Ignore symbolic links', async () => {
const relativePath = path.join('__tests__', '_temp', 'search', 'folder-l')
const searchResult = await findFilesToUpload(relativePath, {
followSymbolicLinks: false
})
expect(searchResult.filesToUpload.length).toEqual(0)
})
it('Single file using wildcard', async () => { it('Single file using wildcard', async () => {
const expectedRoot = path.join(root, 'folder-h') const expectedRoot = path.join(root, 'folder-h')
const searchPath = path.join(root, 'folder-h', '**/*lonely*') const searchPath = path.join(root, 'folder-h', '**/*lonely*')
const searchResult = await findFilesToUpload(searchPath) const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(1) expect(searchResult.filesToUpload.length).toEqual(1)
expect(searchResult.filesToUpload[0]).toEqual(lonelyFilePath) expect(searchResult.filesToUpload[0]).toEqual(lonelyFilePath)
@ -224,10 +270,33 @@ describe('Search', () => {
expect(searchResult.rootDirectory).toEqual(expectedRootDirectory) expect(searchResult.rootDirectory).toEqual(expectedRootDirectory)
}) })
it('Directory search - Absolute Path - Symbolic Link', async () => {
const relativePath = path.join('__tests__', '_temp', 'search', 'folder-l')
const searchResult = await findFilesToUpload(relativePath)
expect(searchResult.filesToUpload.length).toEqual(2)
expect(
searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem4Path)
).toEqual(true)
expect(
searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem5Path)
).toEqual(true)
})
it('Directory search - Absolute Path - Symbolic Link - Ignore symbolic links', async () => {
const relativePath = path.join('__tests__', '_temp', 'search', 'folder-l')
const searchResult = await findFilesToUpload(relativePath, {
followSymbolicLinks: false
})
expect(searchResult.filesToUpload.length).toEqual(0)
})
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) expect(searchResult.filesToUpload.length).toEqual(12)
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)
@ -249,6 +318,12 @@ describe('Search', () => {
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual( expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true true
) )
expect(
searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem4Path)
).toEqual(true)
expect(
searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem5Path)
).toEqual(true)
expect(searchResult.rootDirectory).toEqual(root) expect(searchResult.rootDirectory).toEqual(root)
}) })
@ -261,7 +336,7 @@ describe('Search', () => {
'**/*[Ss]earch*' '**/*[Ss]earch*'
) )
const searchResult = await findFilesToUpload(searchPath) const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(10) expect(searchResult.filesToUpload.length).toEqual(12)
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)
@ -283,6 +358,12 @@ describe('Search', () => {
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual( expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true true
) )
expect(
searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem4Path)
).toEqual(true)
expect(
searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem5Path)
).toEqual(true)
expect(searchResult.rootDirectory).toEqual(root) expect(searchResult.rootDirectory).toEqual(root)
}) })

View File

@ -17,6 +17,11 @@ inputs:
error: Fail the action with an error message error: Fail the action with an error message
ignore: Do not output any warnings or errors, the action does not fail ignore: Do not output any warnings or errors, the action does not fail
default: 'warn' default: 'warn'
follow-symbolic-links:
description: >
If true, symlink directories will be used to search files.
If false, symlink directories will be ignored to search files.
default: 'true'
retention-days: retention-days:
description: > description: >
Duration after which artifact will expire in days. 0 means using default retention. Duration after which artifact will expire in days. 0 means using default retention.

7
dist/merge/index.js vendored
View File

@ -129675,7 +129675,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}); });
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.findFilesToUpload = void 0; exports.findFilesToUpload = exports.getGlobOptions = void 0;
const glob = __importStar(__nccwpck_require__(28090)); const glob = __importStar(__nccwpck_require__(28090));
const path = __importStar(__nccwpck_require__(71017)); const path = __importStar(__nccwpck_require__(71017));
const core_1 = __nccwpck_require__(42186); const core_1 = __nccwpck_require__(42186);
@ -129690,6 +129690,10 @@ function getDefaultGlobOptions() {
omitBrokenSymbolicLinks: true omitBrokenSymbolicLinks: true
}; };
} }
function getGlobOptions(followSymbolicLinks) {
return Object.assign({ followSymbolicLinks }, getDefaultGlobOptions());
}
exports.getGlobOptions = getGlobOptions;
/** /**
* 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
@ -129746,6 +129750,7 @@ function findFilesToUpload(searchPath, globOptions) {
const searchResults = []; const searchResults = [];
const globber = yield glob.create(searchPath, globOptions || getDefaultGlobOptions()); const globber = yield glob.create(searchPath, globOptions || getDefaultGlobOptions());
const rawSearchResults = yield globber.glob(); const rawSearchResults = yield globber.glob();
console.log(rawSearchResults);
/* /*
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
Detect any files that could be overwritten for user awareness Detect any files that could be overwritten for user awareness

12
dist/upload/index.js vendored
View File

@ -129433,7 +129433,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}); });
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.findFilesToUpload = void 0; exports.findFilesToUpload = exports.getGlobOptions = void 0;
const glob = __importStar(__nccwpck_require__(28090)); const glob = __importStar(__nccwpck_require__(28090));
const path = __importStar(__nccwpck_require__(71017)); const path = __importStar(__nccwpck_require__(71017));
const core_1 = __nccwpck_require__(42186); const core_1 = __nccwpck_require__(42186);
@ -129448,6 +129448,10 @@ function getDefaultGlobOptions() {
omitBrokenSymbolicLinks: true omitBrokenSymbolicLinks: true
}; };
} }
function getGlobOptions(followSymbolicLinks) {
return Object.assign({ followSymbolicLinks }, getDefaultGlobOptions());
}
exports.getGlobOptions = getGlobOptions;
/** /**
* 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
@ -129504,6 +129508,7 @@ function findFilesToUpload(searchPath, globOptions) {
const searchResults = []; const searchResults = [];
const globber = yield glob.create(searchPath, globOptions || getDefaultGlobOptions()); const globber = yield glob.create(searchPath, globOptions || getDefaultGlobOptions());
const rawSearchResults = yield globber.glob(); const rawSearchResults = yield globber.glob();
console.log(rawSearchResults);
/* /*
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
Detect any files that could be overwritten for user awareness Detect any files that could be overwritten for user awareness
@ -129637,6 +129642,7 @@ var Inputs;
Inputs["Name"] = "name"; Inputs["Name"] = "name";
Inputs["Path"] = "path"; Inputs["Path"] = "path";
Inputs["IfNoFilesFound"] = "if-no-files-found"; Inputs["IfNoFilesFound"] = "if-no-files-found";
Inputs["FollowSymbolicLinks"] = "follow-symbolic-links";
Inputs["RetentionDays"] = "retention-days"; Inputs["RetentionDays"] = "retention-days";
Inputs["CompressionLevel"] = "compression-level"; Inputs["CompressionLevel"] = "compression-level";
Inputs["Overwrite"] = "overwrite"; Inputs["Overwrite"] = "overwrite";
@ -129739,6 +129745,7 @@ function getInputs() {
const overwrite = core.getBooleanInput(constants_1.Inputs.Overwrite); const overwrite = core.getBooleanInput(constants_1.Inputs.Overwrite);
const ifNoFilesFound = core.getInput(constants_1.Inputs.IfNoFilesFound); const ifNoFilesFound = core.getInput(constants_1.Inputs.IfNoFilesFound);
const noFileBehavior = constants_1.NoFileOptions[ifNoFilesFound]; const noFileBehavior = constants_1.NoFileOptions[ifNoFilesFound];
const followSymbolicLinks = core.getBooleanInput(constants_1.Inputs.FollowSymbolicLinks);
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)}`);
} }
@ -129746,6 +129753,7 @@ function getInputs() {
artifactName: name, artifactName: name,
searchPath: path, searchPath: path,
ifNoFilesFound: noFileBehavior, ifNoFilesFound: noFileBehavior,
followSymbolicLinks: followSymbolicLinks,
overwrite: overwrite overwrite: overwrite
}; };
const retentionDaysStr = core.getInput(constants_1.Inputs.RetentionDays); const retentionDaysStr = core.getInput(constants_1.Inputs.RetentionDays);
@ -129835,7 +129843,7 @@ function deleteArtifactIfExists(artifactName) {
function run() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const inputs = (0, input_helper_1.getInputs)(); const inputs = (0, input_helper_1.getInputs)();
const searchResult = yield (0, search_1.findFilesToUpload)(inputs.searchPath); const searchResult = yield (0, search_1.findFilesToUpload)(inputs.searchPath, (0, search_1.getGlobOptions)(inputs.followSymbolicLinks));
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) {

View File

@ -19,6 +19,15 @@ function getDefaultGlobOptions(): glob.GlobOptions {
} }
} }
export function getGlobOptions(
followSymbolicLinks?: boolean
): glob.GlobOptions {
return {
followSymbolicLinks,
...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
@ -88,6 +97,7 @@ export async function findFilesToUpload(
globOptions || getDefaultGlobOptions() globOptions || getDefaultGlobOptions()
) )
const rawSearchResults: string[] = await globber.glob() const rawSearchResults: string[] = await globber.glob()
console.log(rawSearchResults)
/* /*
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

View File

@ -3,6 +3,7 @@ export enum Inputs {
Name = 'name', Name = 'name',
Path = 'path', Path = 'path',
IfNoFilesFound = 'if-no-files-found', IfNoFilesFound = 'if-no-files-found',
FollowSymbolicLinks = 'follow-symbolic-links',
RetentionDays = 'retention-days', RetentionDays = 'retention-days',
CompressionLevel = 'compression-level', CompressionLevel = 'compression-level',
Overwrite = 'overwrite' Overwrite = 'overwrite'

View File

@ -13,6 +13,8 @@ export function getInputs(): UploadInputs {
const ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound) const ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound)
const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound] const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound]
const followSymbolicLinks = core.getBooleanInput(Inputs.FollowSymbolicLinks)
if (!noFileBehavior) { if (!noFileBehavior) {
core.setFailed( core.setFailed(
`Unrecognized ${ `Unrecognized ${
@ -27,6 +29,7 @@ export function getInputs(): UploadInputs {
artifactName: name, artifactName: name,
searchPath: path, searchPath: path,
ifNoFilesFound: noFileBehavior, ifNoFilesFound: noFileBehavior,
followSymbolicLinks: followSymbolicLinks,
overwrite: overwrite overwrite: overwrite
} as UploadInputs } as UploadInputs

View File

@ -3,7 +3,7 @@ import artifact, {
UploadArtifactOptions, UploadArtifactOptions,
ArtifactNotFoundError ArtifactNotFoundError
} from '@actions/artifact' } from '@actions/artifact'
import {findFilesToUpload} from '../shared/search' import {findFilesToUpload, getGlobOptions} from '../shared/search'
import {getInputs} from './input-helper' import {getInputs} from './input-helper'
import {NoFileOptions} from './constants' import {NoFileOptions} from './constants'
import {uploadArtifact} from '../shared/upload-artifact' import {uploadArtifact} from '../shared/upload-artifact'
@ -24,7 +24,10 @@ async function deleteArtifactIfExists(artifactName: string): Promise<void> {
export async function run(): Promise<void> { export async function run(): Promise<void> {
const inputs = getInputs() const inputs = getInputs()
const searchResult = await findFilesToUpload(inputs.searchPath) const searchResult = await findFilesToUpload(
inputs.searchPath,
getGlobOptions(inputs.followSymbolicLinks)
)
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) {

View File

@ -16,6 +16,11 @@ export interface UploadInputs {
*/ */
ifNoFilesFound: NoFileOptions ifNoFilesFound: NoFileOptions
/**
* Wether or not to follow symbolic links when searching for files to upload
*/
followSymbolicLinks?: boolean
/** /**
* Duration after which artifact will expire in days * Duration after which artifact will expire in days
*/ */