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

Add if-no-files-found option to merge action #521

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 36 additions & 2 deletions __tests__/merge.test.ts
Expand Up @@ -57,6 +57,7 @@ const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
const inputs = {
[Inputs.Name]: 'my-merged-artifact',
[Inputs.Pattern]: '*',
[Inputs.IfNoFilesFound]: 'error',
[Inputs.SeparateDirectories]: false,
[Inputs.RetentionDays]: 0,
[Inputs.CompressionLevel]: 6,
Expand Down Expand Up @@ -122,11 +123,44 @@ describe('merge', () => {
)
})

it('fails if no artifacts found', async () => {
it('supports error (by default) if no artifacts found', async () => {
mockInputs({[Inputs.Pattern]: 'this-does-not-match'})

expect(run()).rejects.toThrow()
await run()

expect(core.setFailed).toHaveBeenCalledWith(
`No artifacts were found with the provided pattern: this-does-not-match.`
)
expect(artifact.uploadArtifact).not.toBeCalled()
expect(artifact.downloadArtifact).not.toBeCalled()
})

it('supports warn if no artifacts found', async () => {
mockInputs({
[Inputs.Pattern]: 'this-does-not-match',
[Inputs.IfNoFilesFound]: 'warn'
})

await run()

expect(core.warning).toHaveBeenCalledWith(
`No artifacts were found with the provided pattern: this-does-not-match.`
)
expect(artifact.uploadArtifact).not.toBeCalled()
expect(artifact.downloadArtifact).not.toBeCalled()
})

it('supports ignore if no artifacts found', async () => {
mockInputs({
[Inputs.Pattern]: 'this-does-not-match',
[Inputs.IfNoFilesFound]: 'ignore'
})

await run()

expect(core.info).toHaveBeenCalledWith(
`No artifacts were found with the provided pattern: this-does-not-match.`
)
expect(artifact.uploadArtifact).not.toBeCalled()
expect(artifact.downloadArtifact).not.toBeCalled()
})
Expand Down
52 changes: 51 additions & 1 deletion dist/merge/index.js
Expand Up @@ -129407,6 +129407,7 @@ var Inputs;
(function (Inputs) {
Inputs["Name"] = "name";
Inputs["Pattern"] = "pattern";
Inputs["IfNoFilesFound"] = "if-no-files-found";
Inputs["SeparateDirectories"] = "separate-directories";
Inputs["RetentionDays"] = "retention-days";
Inputs["CompressionLevel"] = "compression-level";
Expand Down Expand Up @@ -129486,6 +129487,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getInputs = void 0;
const core = __importStar(__nccwpck_require__(42186));
const constants_1 = __nccwpck_require__(80746);
const constants_2 = __nccwpck_require__(64068);
/**
* Helper to get all the inputs for the action
*/
Expand All @@ -129494,9 +129496,15 @@ function getInputs() {
const pattern = core.getInput(constants_1.Inputs.Pattern, { required: true });
const separateDirectories = core.getBooleanInput(constants_1.Inputs.SeparateDirectories);
const deleteMerged = core.getBooleanInput(constants_1.Inputs.DeleteMerged);
const ifNoFilesFound = core.getInput(constants_1.Inputs.IfNoFilesFound);
const noFileBehavior = constants_2.NoFileOptions[ifNoFilesFound];
if (!noFileBehavior) {
core.setFailed(`Unrecognized ${constants_1.Inputs.IfNoFilesFound} input. Provided: ${ifNoFilesFound}. Available options: ${Object.keys(constants_2.NoFileOptions)}`);
}
const inputs = {
name,
pattern,
ifNoFilesFound: noFileBehavior,
separateDirectories,
deleteMerged,
retentionDays: 0,
Expand Down Expand Up @@ -129574,6 +129582,7 @@ const core = __importStar(__nccwpck_require__(42186));
const minimatch_1 = __nccwpck_require__(61953);
const artifact_1 = __importDefault(__nccwpck_require__(79450));
const input_helper_1 = __nccwpck_require__(17661);
const constants_1 = __nccwpck_require__(64068);
const upload_artifact_1 = __nccwpck_require__(56680);
const search_1 = __nccwpck_require__(8725);
const PARALLEL_DOWNLOADS = 5;
Expand All @@ -129594,7 +129603,22 @@ function run() {
const artifacts = listArtifactResponse.artifacts.filter(artifact => matcher.match(artifact.name));
core.debug(`Filtered from ${listArtifactResponse.artifacts.length} to ${artifacts.length} artifacts`);
if (artifacts.length === 0) {
throw new Error(`No artifacts found matching pattern '${inputs.pattern}'`);
// No files were found, different use cases warrant different types of behavior if nothing is found
switch (inputs.ifNoFilesFound) {
case constants_1.NoFileOptions.warn: {
core.warning(`No artifacts were found with the provided pattern: ${inputs.pattern}.`);
break;
}
case constants_1.NoFileOptions.error: {
core.setFailed(`No artifacts were found with the provided pattern: ${inputs.pattern}.`);
break;
}
case constants_1.NoFileOptions.ignore: {
core.info(`No artifacts were found with the provided pattern: ${inputs.pattern}.`);
break;
}
}
return;
}
core.info(`Preparing to download the following artifacts:`);
artifacts.forEach(artifact => {
Expand Down Expand Up @@ -129635,6 +129659,32 @@ function run() {
exports.run = run;


/***/ }),

/***/ 64068:
/***/ ((__unused_webpack_module, exports) => {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.NoFileOptions = void 0;
var NoFileOptions;
(function (NoFileOptions) {
/**
* Default. Output a warning but do not fail the action
*/
NoFileOptions["warn"] = "warn";
/**
* Fail the action with an error message
*/
NoFileOptions["error"] = "error";
/**
* Do not output any warnings or errors, the action does not fail
*/
NoFileOptions["ignore"] = "ignore";
})(NoFileOptions = exports.NoFileOptions || (exports.NoFileOptions = {}));


/***/ }),

/***/ 8725:
Expand Down
50 changes: 31 additions & 19 deletions dist/upload/index.js
Expand Up @@ -129393,6 +129393,32 @@ function regExpEscape (s) {
}


/***/ }),

/***/ 64068:
/***/ ((__unused_webpack_module, exports) => {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.NoFileOptions = void 0;
var NoFileOptions;
(function (NoFileOptions) {
/**
* Default. Output a warning but do not fail the action
*/
NoFileOptions["warn"] = "warn";
/**
* Fail the action with an error message
*/
NoFileOptions["error"] = "error";
/**
* Do not output any warnings or errors, the action does not fail
*/
NoFileOptions["ignore"] = "ignore";
})(NoFileOptions = exports.NoFileOptions || (exports.NoFileOptions = {}));


/***/ }),

/***/ 8725:
Expand Down Expand Up @@ -129630,7 +129656,7 @@ exports.uploadArtifact = uploadArtifact;
"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.NoFileOptions = exports.Inputs = void 0;
exports.Inputs = void 0;
/* eslint-disable no-unused-vars */
var Inputs;
(function (Inputs) {
Expand All @@ -129641,21 +129667,6 @@ var Inputs;
Inputs["CompressionLevel"] = "compression-level";
Inputs["Overwrite"] = "overwrite";
})(Inputs = exports.Inputs || (exports.Inputs = {}));
var NoFileOptions;
(function (NoFileOptions) {
/**
* Default. Output a warning but do not fail the action
*/
NoFileOptions["warn"] = "warn";
/**
* Fail the action with an error message
*/
NoFileOptions["error"] = "error";
/**
* Do not output any warnings or errors, the action does not fail
*/
NoFileOptions["ignore"] = "ignore";
})(NoFileOptions = exports.NoFileOptions || (exports.NoFileOptions = {}));


/***/ }),
Expand Down Expand Up @@ -129730,6 +129741,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getInputs = void 0;
const core = __importStar(__nccwpck_require__(42186));
const constants_1 = __nccwpck_require__(86154);
const constants_2 = __nccwpck_require__(64068);
/**
* Helper to get all the inputs for the action
*/
Expand All @@ -129738,9 +129750,9 @@ function getInputs() {
const path = core.getInput(constants_1.Inputs.Path, { required: true });
const overwrite = core.getBooleanInput(constants_1.Inputs.Overwrite);
const ifNoFilesFound = core.getInput(constants_1.Inputs.IfNoFilesFound);
const noFileBehavior = constants_1.NoFileOptions[ifNoFilesFound];
const noFileBehavior = constants_2.NoFileOptions[ifNoFilesFound];
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_2.NoFileOptions)}`);
}
const inputs = {
artifactName: name,
Expand Down Expand Up @@ -129815,7 +129827,7 @@ const core = __importStar(__nccwpck_require__(42186));
const artifact_1 = __importStar(__nccwpck_require__(79450));
const search_1 = __nccwpck_require__(8725);
const input_helper_1 = __nccwpck_require__(67022);
const constants_1 = __nccwpck_require__(86154);
const constants_1 = __nccwpck_require__(64068);
const upload_artifact_1 = __nccwpck_require__(56680);
function deleteArtifactIfExists(artifactName) {
return __awaiter(this, void 0, void 0, function* () {
Expand Down
8 changes: 8 additions & 0 deletions merge/README.md
Expand Up @@ -36,6 +36,14 @@ For most cases, this may not be the most efficient solution. See [the migration
# Optional. Default is '*'
pattern:

# The desired behavior if no artifacts are found using the provided pattern.
# Available Options:
# warn: Output a warning but do not fail the action
# error: Fail the action with an error message
# ignore: Do not output any warnings or errors, the action does not fail
# Optional. Default is 'error'
if-no-files-found:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK we're talking about artifacts, not files. That's why I would call it at least if-no-artifacts-found. However, some other wording options (I don't have a strong opinion here, just some thoughts to find a good solution):

  • no-artifacts-behavior / options: ["warn", "error", "ignore"]
  • no-artifacts-behavior / options: ["exit-with-warning", "fail", "exit-gracefully"]


# If true, the artifacts will be merged into separate directories.
# If false, the artifacts will be merged into the root of the destination.
# Optional. Default is 'false'
Expand Down
9 changes: 9 additions & 0 deletions merge/action.yml
Expand Up @@ -9,6 +9,15 @@ inputs:
pattern:
description: 'A glob pattern matching the artifact names that should be merged.'
default: '*'
if-no-files-found:
description: >
The desired behavior if no artifacts are found using the provided pattern.

Available Options:
warn: Output a warning but do not fail the action
error: Fail the action with an error message
ignore: Do not output any warnings or errors, the action does not fail
default: 'error'
separate-directories:
description: 'When multiple artifacts are matched, this changes the behavior of how they are merged in the archive.
If true, the matched artifacts will be extracted into individual named directories within the specified path.
Expand Down
1 change: 1 addition & 0 deletions src/merge/constants.ts
Expand Up @@ -2,6 +2,7 @@
export enum Inputs {
Name = 'name',
Pattern = 'pattern',
IfNoFilesFound = 'if-no-files-found',
SeparateDirectories = 'separate-directories',
RetentionDays = 'retention-days',
CompressionLevel = 'compression-level',
Expand Down
15 changes: 15 additions & 0 deletions src/merge/input-helper.ts
@@ -1,5 +1,6 @@
import * as core from '@actions/core'
import {Inputs} from './constants'
import {NoFileOptions} from '../shared/constants'
import {MergeInputs} from './merge-inputs'

/**
Expand All @@ -11,9 +12,23 @@ export function getInputs(): MergeInputs {
const separateDirectories = core.getBooleanInput(Inputs.SeparateDirectories)
const deleteMerged = core.getBooleanInput(Inputs.DeleteMerged)

const ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound)
const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound]

if (!noFileBehavior) {
core.setFailed(
`Unrecognized ${
Inputs.IfNoFilesFound
} input. Provided: ${ifNoFilesFound}. Available options: ${Object.keys(
NoFileOptions
)}`
)
}

const inputs = {
name,
pattern,
ifNoFilesFound: noFileBehavior,
separateDirectories,
deleteMerged,
retentionDays: 0,
Expand Down
24 changes: 23 additions & 1 deletion src/merge/merge-artifacts.ts
Expand Up @@ -4,6 +4,7 @@ import * as core from '@actions/core'
import {Minimatch} from 'minimatch'
import artifactClient, {UploadArtifactOptions} from '@actions/artifact'
import {getInputs} from './input-helper'
import {NoFileOptions} from '../shared/constants'
import {uploadArtifact} from '../shared/upload-artifact'
import {findFilesToUpload} from '../shared/search'

Expand Down Expand Up @@ -32,7 +33,28 @@ export async function run(): Promise<void> {
)

if (artifacts.length === 0) {
throw new Error(`No artifacts found matching pattern '${inputs.pattern}'`)
// No files were found, different use cases warrant different types of behavior if nothing is found
switch (inputs.ifNoFilesFound) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would definitely add a default behavior of the switch statement emitting an error in case the value is something not yet know, even though the values are validated before. Just to make sure somebody implementing a new option can not forget it.

case NoFileOptions.warn: {
core.warning(
`No artifacts were found with the provided pattern: ${inputs.pattern}.`
)
break
}
case NoFileOptions.error: {
core.setFailed(
`No artifacts were found with the provided pattern: ${inputs.pattern}.`
)
break
}
case NoFileOptions.ignore: {
core.info(
`No artifacts were found with the provided pattern: ${inputs.pattern}.`
)
break
}
}
return;
}

core.info(`Preparing to download the following artifacts:`)
Expand Down
7 changes: 7 additions & 0 deletions src/merge/merge-inputs.ts
@@ -1,3 +1,5 @@
import {NoFileOptions} from '../shared/constants'

export interface MergeInputs {
/**
* The name of the artifact that the artifacts will be merged into
Expand All @@ -9,6 +11,11 @@ export interface MergeInputs {
*/
pattern: string

/**
* The desired behavior if no files are found with the provided search path
*/
ifNoFilesFound: NoFileOptions

/**
* Duration after which artifact will expire in days
*/
Expand Down
16 changes: 16 additions & 0 deletions src/shared/constants.ts
@@ -0,0 +1,16 @@
export enum NoFileOptions {
/**
* Default. Output a warning but do not fail the action
*/
warn = 'warn',

/**
* Fail the action with an error message
*/
error = 'error',

/**
* Do not output any warnings or errors, the action does not fail
*/
ignore = 'ignore'
}