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

This commit is contained in:
Charlie Croom 2024-02-13 12:16:53 -05:00
parent 4c0ff1c489
commit 03a6dff9d4
No known key found for this signature in database
14 changed files with 202 additions and 44 deletions

View File

@ -57,6 +57,7 @@ const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
const inputs = { const inputs = {
[Inputs.Name]: 'my-merged-artifact', [Inputs.Name]: 'my-merged-artifact',
[Inputs.Pattern]: '*', [Inputs.Pattern]: '*',
[Inputs.IfNoFilesFound]: 'error',
[Inputs.SeparateDirectories]: false, [Inputs.SeparateDirectories]: false,
[Inputs.RetentionDays]: 0, [Inputs.RetentionDays]: 0,
[Inputs.CompressionLevel]: 6, [Inputs.CompressionLevel]: 6,
@ -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'}) 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.uploadArtifact).not.toBeCalled()
expect(artifact.downloadArtifact).not.toBeCalled() expect(artifact.downloadArtifact).not.toBeCalled()
}) })

52
dist/merge/index.js vendored
View File

@ -129407,6 +129407,7 @@ var Inputs;
(function (Inputs) { (function (Inputs) {
Inputs["Name"] = "name"; Inputs["Name"] = "name";
Inputs["Pattern"] = "pattern"; Inputs["Pattern"] = "pattern";
Inputs["IfNoFilesFound"] = "if-no-files-found";
Inputs["SeparateDirectories"] = "separate-directories"; Inputs["SeparateDirectories"] = "separate-directories";
Inputs["RetentionDays"] = "retention-days"; Inputs["RetentionDays"] = "retention-days";
Inputs["CompressionLevel"] = "compression-level"; Inputs["CompressionLevel"] = "compression-level";
@ -129486,6 +129487,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getInputs = void 0; exports.getInputs = void 0;
const core = __importStar(__nccwpck_require__(42186)); const core = __importStar(__nccwpck_require__(42186));
const constants_1 = __nccwpck_require__(80746); const constants_1 = __nccwpck_require__(80746);
const constants_2 = __nccwpck_require__(64068);
/** /**
* Helper to get all the inputs for the action * Helper to get all the inputs for the action
*/ */
@ -129494,9 +129496,15 @@ function getInputs() {
const pattern = core.getInput(constants_1.Inputs.Pattern, { required: true }); const pattern = core.getInput(constants_1.Inputs.Pattern, { required: true });
const separateDirectories = core.getBooleanInput(constants_1.Inputs.SeparateDirectories); const separateDirectories = core.getBooleanInput(constants_1.Inputs.SeparateDirectories);
const deleteMerged = core.getBooleanInput(constants_1.Inputs.DeleteMerged); 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 = { const inputs = {
name, name,
pattern, pattern,
ifNoFilesFound: noFileBehavior,
separateDirectories, separateDirectories,
deleteMerged, deleteMerged,
retentionDays: 0, retentionDays: 0,
@ -129574,6 +129582,7 @@ const core = __importStar(__nccwpck_require__(42186));
const minimatch_1 = __nccwpck_require__(61953); const minimatch_1 = __nccwpck_require__(61953);
const artifact_1 = __importDefault(__nccwpck_require__(79450)); const artifact_1 = __importDefault(__nccwpck_require__(79450));
const input_helper_1 = __nccwpck_require__(17661); const input_helper_1 = __nccwpck_require__(17661);
const constants_1 = __nccwpck_require__(64068);
const upload_artifact_1 = __nccwpck_require__(56680); const upload_artifact_1 = __nccwpck_require__(56680);
const search_1 = __nccwpck_require__(8725); const search_1 = __nccwpck_require__(8725);
const PARALLEL_DOWNLOADS = 5; const PARALLEL_DOWNLOADS = 5;
@ -129594,7 +129603,22 @@ function run() {
const artifacts = listArtifactResponse.artifacts.filter(artifact => matcher.match(artifact.name)); const artifacts = listArtifactResponse.artifacts.filter(artifact => matcher.match(artifact.name));
core.debug(`Filtered from ${listArtifactResponse.artifacts.length} to ${artifacts.length} artifacts`); core.debug(`Filtered from ${listArtifactResponse.artifacts.length} to ${artifacts.length} artifacts`);
if (artifacts.length === 0) { 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:`); core.info(`Preparing to download the following artifacts:`);
artifacts.forEach(artifact => { artifacts.forEach(artifact => {
@ -129635,6 +129659,32 @@ function run() {
exports.run = 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: /***/ 8725:

50
dist/upload/index.js vendored
View File

@ -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: /***/ 8725:
@ -129630,7 +129656,7 @@ exports.uploadArtifact = uploadArtifact;
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.NoFileOptions = exports.Inputs = void 0; exports.Inputs = void 0;
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
var Inputs; var Inputs;
(function (Inputs) { (function (Inputs) {
@ -129641,21 +129667,6 @@ var Inputs;
Inputs["CompressionLevel"] = "compression-level"; Inputs["CompressionLevel"] = "compression-level";
Inputs["Overwrite"] = "overwrite"; Inputs["Overwrite"] = "overwrite";
})(Inputs = exports.Inputs || (exports.Inputs = {})); })(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 = {}));
/***/ }), /***/ }),
@ -129730,6 +129741,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getInputs = void 0; exports.getInputs = void 0;
const core = __importStar(__nccwpck_require__(42186)); const core = __importStar(__nccwpck_require__(42186));
const constants_1 = __nccwpck_require__(86154); const constants_1 = __nccwpck_require__(86154);
const constants_2 = __nccwpck_require__(64068);
/** /**
* Helper to get all the inputs for the action * Helper to get all the inputs for the action
*/ */
@ -129738,9 +129750,9 @@ function getInputs() {
const path = core.getInput(constants_1.Inputs.Path, { required: true }); const path = core.getInput(constants_1.Inputs.Path, { required: true });
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_2.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_2.NoFileOptions)}`);
} }
const inputs = { const inputs = {
artifactName: name, artifactName: name,
@ -129815,7 +129827,7 @@ const core = __importStar(__nccwpck_require__(42186));
const artifact_1 = __importStar(__nccwpck_require__(79450)); const artifact_1 = __importStar(__nccwpck_require__(79450));
const search_1 = __nccwpck_require__(8725); const search_1 = __nccwpck_require__(8725);
const input_helper_1 = __nccwpck_require__(67022); 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); const upload_artifact_1 = __nccwpck_require__(56680);
function deleteArtifactIfExists(artifactName) { function deleteArtifactIfExists(artifactName) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {

View File

@ -36,6 +36,14 @@ For most cases, this may not be the most efficient solution. See [the migration
# Optional. Default is '*' # Optional. Default is '*'
pattern: 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:
# If true, the artifacts will be merged into separate directories. # If true, the artifacts will be merged into separate directories.
# If false, the artifacts will be merged into the root of the destination. # If false, the artifacts will be merged into the root of the destination.
# Optional. Default is 'false' # Optional. Default is 'false'

View File

@ -9,6 +9,15 @@ inputs:
pattern: pattern:
description: 'A glob pattern matching the artifact names that should be merged.' description: 'A glob pattern matching the artifact names that should be merged.'
default: '*' 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: separate-directories:
description: 'When multiple artifacts are matched, this changes the behavior of how they are merged in the archive. 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. If true, the matched artifacts will be extracted into individual named directories within the specified path.

View File

@ -2,6 +2,7 @@
export enum Inputs { export enum Inputs {
Name = 'name', Name = 'name',
Pattern = 'pattern', Pattern = 'pattern',
IfNoFilesFound = 'if-no-files-found',
SeparateDirectories = 'separate-directories', SeparateDirectories = 'separate-directories',
RetentionDays = 'retention-days', RetentionDays = 'retention-days',
CompressionLevel = 'compression-level', CompressionLevel = 'compression-level',

View File

@ -1,5 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {Inputs} from './constants' import {Inputs} from './constants'
import {NoFileOptions} from '../shared/constants'
import {MergeInputs} from './merge-inputs' import {MergeInputs} from './merge-inputs'
/** /**
@ -11,9 +12,23 @@ export function getInputs(): MergeInputs {
const separateDirectories = core.getBooleanInput(Inputs.SeparateDirectories) const separateDirectories = core.getBooleanInput(Inputs.SeparateDirectories)
const deleteMerged = core.getBooleanInput(Inputs.DeleteMerged) 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 = { const inputs = {
name, name,
pattern, pattern,
ifNoFilesFound: noFileBehavior,
separateDirectories, separateDirectories,
deleteMerged, deleteMerged,
retentionDays: 0, retentionDays: 0,

View File

@ -4,6 +4,7 @@ import * as core from '@actions/core'
import {Minimatch} from 'minimatch' import {Minimatch} from 'minimatch'
import artifactClient, {UploadArtifactOptions} from '@actions/artifact' import artifactClient, {UploadArtifactOptions} from '@actions/artifact'
import {getInputs} from './input-helper' import {getInputs} from './input-helper'
import {NoFileOptions} from '../shared/constants'
import {uploadArtifact} from '../shared/upload-artifact' import {uploadArtifact} from '../shared/upload-artifact'
import {findFilesToUpload} from '../shared/search' import {findFilesToUpload} from '../shared/search'
@ -32,7 +33,28 @@ export async function run(): Promise<void> {
) )
if (artifacts.length === 0) { 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 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:`) core.info(`Preparing to download the following artifacts:`)

View File

@ -1,3 +1,5 @@
import {NoFileOptions} from '../shared/constants'
export interface MergeInputs { export interface MergeInputs {
/** /**
* The name of the artifact that the artifacts will be merged into * The name of the artifact that the artifacts will be merged into
@ -9,6 +11,11 @@ export interface MergeInputs {
*/ */
pattern: string 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 * Duration after which artifact will expire in days
*/ */

16
src/shared/constants.ts Normal file
View File

@ -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'
}

View File

@ -6,21 +6,4 @@ export enum Inputs {
RetentionDays = 'retention-days', RetentionDays = 'retention-days',
CompressionLevel = 'compression-level', CompressionLevel = 'compression-level',
Overwrite = 'overwrite' Overwrite = 'overwrite'
} }
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'
}

View File

@ -1,5 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {Inputs, NoFileOptions} from './constants' import {Inputs} from './constants'
import {NoFileOptions} from '../shared/constants'
import {UploadInputs} from './upload-inputs' import {UploadInputs} from './upload-inputs'
/** /**

View File

@ -5,7 +5,7 @@ import artifact, {
} from '@actions/artifact' } from '@actions/artifact'
import {findFilesToUpload} from '../shared/search' import {findFilesToUpload} from '../shared/search'
import {getInputs} from './input-helper' import {getInputs} from './input-helper'
import {NoFileOptions} from './constants' import {NoFileOptions} from '../shared/constants'
import {uploadArtifact} from '../shared/upload-artifact' import {uploadArtifact} from '../shared/upload-artifact'
async function deleteArtifactIfExists(artifactName: string): Promise<void> { async function deleteArtifactIfExists(artifactName: string): Promise<void> {

View File

@ -1,4 +1,4 @@
import {NoFileOptions} from './constants' import {NoFileOptions} from '../shared/constants'
export interface UploadInputs { export interface UploadInputs {
/** /**