Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement follow-symlinks boolean action input #201

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
26 changes: 23 additions & 3 deletions __tests__/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as core from '@actions/core'
import * as path from 'path'
import * as io from '@actions/io'
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 searchItem1Path = path.join(
Expand Down Expand Up @@ -110,6 +110,12 @@ describe('Search', () => {
await fs.writeFile(amazingFileInFolderHPath, 'amazing file')

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:
root/
Expand All @@ -136,6 +142,7 @@ describe('Search', () => {
folder-j/
folder-k/
lonely-file.txt
symlink-to-folder-d/ -> ./folder-d/
search-item5.txt
*/
})
Expand Down Expand Up @@ -227,7 +234,8 @@ describe('Search', () => {
it('Wildcard search - Absolute Path', async () => {
const searchPath = path.join(root, '**/*[Ss]earch*')
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(searchItem2Path)).toEqual(true)
Expand Down Expand Up @@ -261,7 +269,8 @@ describe('Search', () => {
'**/*[Ss]earch*'
)
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(searchItem2Path)).toEqual(true)
Expand Down Expand Up @@ -352,4 +361,15 @@ describe('Search', () => {
)
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)
})
})
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ inputs:
Minimum 1 day.
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:
using: 'node12'
main: 'dist/index.js'
20 changes: 15 additions & 5 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4028,7 +4028,8 @@ function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
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) {
// No files were found, different use cases warrant different types of behavior if nothing is found
switch (inputs.ifNoFilesFound) {
Expand Down Expand Up @@ -6440,6 +6441,7 @@ function getDefaultGlobOptions() {
omitBrokenSymbolicLinks: true
};
}
exports.getDefaultGlobOptions = getDefaultGlobOptions;
/**
* 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
Expand Down Expand Up @@ -6494,7 +6496,8 @@ function getMultiPathLCA(searchPaths) {
function findFilesToUpload(searchPath, globOptions) {
return __awaiter(this, void 0, void 0, function* () {
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();
/*
Files are saved with case insensitivity. Uploading both a.txt and A.txt will files to be overwritten
Expand All @@ -6506,8 +6509,11 @@ function findFilesToUpload(searchPath, globOptions) {
directories so filter any directories out from the raw search results
*/
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()) {
core_1.debug(`File:${searchResult} was found using the provided searchPath`);
searchResults.push(searchResult);
Expand Down Expand Up @@ -6577,14 +6583,17 @@ function getInputs() {
const name = core.getInput(constants_1.Inputs.Name);
const path = core.getInput(constants_1.Inputs.Path, { required: true });
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];
if (!noFileBehavior) {
core.setFailed(`Unrecognized ${constants_1.Inputs.IfNoFilesFound} input. Provided: ${ifNoFilesFound}. Available options: ${Object.keys(constants_1.NoFileOptions)}`);
}
const inputs = {
artifactName: name,
searchPath: path,
ifNoFilesFound: noFileBehavior
ifNoFilesFound: noFileBehavior,
followSymlinks
};
const retentionDaysStr = core.getInput(constants_1.Inputs.RetentionDays);
if (retentionDaysStr) {
Expand Down Expand Up @@ -7521,6 +7530,7 @@ var Inputs;
Inputs["Path"] = "path";
Inputs["IfNoFilesFound"] = "if-no-files-found";
Inputs["RetentionDays"] = "retention-days";
Inputs["FollowSymlinks"] = "follow-symlinks";
})(Inputs = exports.Inputs || (exports.Inputs = {}));
var NoFileOptions;
(function (NoFileOptions) {
Expand Down
Loading