diff --git a/README.md b/README.md index a4f4c7f..88b514d 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,11 @@ For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md). # Optional. Default is 'warn' 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' + follow-symbolic-links: + # Duration after which artifact will expire in days. 0 means using default retention. # Minimum 1 day. # Maximum 90 days unless changed from the repository settings page. diff --git a/__tests__/search.test.ts b/__tests__/search.test.ts index e0ab26d..9e44351 100644 --- a/__tests__/search.test.ts +++ b/__tests__/search.test.ts @@ -61,6 +61,18 @@ const lonelyFilePath = path.join( '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', () => { beforeAll(async () => { // 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(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: root/ @@ -136,6 +154,7 @@ describe('Search', () => { folder-j/ folder-k/ lonely-file.txt + folder-l/ (symbolic link to folder-i) 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 () => { const expectedRoot = path.join(root, 'folder-h') const searchPath = path.join(root, 'folder-h', '**/*lonely*') + const searchResult = await findFilesToUpload(searchPath) expect(searchResult.filesToUpload.length).toEqual(1) expect(searchResult.filesToUpload[0]).toEqual(lonelyFilePath) @@ -224,10 +270,33 @@ describe('Search', () => { 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 () => { const searchPath = path.join(root, '**/*[Ss]earch*') 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(searchItem2Path)).toEqual(true) @@ -249,6 +318,12 @@ describe('Search', () => { expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual( true ) + expect( + searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem4Path) + ).toEqual(true) + expect( + searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem5Path) + ).toEqual(true) expect(searchResult.rootDirectory).toEqual(root) }) @@ -261,7 +336,7 @@ describe('Search', () => { '**/*[Ss]earch*' ) 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(searchItem2Path)).toEqual(true) @@ -283,6 +358,12 @@ describe('Search', () => { expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual( true ) + expect( + searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem4Path) + ).toEqual(true) + expect( + searchResult.filesToUpload.includes(symbolicLinkExtraSearchItem5Path) + ).toEqual(true) expect(searchResult.rootDirectory).toEqual(root) }) diff --git a/action.yml b/action.yml index 38d4fdc..9372e25 100644 --- a/action.yml +++ b/action.yml @@ -17,6 +17,11 @@ inputs: error: Fail the action with an error message ignore: Do not output any warnings or errors, the action does not fail 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: description: > Duration after which artifact will expire in days. 0 means using default retention. diff --git a/dist/merge/index.js b/dist/merge/index.js index b3c69c9..b2ca93f 100644 --- a/dist/merge/index.js +++ b/dist/merge/index.js @@ -141018,7 +141018,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.findFilesToUpload = void 0; +exports.findFilesToUpload = exports.getGlobOptions = void 0; const glob = __importStar(__nccwpck_require__(28090)); const path = __importStar(__nccwpck_require__(71017)); const core_1 = __nccwpck_require__(42186); @@ -141033,6 +141033,10 @@ function getDefaultGlobOptions() { 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 * the delimiter to control the directory structure for the artifact. This function returns the LCA @@ -141089,6 +141093,7 @@ function findFilesToUpload(searchPath, globOptions) { const searchResults = []; const globber = yield glob.create(searchPath, globOptions || getDefaultGlobOptions()); 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 Detect any files that could be overwritten for user awareness diff --git a/dist/upload/index.js b/dist/upload/index.js index 8bf1b35..b29f51b 100644 --- a/dist/upload/index.js +++ b/dist/upload/index.js @@ -140566,7 +140566,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.findFilesToUpload = void 0; +exports.findFilesToUpload = exports.getGlobOptions = void 0; const glob = __importStar(__nccwpck_require__(28090)); const path = __importStar(__nccwpck_require__(71017)); const core_1 = __nccwpck_require__(42186); @@ -140581,6 +140581,10 @@ function getDefaultGlobOptions() { 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 * the delimiter to control the directory structure for the artifact. This function returns the LCA @@ -140637,6 +140641,7 @@ function findFilesToUpload(searchPath, globOptions) { const searchResults = []; const globber = yield glob.create(searchPath, globOptions || getDefaultGlobOptions()); 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 Detect any files that could be overwritten for user awareness @@ -140770,6 +140775,7 @@ var Inputs; Inputs["Name"] = "name"; Inputs["Path"] = "path"; Inputs["IfNoFilesFound"] = "if-no-files-found"; + Inputs["FollowSymbolicLinks"] = "follow-symbolic-links"; Inputs["RetentionDays"] = "retention-days"; Inputs["CompressionLevel"] = "compression-level"; Inputs["Overwrite"] = "overwrite"; @@ -140872,6 +140878,7 @@ function getInputs() { const overwrite = core.getBooleanInput(constants_1.Inputs.Overwrite); const ifNoFilesFound = core.getInput(constants_1.Inputs.IfNoFilesFound); const noFileBehavior = constants_1.NoFileOptions[ifNoFilesFound]; + const followSymbolicLinks = core.getBooleanInput(constants_1.Inputs.FollowSymbolicLinks); if (!noFileBehavior) { core.setFailed(`Unrecognized ${constants_1.Inputs.IfNoFilesFound} input. Provided: ${ifNoFilesFound}. Available options: ${Object.keys(constants_1.NoFileOptions)}`); } @@ -140879,6 +140886,7 @@ function getInputs() { artifactName: name, searchPath: path, ifNoFilesFound: noFileBehavior, + followSymbolicLinks: followSymbolicLinks, overwrite: overwrite }; const retentionDaysStr = core.getInput(constants_1.Inputs.RetentionDays); @@ -140968,7 +140976,7 @@ function deleteArtifactIfExists(artifactName) { function run() { return __awaiter(this, void 0, void 0, function* () { 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) { // No files were found, different use cases warrant different types of behavior if nothing is found switch (inputs.ifNoFilesFound) { diff --git a/src/shared/search.ts b/src/shared/search.ts index bd80164..2ea271d 100644 --- a/src/shared/search.ts +++ b/src/shared/search.ts @@ -19,6 +19,15 @@ function getDefaultGlobOptions(): glob.GlobOptions { } } +export function getGlobOptions( + followSymbolicLinks?: boolean +): glob.GlobOptions { + return { + ...getDefaultGlobOptions(), + followSymbolicLinks + } +} + /** * 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 diff --git a/src/upload/constants.ts b/src/upload/constants.ts index 272f842..6e86565 100644 --- a/src/upload/constants.ts +++ b/src/upload/constants.ts @@ -3,6 +3,7 @@ export enum Inputs { Name = 'name', Path = 'path', IfNoFilesFound = 'if-no-files-found', + FollowSymbolicLinks = 'follow-symbolic-links', RetentionDays = 'retention-days', CompressionLevel = 'compression-level', Overwrite = 'overwrite' diff --git a/src/upload/input-helper.ts b/src/upload/input-helper.ts index 3e24f25..093e0e5 100644 --- a/src/upload/input-helper.ts +++ b/src/upload/input-helper.ts @@ -13,6 +13,8 @@ export function getInputs(): UploadInputs { const ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound) const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound] + const followSymbolicLinks = core.getBooleanInput(Inputs.FollowSymbolicLinks) + if (!noFileBehavior) { core.setFailed( `Unrecognized ${ @@ -27,6 +29,7 @@ export function getInputs(): UploadInputs { artifactName: name, searchPath: path, ifNoFilesFound: noFileBehavior, + followSymbolicLinks: followSymbolicLinks, overwrite: overwrite } as UploadInputs diff --git a/src/upload/upload-artifact.ts b/src/upload/upload-artifact.ts index 8c77543..d193890 100644 --- a/src/upload/upload-artifact.ts +++ b/src/upload/upload-artifact.ts @@ -3,7 +3,7 @@ import artifact, { UploadArtifactOptions, ArtifactNotFoundError } from '@actions/artifact' -import {findFilesToUpload} from '../shared/search' +import {findFilesToUpload, getGlobOptions} from '../shared/search' import {getInputs} from './input-helper' import {NoFileOptions} from './constants' import {uploadArtifact} from '../shared/upload-artifact' @@ -24,7 +24,10 @@ async function deleteArtifactIfExists(artifactName: string): Promise { export async function run(): Promise { const inputs = getInputs() - const searchResult = await findFilesToUpload(inputs.searchPath) + const searchResult = await findFilesToUpload( + inputs.searchPath, + getGlobOptions(inputs.followSymbolicLinks) + ) if (searchResult.filesToUpload.length === 0) { // No files were found, different use cases warrant different types of behavior if nothing is found switch (inputs.ifNoFilesFound) { diff --git a/src/upload/upload-inputs.ts b/src/upload/upload-inputs.ts index 1e7a46f..2e5f16c 100644 --- a/src/upload/upload-inputs.ts +++ b/src/upload/upload-inputs.ts @@ -16,6 +16,11 @@ export interface UploadInputs { */ 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 */